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

我知道,如果我在里面的某个函数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函数处于活动状态时,我会得到第一个推送的本地variables的地址。 我怎样才能访问返回地址(主叫做foo),这个地址在堆栈之前的某个地方被推到了这个variables之前? 该位置是否固定,可以相对于第一个局部variables进行访问? 我怎样才能修改它?

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

Solutions Collecting From Web of "我如何确定栈上的返回地址?"

有一个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 $ 

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

如果你然后声明一个int * xptr并将它初始化为&x ,它将指向x。

没有什么(很多)阻止你将指针递减一点,或者增加它以后再看。 你周围的某个地方有你的回信地址。

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

除非使用编译器内置函数获取返回地址,否则您将不得不求助于内联汇编器来获取值。 其他似乎在调试中起作用的技术对于编译器优化来说是非常可恶的。

你可以像这样探索堆栈

 // 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是返回地址相对于虚拟数组的地址。

谨慎的一句话 – 阅读所有你想要的,但不要修改任何使用该数组,除非(一)你是绝对确定你正在修改的堆栈的哪一部分,或者(二)只是想看到一个有趣的/不可预知的崩溃模式。

另请注意,一般来说,C语言并不能保证你的返回地址是在一个栈上,或者在RAM的任何地方。

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

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

尝试这个

 //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