代码之家  ›  专栏  ›  技术社区  ›  Vinit Dhatrak

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

  •  17
  • Vinit Dhatrak  · 技术社区  · 15 年前

    我知道如果我在某个功能里面 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处理器上的Ubuntu9.04。

    6 回复  |  直到 9 年前
        1
  •  25
  •   DigitalRoss    15 年前

    为此,有一个GCC内置: void * __builtin_return_address (unsigned int level)

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

    在某些架构中,您可以在堆栈中相对于第一个参数找到它。例如,在ia32上,参数被推送(按相反的顺序),然后调用将推送返回地址。记住,堆栈几乎总是(在ia32上)增长 向下地 . 尽管技术上你需要 阿比 调用规则 (有时打电话 链接约定 )对于您的语言和硬件平台,在实践中,您通常可以猜测您是否知道过程调用machine op是如何工作的。

    函数的第一个参数和返回地址在堆栈上的位置之间的关系比本地地址和返回地址之间的关系更可能是可靠的固定值。但是,您当然可以打印出本地和第一个参数的地址,并且您经常会发现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
    $ 
    
        2
  •  3
  •   Carl Smotricz    15 年前

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

    如果您随后声明 int * xptr 并将其初始化为 &x ,它将指向X。

    没有什么可以阻止你减少指针,让它在前面看一看,或者增加它以便以后看。这附近有你的寄信人地址。

        3
  •  2
  •   Rob Walker    15 年前

    要知道寄信人的地址,你需要知道 calling convention 是。这通常由编译器设置,并取决于平台,但您可以用特定于平台的方式强制它,例如使用 __declspec(stdcall) 在窗户上。优化编译器还可以为没有外部作用域的函数发明自己的调用约定。

    除非使用编译器内置程序来获取返回地址,否则必须使用内联汇编程序来获取值。其他在调试中起作用的技术对于编译器的优化来说是非常有用的。

        4
  •  2
  •   JustJeff    15 年前

    你可以像这样在烟囱周围探测

    // 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以索引数组的方式不是特别出名。在这里,您在堆栈上声明一个虚拟数组,然后查看与之相关的+/-。

    正如RobWalker所指出的,您肯定需要了解编译器调用约定,以了解您正在查看的数据。您可以打印出一些函数的地址,并查找类似范围内的值,并在返回地址所在的位置直观地相对于虚拟数组。

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

        5
  •  1
  •   unwind    15 年前

    请注意,一般来说,C语言不能保证您的返回地址在堆栈上,或者实际上在RAM中的任何地方。

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

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

        6
  •  -1
  •   enthusiasticgeek    13 年前

    试试这个

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