What a return address is and why you'd look for it
A return address is the memory location your program will jump to after a function finishes running. When your code calls a function, the processor stores this address so it knows where to resume execution when that function returns. In debugging, you often need to see this address to understand the call stack, trace where a crash happened, or verify that function calls are happening in the order you expect.
GDB (the GNU Debugger) stores return addresses in the stack frame of each function call. Finding them is straightforward once you know where to look and what commands reveal them.
Key Takeaways
- The info frame command shows the return address for the current function in the "saved pc" field.
- The backtrace command displays the call stack with return addresses for all active functions, making it easy to see the path your program took.
- You can examine the raw stack memory with x/a $sp to see return addresses stored on the stack itself.
- The disassemble command with a return address lets you see the exact machine instructions at that location.
Using info frame to see the current return address
When your program is paused in a debugger, the info frame command prints detailed information about the current stack frame. The line labeled "saved pc" shows the return address — the exact memory location where execution will resume when the current function returns.
Type this at the GDB prompt:
(gdb) info frame
The output will look something like this:
Stack level 0, frame at 0x7fffffffde90: rip = 0x555555554680 in main (program.c:15) saved pc = 0x7ffff7a05082 called by frame at 0x7fffffffdf00 source language c. Arglist at 0x7fffffffde80, args: argc=1, argv=0x7fffffffdf88 Locals at 0x7fffffffde80, Frame size = 32 bytes
The "saved pc" value is your return address. This is where the program will jump once the current function ends. If you're inside a nested function call, this address points back into the calling function.
Reading the call stack with backtrace
The backtrace command (or bt for short) shows every function call that led to your current position. Each line includes the return address for that frame, letting you see the entire path your program took.
Type this at the GDB prompt:
(gdb) backtrace
A typical output looks like:
#0 0x0000555555554680 in function_c () at program.c:25 #1 0x0000555555554710 in function_b () at program.c:18 #2 0x0000555555554750 in function_a () at program.c:12 #3 0x0000555555554790 in main () at program.c:5 #4 0x00007ffff7a05082 in __libc_start_main () from /lib64/libc.so.6 #5 0x00005555555545cd in _start () at program.c:1
Each address at the start of a line is the return address for that frame. Frame #0 is where you are now. Frame #1 is the function that called the current one, and so on up the chain. The address in frame #1 (0x0000555555554710) is the return address you'd see if you ran info frame while inside function_b.
Examining stack memory directly
Return addresses live on the stack, and you can read them directly using GDB's memory examination commands. The stack pointer register ($sp on x86-64, or $esp on 32-bit systems) points to the top of the stack where the return address is typically stored.
To see the return address as raw memory:
(gdb) x/a $sp
The x command means "examine memory". The /a format means "print as an address". This will show you the return address stored at the current stack pointer location. If you want to see several values on the stack, use:
(gdb) x/10a $sp
This prints 10 address-sized values starting from the stack pointer. You'll see return addresses and other data stored on the stack. The exact layout depends on your CPU architecture and calling convention, but the first value is usually the return address for the current function.
Matching return addresses to source code
Once you have a return address, you can find out what source code line it corresponds to. Use the info line command with the address:
(gdb) info line *0x555555554710
GDB will tell you the file and line number. You can also use disassemble to see the machine instructions at that address:
(gdb) disassemble 0x555555554710
This shows the assembly code around that address, which helps you understand exactly what the processor was doing when it stored that return address. The instruction just before the return address is usually a call instruction that jumped into the function you're currently debugging.
Viewing return addresses in different frames
You don't have to stay in the current frame to see return addresses. Use frame to move to a different level in the call stack, then run info frame again:
(gdb) frame 2 (gdb) info frame
This moves you to frame 2 (the third frame from the bottom of the stack) and shows its return address. You can also use up and down` to move one frame at a time through the call stack. This is useful when you're trying to understand where a crash originated or why a function was called from an unexpected place.
Frequently Asked Questions
Why does the return address sometimes look like it's inside a library?
If you see a return address in libc or another shared library, it means your code called a library function. The return address points back into your code, but GDB is showing you the library's address space because that's where the current instruction pointer is. Use backtrace to see the full chain and find where your code called the library.
Can I set a breakpoint at a return address?
Yes. Use break *0x555555554710 (with the asterisk) to set a breakpoint at a specific address. The program will pause when execution reaches that address. This is useful if you want to catch a function right before it returns.
What's the difference between the return address and the instruction pointer?
The instruction pointer (rip on x86-64) is where the processor is executing right now. The return address is where it will go next when the current function finishes. They're different locations — the return address is stored on the stack, waiting to be used.
How do I find the return address if the program crashed?
After a crash, GDB keeps the program state frozen. Run backtrace to see the call stack at the moment of the crash. Each frame shows a return address. The frame at the top of the list is where the crash happened.