如何从C中的堆栈指针获取返回地址?

时间:2021-02-17 08:54:24

标签: c

抱歉,我的 C 有点生疏了。

我想仅使用堆栈指针的地址来获取返回地址作为练习。 我知道我可以使用 __builtin_return_address 函数直接获取返回地址,但我想使用堆栈指针手动获取。

现在我只有指向返回地址的指针的地址,但我想获得实际地址。

void a() {
    void *sp = __builtin_frame_address(0);
    printf("%p\n", (*(&sp) + 0x8));
}

int main() {
    a();
}

1 个答案:

答案 0 :(得分:3)

来自此扩展程序的 GNU documentation

<块引用>

6.51 获取函数的返回地址或帧地址

这些函数可用于获取有关函数调用者的信息。

内置函数:void * __builtin_return_address (unsigned int level)

此函数返回当前函数或其调用者之一的返回地址。 level 参数是扫描调用堆栈的帧数。 0 的值产生当前函数的返回地址,1 的值产生当前函数调用者的返回地址,依此类推。内联时的预期行为是函数返回返回的函数的地址。要解决此问题,请使用 noinline 函数属性。

级别参数必须是一个常量整数。

在某些机器上,可能无法确定除当前函数之外的任何函数的返回地址;在这种情况下,或者当到达栈顶时,该函数返回一个未指定的值。此外,__builtin_frame_address 可用于确定是否已到达堆栈顶部。

可能需要对返回值进行额外的后处理,请参阅 __builtin_extract_return_addr

返回地址在内存中的存储表示可能与__builtin_return_address返回的地址不同。例如,在 AArch64 上,存储的地址可能会使用返回地址签名进行修改,而 __builtin_return_address 返回的地址则不会。

使用非零参数调用此函数可能会产生不可预测的影响,包括调用程序崩溃。因此,当 -Wframe-address 选项生效时,会诊断被认为不安全的调用。此类调用仅应在调试情况下进行。

在代码地址可表示为 void * 的目标上,

void *addr = __builtin_extract_return_addr(__builtin_return_address(0)); 给出当前函数将返回的代码地址。例如,这样的地址可以与 dladdr 或其他处理代码地址的接口一起使用。

内置函数:void * __builtin_extract_return_addr(void *addr)

__builtin_return_address 返回的地址可能必须通过此函数输入才能获得实际编码地址。例如,在 31 位 S/390 平台上,最高位必须被屏蔽,或者在 SPARC 平台上,必须添加偏移量才能执行真正的下一条指令。

如果不需要修正,这个函数只需通过 addr

内置函数:void * __builtin_frob_return_addr(void *addr)

此函数执行 __builtin_extract_return_addr 的相反操作。

内置函数:void * __builtin_frame_address(unsigned int level)

这个函数类似于__builtin_return_address,但它返回的是函数帧的地址,而不是函数的返回地址。以 __builtin_frame_address 的值调用 0 会产生当前函数的帧地址,1 的值会产生当前函数调用者的帧地址,依此类推。

帧是堆栈上保存局部变量和保存寄存器的区域。帧地址通常是函数压入堆栈的第一个字的地址。但是,确切的定义取决于处理器和调用约定。如果处理器有专用的帧指针寄存器,并且函数有帧,则__builtin_frame_address返回帧指针寄存器的值。

在某些机器上,可能无法确定除当前函数之外的任何函数的帧地址;在这种情况下,或者当到达栈顶时,如果第一帧指针被启动代码正确初始化,这个函数将返回 0。

使用非零参数调用此函数可能会产生不可预测的影响,包括调用程序崩溃。因此,当 -Wframe-address 选项生效时,会诊断被认为不安全的调用。此类调用仅应在调试情况下进行。

修改后的代码如下:

#include <stdio.h>

void a() {
    printf("return address: %p\n", __builtin_extract_return_addr(__builtin_return_address(0)));
}

int main() {
    a();
    return 0;
}

从帧地址中获取返回地址并不总是可能的,并且是非常系统特定的,它还取决于函数调用约定。编译器在编译内置 __builtin_return_address 时会考虑这些因素,尝试从 __builtin_frame_address 的值手动完成是非常棘手的。

如果您的目标是构建一个自省系统,您可能需要分别存储框架和返回地址。您将需要解析调试信息以尝试理解通过这些指针可访问的数据。