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

问题描述 投票:0回答:1

我最近一直在尝试使用此处描述的__builtin_extract_return_addr函数(https://gcc.gnu.org/onlinedocs/gcc/Return-Address.html)获得RSP指针的编码值。我有意避免使用__builtin_return_address(0)函数,而只是尝试使用RSP寄存器将返回地址值返回给调用方。

此功能的说明如前所述:

void * __ builtin_extract_return_addrvoid * addr

由__builtin_return_address返回的地址可能必须通过此函数提供,以获取实际的编码地址。如果不需要修复,则此函数仅通过addr传递。

据我所知,似乎该函数可以获取任意地址并获得实际的编码地址。 (例如,如果RSP 0x7fffffffe458 —▸ 0x40058e (main+14),然后使用__builtin_extract_return_addr(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寄存器的值,然后使用builtin_extract_return_addr查看是否可以获取RSP寄存器中存储内容的编码地址。这是我使用gdb调试时的失败,我理解它是因为调用此行时的RSP寄存器值将没有返回到调用方的地址。

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

RSP 0x7fffffffe458 —▸ 0x40058e (main+14) ◂— mov eax, 0

总而言之,我正在尝试使用__builtin_return_address(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);

[Here是print_sp和main()函数的反汇编

[此外,这是我看过的StackOverflow中发布的一些类似问题:Retq instruction, where does it returnMeaning of 0x8($rsp)

我希望这个问题有意义。请让我知道是否有任何不清楚的地方,我会尽力澄清它们。

谢谢,

c gcc return inline-assembly cpu-registers
1个回答
0
投票

我不确定为什么要避免使用__builtin_return_address(0),因为它可能是获取返回地址的最佳选择。如果可以使用它,请这样做,但是我们现在将继续使用它。

您的反汇编显示您在函数序言中使用了帧指针寄存器,这意味着您可能在编译中使用零优化。在这种情况下,您可以使用rbp而不是rsp,因为它在函数开始时采用rsp的值并保持不变。但是,您将需要在从rbp获得的指针上添加+1(请注意,在void指针上添加+1实际上会在x86_64中添加8个字节)。这样做是因为在将rsp复制到rbp的mov指令之前,有一条push指令会将旧的rbp值推入堆栈,因此存储在rsp中的堆栈指针已减少了8个字节(堆栈向下增长)以保存旧帧指针私下存储在rbp中。此过程是x86程序集中的调用约定,如果您需要更多了解相对于帧指针和堆栈指针而言,返回地址在堆栈中的存储位置,则可以阅读此过程。

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

void *bp = NULL;
asm ("movq %%rbp, %0\n"
     : "=r" (bp));
void *ra;
ra = (void *) *((long *)bp + 1);

ra现在应该是您的寄信人地址。不幸的是,这仅在编译器使用帧指针寄存器rbp时才有效,而在编译时使用优化通常不起作用。例如,如果您使用的是GCC,则使用-o0以外的任何级别都会使您出错。万一您应该使用__builtin_extract_return_addr(ra),就我而言,我并不需要它。还请注意,除非您进行静态链接,否则返回地址将是您在反汇编+可执行文件起始地址(这是您的可执行文件在内存中加载的位置)中看到的值。您可以通过全局声明extern char __executable_start;然后打印其地址来获取此地址。

© www.soinside.com 2019 - 2024. All rights reserved.