20

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.

    #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?

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

Cole Tobin
  • 9,206
  • 15
  • 49
  • 74
Vinit Dhatrak
  • 6,814
  • 8
  • 27
  • 30
  • "How can I modify it?" - consider using setjmp/longjmp. – Steve Jessop Nov 07 '09 at 14:05
  • I guess void * __builtin_return_address (unsigned int level) will not solve my problem. It will return me a return address, not the location of return address. Let me know if I should re phrase the above problem statement. – Vinit Dhatrak Nov 07 '09 at 14:35
  • There is no simple, reliable way to do what you want, even given that you've restricted to gcc on x86. I can only recommend asking another question stating your actual problem. If nothing else, modifying the link pointer is not guaranteed to result in successfully returning to the address you write in there. Call site A saved some registers before calling foo. Call site B might have saved different registers, and whatever it does to restore them will fail, because the stack is in the state A left it, not the state B expects. – Steve Jessop Nov 09 '09 at 01:30

6 Answers6

29

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
$ 
DigitalRoss
  • 143,651
  • 25
  • 248
  • 329
  • @DigitalRoss does not seem to be working, can you paste a simple example? – Vinit Dhatrak Nov 07 '09 at 13:37
  • @DigitalRoss so you mean return address is in-between first parameter to function and local variables? – Vinit Dhatrak Nov 07 '09 at 13:45
  • 2
    In general, yes. The parameters get pushed, then the PC, then stack is further decremented for locals and any temporaries that the compiler needs. Since you can take the address of a parameter and of a local, you can track down your PC. Remember that negative subscripts work just fine in C. `f(int p) { printf("%p", (&p)[-1]); }` – DigitalRoss Nov 07 '09 at 13:48
  • oh okk .. I guess this is how __builtin_return_address must be able to determine the return address. Also I guess, this negative subscription will work with first parameter due to C calling convention of pushing parameters from right to left. Correct me if I am wrong here. – Vinit Dhatrak Nov 07 '09 at 13:56
  • 8
    As DigitalRoss says, though, this is all very architecture-dependent. On ARM (which I mention since the question is tagged "linux gcc", not "linux gcc x86"), the link pointer is in r14, not (necessarily) on the stack at all. You return with `MOV r15,r14` or `BX R14`. So on gcc you should always use the builtin rather than trying to guess the calling convention. – Steve Jessop Nov 07 '09 at 14:02
  • @onebyone Nice input. didnt think of other arch. Thank you. – Vinit Dhatrak Nov 07 '09 at 14:05
  • Actually, the way __builtin_return_address determines the location of the PC is: it has access to the code generator, so it can pull it from a register or whatever. It may not be perfectly reliable, in theory, gcc could ship with a broken builtin because it isn't needed to compile conforming programs or any of gcc's extensions to the C language. I think I recall seeing it broken on gcc-alpha once. – DigitalRoss Nov 07 '09 at 14:08
  • @DigitalRoss @onebyone @ALL ... hey I just realized, this will not solve my problem. I wanted a address on stack which contains return address so that I can modify it in future. I dont want the actually address. :) – Vinit Dhatrak Nov 07 '09 at 14:33
4

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.

Carl Smotricz
  • 66,391
  • 18
  • 125
  • 167
  • Thank you Carl, it will be very nice if I can get the exact offset from x (positive/negative). – Vinit Dhatrak Nov 07 '09 at 13:30
  • That will depend on your compiler. You should run and look at your program in a debugger, that will show you all the necessary details. If you don't have a debugger, print out a few words around x and experiment a bit! This will be a learning experience. – Carl Smotricz Nov 07 '09 at 13:33
4

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.

JustJeff
  • 12,640
  • 5
  • 49
  • 63
1

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.

Rob Walker
  • 46,588
  • 15
  • 99
  • 136
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.

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.

unwind
  • 391,730
  • 64
  • 469
  • 606
-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
enthusiasticgeek
  • 2,640
  • 46
  • 53