如何确定堆栈上的返回地址?

时间:2009-11-07 13:22:29

标签: c linux gcc stack

我知道如果我在某个函数foo()里面,这个函数在bar()函数的某个地方被调用,那么这个返回地址就会被推送到堆栈上。

    #include <stdio.h>

    void foo()
    {
            unsigned int x;
            printf("inside foo %x\n", &x);
    }
    int main()
    {
            foo();
            printf("in main\n");
            return 0;
    }

在上面的代码中,当foo函数处于活动状态时,我将获得堆栈上第一个推送的局部变量的地址。如何访问在堆栈上此变量之前某处推送的返回地址(主要称为foo)?该位置是固定的,可以相对于第一个局部变量访问吗?我怎么修改它?

编辑:我的环境是带有gcc编译器的x86处理器上的Ubuntu 9.04。

6 个答案:

答案 0 :(得分:26)

内置gcc:void * __builtin_return_address (unsigned int level)

请参阅http://gcc.gnu.org/onlinedocs/gcc/Return-Address.html

在某些体系结构上,您可以在堆栈中找到它相对于第一个参数。例如,在ia32上,按下参数(按相反顺序),然​​后调用将推送返回地址。请记住,堆栈几乎总是(并且在ia32上)向下。虽然从技术上讲,您需要为您的语言和硬件平台提供 ABI 调用约定(有时称为链接约定),但实际上您通常可以猜测是否你知道程序调用机器的运作方式。

函数的第一个参数与堆栈上的返回地址的位置之间的关系更可能是一个可靠的固定值,而不是本地和返回地址之间的关系。但是,您当然可以打印出本地地址和第一个参数的地址,并且您经常可以找到它们之间的PC。

$ expand < ra.c
#include <stdio.h>

int main(int ac, char **av) {
  printf("%p\n", __builtin_return_address(0));
  return 0;
}
$ cc -Wall ra.c; ./a.out
0xb7e09775
$ 

答案 1 :(得分:3)

当你声明局部变量时,它们也在堆栈上 - 例如x。

如果您随后宣布int * xptr并将其初始化为&x,则会指向x。

没有(多)阻止你将指针递减一点之前,或者将其递增以便稍后查看。在附近有你的回信地址。

答案 2 :(得分:2)

要知道返回地址的位置,您需要知道calling convention是什么。这通常由编译器设置并依赖于平台,但您可以以特定于平台的方式强制它,例如在Windows上使用__declspec(stdcall)。优化编译器也可以为没有外部作用域的函数创建自己的调用约定。

除非使用编译器内置函数来获取返回地址,否则您必须求助内联汇编程序来获取值。其他似乎在调试中工作的技术对于编译器优化非常有用。

答案 3 :(得分:2)

你可以像这样探测堆栈

// assuming a 32 bit machine here
void digInStack(void) {

    int i;
    long sneak[1];

    // feel free to adjust the search limits
    for( i = -32; i <= 32; ++i) {
        printf("offset %3d: data 0x%08X\n", i, sneak[i]);
    }
 }

你可以侥幸成功,因为C因为你对数组的索引不是特别有名而闻名。在这里,你在堆栈上声明一个虚拟数组,然后相对于它调整+/-。

正如Rob Walker所指出的,你肯定需要知道你的编译器调用约定,以了解你正在查看的数据。您可以打印出一些函数的地址,并查找相似范围内的值,以及返回地址相对于虚拟数组的intuit。

注意事项 - 阅读所有你想要的内容,但不要使用该数组修改任何内容,除非(a)你完全确定你正在修改的堆栈的哪个部分,或者(b)只是想要看到一个有趣/不可预测的崩溃模式。

答案 4 :(得分:1)

另请注意,一般情况下,C语言不保证您的返回地址在堆栈中,或者实际上在RAM中的任何位置。

有些处理器体系结构将返回地址存储在寄存器中,仅在调用开始嵌套时才使用RAM。还有其他架构,其中有一个单独的堆栈用于返回地址,CPU无法读取。这两个仍然可以为它们实现C编译器。

这就是为什么你需要更清楚你的环境。

答案 5 :(得分:-1)

试试这个

//test1.cc
//compile with
//g++ -g test1.cc -o test1 

#include <stdio.h>

void
print_function(void *p) {
    char cmd[128];
    FILE *fp;

    snprintf(cmd, sizeof(cmd), "addr2line -e %s -f %p", "test1", p);
    fp = popen(cmd, "r");
    if (fp) {
    char buf[128];
    while (fgets(buf, sizeof(buf), fp)) {
        printf("%s", buf); 
    }
    }
}

void
f2(void) {
    print_function(__builtin_return_address(0));
}

void
f1(void) {
    f2();
}

int
main(int argc, char *argv[]) {
    f1();
    return(0);
}

输出应该看起来像

_Z2f1v
/home/<user>/<dir>/test1.cc:30