使用_builtin_extract_return_addr()函数查找ret指令的RSP值

使用_builtin_extract_return_addr()函数查找ret指令的RSP值,c,gcc,return,inline-assembly,cpu-registers,C,Gcc,Return,Inline Assembly,Cpu Registers,我最近一直在尝试使用这里描述的_u内置的_extract_return_addr函数()来获取RSP指针的编码值。我故意避免使用uu内置的u return_address(0)函数,而只是尝试使用RSP寄存器向调用者获取返回地址值 此功能的说明如下所述: void*内置提取返回地址(void*addr) 由uu内置u return u address返回的地址可能必须通过此函数来获取实际的编码地址。如果不需要修正,这个函数只需通过addr 根据我的理解,这个函数似乎可以获取任意地址并获得实际的编

我最近一直在尝试使用这里描述的_u内置的_extract_return_addr函数()来获取RSP指针的编码值。我故意避免使用uu内置的u return_address(0)函数,而只是尝试使用RSP寄存器向调用者获取返回地址值

此功能的说明如下所述:

void*内置提取返回地址(void*addr)

由uu内置u return u address返回的地址可能必须通过此函数来获取实际的编码地址。如果不需要修正,这个函数只需通过addr

根据我的理解,这个函数似乎可以获取任意地址并获得实际的编码地址。(例如,如果
RSP 0x7FFFFFE458-▸ 0x40058e(主+14)
, 然后使用内置提取返回地址(0x7fffffffe458)应该是0x40058e )

因此,我有一个非常简单的测试代码,我一直在使用它来了解这一点,但没有得到我试图得到的值,所以我想在StackOverflow中问一个问题:

void print_sp() {
   register void *sp asm ("rsp");
   printf("%p\n", __builtin_extract_return_addr(sp));

   void *addr = 0x7fffffffe458;
   printf("%p\n", __builtin_extract_return_addr((addr)));

   printf("%p\n", __builtin_return_address(0)); // I am trying to avoid using this
}

int main() {
   print_sp();
}

在print_sp()函数的前两行中,我读取并打印RSP寄存器值,然后使用内置的extract_return_addr查看是否可以获得RSP寄存器中存储内容的编码地址。这是一个失败,因为我使用gdb进行调试,我理解这是因为调用此行时的RSP寄存器值将没有返回给调用方的地址

在print_sp()函数的第二行中,我将void*addr硬编码为0x7fffffe458的值,然后使用此地址值查看是否可以获得解码的返回地址。原因是在执行
ret
指令时,RSP值如下所示 详情如下:

RSP 0x7FFFFFE458-▸ 0x40058e(主+14)◂— mov eax,0

总而言之,我正在尝试使用_内置的返回地址(0)值获取返回地址值0x400578,而不使用

我还尝试实现了
addq$8、%%rsp;jmpq-8(%%rsp)
使用内联程序集,但无效。像这样的

uintptr_t result;
asm volatile ( "mov %%rsp, %[value]\n\t"
               "addq $8,   %[value]\n\t"
                 : [value]"=a"(result)
                 : ); 
uintptr_t  caller_address = (uintptr_t)__builtin_extract_return_addr(result);
是print_sp和main()函数的反汇编

此外,这里有一些类似的问题张贴在StackOverflow中,我看了一下:

我希望这个问题有意义。请让我知道,如果有任何不清楚的地方,我会尝试澄清他们马上


谢谢,

我不知道您为什么要避免使用
\u内置返回地址(0)
,因为这可能是获取返回地址的最佳选择。如果你能使用它,请使用它,但我们现在将使用它

反汇编表明您正在函数序言中使用帧指针寄存器,这意味着您可能在编译中使用零优化。在这种情况下,您可以使用rbp而不是rsp,因为它在函数开始时接受rsp值并保持不变。但是,您需要将+1添加到从rbp获得的指针中(请注意,将+1添加到
long
指针实际上会在x86_64中添加8个字节)

之所以这样做,是因为在将rsp复制到rbp的mov指令之前,有一个push指令将旧的rbp值推送到堆栈中,因此我们存储在rsp中的堆栈指针已减少(堆栈向下增长)8个字节,以保存以前存储在rbp中的旧帧指针

在x86-64调用约定中,将RBP设置为帧指针的过程是可选的。16位x86过去需要这样做,一些32位调用约定依赖它进行回溯,但x86-64调用约定不需要。如果需要更多地了解返回地址相对于帧指针和堆栈指针存储在堆栈中的位置,可以阅读相关内容

我认为不推荐使用寄存器变量,因此我将修改内联汇编的第二种方法

  // works only if compiled without optimization, or -fno-omit-frame-pointer
  // RBP points to the saved-RBP value just below the return address
void *bp;
asm ("movq %%rbp, %0\n"
     : "=r" (bp));
void *ra;
ra = (void *) *((long *)bp + 1);
ra现在应该是您的回信地址。不幸的是,这只有在编译器使用帧指针寄存器rbp时才起作用,而在编译时使用优化时,帧指针寄存器rbp通常不起作用。例如,如果您使用的是GCC,则除
-O0
之外的任何级别使用此方法都会出现错误


为了以防万一,您应该使用
\u内置\u提取\u返回\u地址(ra)
。我的案子不需要它。还请注意,除非您链接的是非饼图可执行文件,否则返回地址将是您在反汇编+可执行文件开始地址(即可执行文件加载到内存中的位置)中看到的值。您可以通过声明
extern char\uuu executable\u start来获取此地址
全局打印其地址。

注意,在Windows调用约定中,
long
仅为32位;如果您想在x86-64上使用“堆栈插槽”,请使用
uint64。(不是
uintptr\u t
,在像Linux x32这样的ILP32 ABI中只有32位。)非常感谢您的建议,这是很有见解的。