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.
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.
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.
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.
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.
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.
This is why you need to be clearer with your environment.
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.
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.
There is a gcc builtin for this: void * __builtin_return_address (unsigned int level)
See 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.
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.
$ 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
$
When you declare local variables, they are also on the stack - x, for instance.
If you then declare an int * xptr
and initialize it to &x
, it will point at 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.
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