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

时间:2021-12-20 07:09:45

I know that if I am inside some function foo() which is called somewhere from bar() function, then this return address is pushed on 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;
    }

In above code, I will get address of first pushed local variable on stack when foo function is active. How can I access the return address (main called foo) that is pushed somewhere before this variable on stack? Is that location fixed and can be accessed relative to first local variable? How can I modify it?

在上面的代码中,当foo函数是活动的时候,我将会得到栈上第一个按下的本地变量的地址。如何访问返回地址(main调用foo),该地址在堆栈上的这个变量之前的某个地方被推?该位置是固定的,可以相对于第一个本地变量访问吗?我如何修改它?

EDIT: My environment is Ubuntu 9.04 on x86 processor with gcc compiler.

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

6 个解决方案

#1


24  

There is a gcc builtin for this: void * __builtin_return_address (unsigned int level)

为此有一个gcc内置:void * __builtin_return_address(无符号int级别)

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

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

On some architectures, you can find it on the stack relative to the first parameter. On ia32, for example, the parameters are pushed (in opposite order) and then a call is made that will push the return address. Remember that the stack almost always (and on ia32) grows downward. Although technically you need the ABI or calling conventions (sometimes called linkage conventions) for your language and hardware platform, in practice you can usually guess if you know how the procedure call machine op works.

在某些体系结构中,您可以相对于第一个参数在堆栈中找到它。例如,在ia32上,参数被推(顺序相反),然后进行调用,从而推入返回地址。请记住,堆栈几乎总是向下增长(在ia32上也是如此)。虽然在技术上,您需要语言和硬件平台的ABI或调用约定(有时称为链接约定),但是实际上,如果您知道过程调用机器op的工作方式,您通常可以猜测。

The relationship between the first parameter to a function and the position of the return address on the stack is far more likely to be a reliably fixed value than the relationship between a local and the return address. However, you can certainly print out the address of a local, and of the first parameter, and you will often find the PC right in between.

第一个参数与一个函数的关系和堆栈上返回地址的位置之间的关系比本地和返回地址之间的关系更有可能是一个可靠的固定值。但是,您当然可以打印本地的地址和第一个参数的地址,并且您经常会在中间找到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  

When you declare local variables, they are also on the stack - x, for instance.

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

If you then declare an int * xptr and initialize it to &x, it will point at x.

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

Nothing (much) stops you from decrementing that pointer to peek a little before, or incrementing it to look later. Somewhere around there is your return address.

没有什么(多少)能阻止您将该指针递减到之前的水平,或者递增到以后的水平。你的回信地址就在附近。

#3


2  

To know where the return address is you need to know what the calling convention is. This will typically be set by the compiler and depends on the platform, but you can force it in platform specific ways, for example using __declspec(stdcall) on windows. An optimizing compiler may also invent its own calling convention for functions that don't have external scope.

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

Barring the use of compiler built-ins to get the return address you would have to resort to inline assembler to get the value. Other techniques that appear to work in debug would be very vunerable to compiler optimizations messing them up.

除非使用编译器内嵌来获取返回地址,否则您必须使用内联汇编程序来获取值。其他似乎可以在调试中工作的技术对编译器优化来说是很难搞砸的。

#4


2  

You can probe around the stack like so

您可以像这样探测堆栈

// 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]);
    }
 }

You can get away with this because C is famous for not be very particular in how you index an array. Here, you declare a dummy array on the stack, and then peek around +/- relative to that.

你可以不这么做,因为C很有名,因为它不是很特别的你如何索引一个数组。在这里,您在堆栈上声明一个虚拟数组,然后查看+/-。

As Rob Walker pointed out, you definitely need to know your compilers calling convention, to understand the data you are looking at. You might print out the address of a few functions, and look for values that are in a similar range, and intuit where the return address is, relative to the dummy array.

正如Rob Walker指出的,您肯定需要知道编译器调用约定,才能理解您正在查看的数据。您可以打印出一些函数的地址,并查找在类似范围内的值,并凭直觉知道返回地址在哪里,相对于虚拟数组。

Word of caution - read all you want, but don't modify anything using that array, unless either (a) you are absolutely sure about what part of the stack it is you are modifying, or (b) just want to see an interesting/unpredictable crash mode.

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

#5


1  

Please also note that in general there is no guarantee made by the C language that your return address is on a stack, or indeed anywhere in RAM, at all.

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

There are processor architectures that store the return address in a register, resorting to RAM only when calls start to nest. There are other architectures where there is a separate stack for return addresses, that is not readable by the CPU. Both of these can still have C compilers implemented for them.

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

This is why you need to be clearer with your environment.

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

#6


-1  

Try this

试试这个

//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);
}

The output should look like

输出应该是这样的

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

#1


24  

There is a gcc builtin for this: void * __builtin_return_address (unsigned int level)

为此有一个gcc内置:void * __builtin_return_address(无符号int级别)

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

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

On some architectures, you can find it on the stack relative to the first parameter. On ia32, for example, the parameters are pushed (in opposite order) and then a call is made that will push the return address. Remember that the stack almost always (and on ia32) grows downward. Although technically you need the ABI or calling conventions (sometimes called linkage conventions) for your language and hardware platform, in practice you can usually guess if you know how the procedure call machine op works.

在某些体系结构中,您可以相对于第一个参数在堆栈中找到它。例如,在ia32上,参数被推(顺序相反),然后进行调用,从而推入返回地址。请记住,堆栈几乎总是向下增长(在ia32上也是如此)。虽然在技术上,您需要语言和硬件平台的ABI或调用约定(有时称为链接约定),但是实际上,如果您知道过程调用机器op的工作方式,您通常可以猜测。

The relationship between the first parameter to a function and the position of the return address on the stack is far more likely to be a reliably fixed value than the relationship between a local and the return address. However, you can certainly print out the address of a local, and of the first parameter, and you will often find the PC right in between.

第一个参数与一个函数的关系和堆栈上返回地址的位置之间的关系比本地和返回地址之间的关系更有可能是一个可靠的固定值。但是,您当然可以打印本地的地址和第一个参数的地址,并且您经常会在中间找到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  

When you declare local variables, they are also on the stack - x, for instance.

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

If you then declare an int * xptr and initialize it to &x, it will point at x.

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

Nothing (much) stops you from decrementing that pointer to peek a little before, or incrementing it to look later. Somewhere around there is your return address.

没有什么(多少)能阻止您将该指针递减到之前的水平,或者递增到以后的水平。你的回信地址就在附近。

#3


2  

To know where the return address is you need to know what the calling convention is. This will typically be set by the compiler and depends on the platform, but you can force it in platform specific ways, for example using __declspec(stdcall) on windows. An optimizing compiler may also invent its own calling convention for functions that don't have external scope.

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

Barring the use of compiler built-ins to get the return address you would have to resort to inline assembler to get the value. Other techniques that appear to work in debug would be very vunerable to compiler optimizations messing them up.

除非使用编译器内嵌来获取返回地址,否则您必须使用内联汇编程序来获取值。其他似乎可以在调试中工作的技术对编译器优化来说是很难搞砸的。

#4


2  

You can probe around the stack like so

您可以像这样探测堆栈

// 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]);
    }
 }

You can get away with this because C is famous for not be very particular in how you index an array. Here, you declare a dummy array on the stack, and then peek around +/- relative to that.

你可以不这么做,因为C很有名,因为它不是很特别的你如何索引一个数组。在这里,您在堆栈上声明一个虚拟数组,然后查看+/-。

As Rob Walker pointed out, you definitely need to know your compilers calling convention, to understand the data you are looking at. You might print out the address of a few functions, and look for values that are in a similar range, and intuit where the return address is, relative to the dummy array.

正如Rob Walker指出的,您肯定需要知道编译器调用约定,才能理解您正在查看的数据。您可以打印出一些函数的地址,并查找在类似范围内的值,并凭直觉知道返回地址在哪里,相对于虚拟数组。

Word of caution - read all you want, but don't modify anything using that array, unless either (a) you are absolutely sure about what part of the stack it is you are modifying, or (b) just want to see an interesting/unpredictable crash mode.

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

#5


1  

Please also note that in general there is no guarantee made by the C language that your return address is on a stack, or indeed anywhere in RAM, at all.

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

There are processor architectures that store the return address in a register, resorting to RAM only when calls start to nest. There are other architectures where there is a separate stack for return addresses, that is not readable by the CPU. Both of these can still have C compilers implemented for them.

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

This is why you need to be clearer with your environment.

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

#6


-1  

Try this

试试这个

//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);
}

The output should look like

输出应该是这样的

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