2

I've read that the return address of a function call is always pushed on the ESP stack. But the thing when i tried to see that using gdb, I didn't find it. Here's the program written in C I've used :

#include <stdio.h>
#include <stdlib.h>

void test_function(int a, int b, int c, int d) {
int flag;
char buffer[10];
flag = 31337;
buffer[0] = 'A';
}

int main() {
  test_function(1, 2, 3, 4);
}

gdb disassemble of this code gives me:

(gdb) disas main
Dump of assembler code for function main:
   0x0804846c <+0>: push   ebp
   0x0804846d <+1>: mov    ebp,esp
   0x0804846f <+3>: and    esp,0xfffffff0
   0x08048472 <+6>: sub    esp,0x10
   0x08048475 <+9>: mov    DWORD PTR [esp+0xc],0x4
   0x0804847d <+17>:    mov    DWORD PTR [esp+0x8],0x3
   0x08048485 <+25>:    mov    DWORD PTR [esp+0x4],0x2
   0x0804848d <+33>:    mov    DWORD PTR [esp],0x1
=> 0x08048494 <+40>:    call   0x804843d <test_function>
   0x08048499 <+45>:    leave  
   0x0804849a <+46>:    ret    
End of assembler dump.
(gdb) x/8xw $esp
0xffffd650: 0x00000001  0x00000002  0x00000003  0x00000004
0xffffd660: 0x080484a0  0x00000000  0x00000000  0xf7e21a83

As you can see , I've put a breakpoint at 0x08048494 (before the function call). I can see the arguments being pushed on the stack (1,2,3,4) but , I don't see the return address , which in this case should be 0x08048499, right ?

2
  • Would you give your output of disassemble test_function. Commented Jun 26, 2015 at 6:18
  • 4
    The call instruction does two things: it jumps to the target address and it pushes the return address on the stack. After reaching your breakpoint, check the stack like you did in your example, then execute stepi in gdb, and check the stack again; esp will have decreased by 4, and 0x08048499 will be at the top of the stack. Commented Jun 26, 2015 at 6:34

2 Answers 2

4

Lets take this small example code:

#include <stdio.h>
#include <stdlib.h>

int foo (int a)
{
  return a ? a << 2: 1000;
}

int main()
{
  printf("The result of foo(10) is %d\n", foo(10));

  return EXIT_SUCCESS;
}

Once in assembly we get:

0000000000400506 <foo>:
  400506:       55                      push   %rbp
  400507:       48 89 e5                mov    %rsp,%rbp
  40050a:       89 7d fc                mov    %edi,-0x4(%rbp)
  40050d:       83 7d fc 00             cmpl   $0x0,-0x4(%rbp)
  400511:       74 08                   je     40051b <foo+0x15>
  400513:       8b 45 fc                mov    -0x4(%rbp),%eax
  400516:       c1 e0 02                shl    $0x2,%eax
  400519:       eb 05                   jmp    400520 <foo+0x1a>
  40051b:       b8 e8 03 00 00          mov    $0x3e8,%eax
  400520:       5d                      pop    %rbp
  400521:       c3                      retq   

0000000000400522 <main>:
  400522:       55                      push   %rbp
  400523:       48 89 e5                mov    %rsp,%rbp
  400526:       bf 0a 00 00 00          mov    $0xa,%edi
  40052b:       e8 d6 ff ff ff          callq  400506 <foo>
  400530:       89 c6                   mov    %eax,%esi
  400532:       bf d4 05 40 00          mov    $0x4005d4,%edi
  400537:       b8 00 00 00 00          mov    $0x0,%eax
  40053c:       e8 9f fe ff ff          callq  4003e0 <printf@plt>
  400541:       b8 00 00 00 00          mov    $0x0,%eax
  400546:       5d                      pop    %rbp
  400547:       c3                      retq   
  400548:       0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
  40054f:       00 

Lets take a look at the following gdb session:

(gdb) break *0x40052b
Breakpoint 1 at 0x40052b: file frame.c, line 13.
(gdb) break *0x400530
Breakpoint 2 at 0x400530: file frame.c, line 13.

We just set a breakpoint before and after the foo procedure call.

(gdb) break foo
Breakpoint 3 at 0x40050d: file frame.c, line 7.

We set a breakpoint in the foo procedure.

(gdb) run
Starting program: /home/fleury/tmp/tests/frame 

Breakpoint 1, 0x000000000040052b in main () at frame.c:13
13    printf("The result of foo(10) is %d\n", foo(10));

We start the program and we hit the first breakpoint before the call to foo.

(gdb) info frame
Stack level 0, frame at 0x7fffffffe0c0:
 rip = 0x40052b in main (frame.c:13); saved rip = 0x7ffff7a54b45
 source language c.
 Arglist at 0x7fffffffe0b0, args: 
 Locals at 0x7fffffffe0b0, Previous frame's sp is 0x7fffffffe0c0
 Saved registers:
  rbp at 0x7fffffffe0b0, rip at 0x7fffffffe0b8

We asked information about the stack frame environment. We can notice that save rip = 0x7ffff7a54b45 (which is the return address of the main procedure).

(gdb) continue
Continuing.

Breakpoint 3, foo (a=10) at frame.c:7
7     return a ? a << 2: 1000;

We continue the execution of the program and got stopped inside the foo procedure (third breakpoint). Lets ask about the stack frame:

 (gdb) info frame
 Stack level 0, frame at 0x7fffffffe0b0:
  rip = 0x40050d in foo (frame.c:7); saved rip = 0x400530
 called by frame at 0x7fffffffe0c0
 source language c.
 Arglist at 0x7fffffffe0a0, args: a=10
 Locals at 0x7fffffffe0a0, Previous frame's sp is 0x7fffffffe0b0
 Saved registers:
  rbp at 0x7fffffffe0a0, rip at 0x7fffffffe0a8

Note that saved rip = 0x400530 which is exactly the position of the next assembly instruction after the call foo.

(gdb) continue
Continuing.

Breakpoint 2, 0x0000000000400530 in main () at frame.c:13
13    printf("The result of foo(10) is %d\n", foo(10));

We keep going in the execution and we reach the second breakpoint at the exit of the foo procedure. Again, lets ask for the return address:

(gdb) info frame
Stack level 0, frame at 0x7fffffffe0c0:
 rip = 0x400530 in main (frame.c:13); saved rip = 0x7ffff7a54b45
 source language c.
 Arglist at 0x7fffffffe0b0, args: 
 Locals at 0x7fffffffe0b0, Previous frame's sp is 0x7fffffffe0c0
 Saved registers:
  rbp at 0x7fffffffe0b0, rip at 0x7fffffffe0b8

It has been restored to the original value when we popped out of foo.

In fact, the info frame (shortened into i f) also tell where to find the stored return address on the stack:

 Saved registers:
  rbp at 0x7fffffffe0b0, rip at 0x7fffffffe0b8

And, if you ask gdb to display the content of 0x7fffffffe0b8 you should see 0x7ffff7a54b45:

(gdb) print /x *0x7fffffffe0b8
$1 = 0x7ffff7a54b45
4
  • Would you explain why in some case the arguments are added first into the stack and then call happens to the function (as in the disassembled code in the question) and in some case, the variables are temporarily copied into the registers and later in the function, those arguments are pushed to stack (as in the disassembled code in your answer). Also, writing the same c program (code as given in question) in my pc, arguments were added to stack later in the function. At first they were just copied into a temporary register. Or should I create a new question, if it would be oftopic over here Commented Jun 26, 2015 at 14:14
  • 1
    This is something totally different, it is because the 'calling convention' are specified by the ABI (Application Binary Interface) you are using. And, they are different if you are in i386 and in amd64. In fact, in i386, all the arguments are passed through the stack. In amd64, the 6 first arguments are passed through the registers, and, starting from the 7th argument, the others are pushed onto the stack. In fact, this would deserve a full question by itself. Do not hesitate to ask for more in a more detailed question on this site.
    – perror
    Commented Jun 26, 2015 at 15:24
  • Thanks, I get it now. BTW, if I want to have a look at ABI code, is it possible or it is stored as executable in my pc, and I will have to search on net. If I need to search on net, please provide a link if you know one. Commented Jun 26, 2015 at 15:29
  • Look at the Wikipedia page about ''calling conventions'' in x86 and, you can also find it here for SystemV-i386 ABI and SystemV-amd64 ABI. But, these comments are too short to make it really clear. Do not fear to ask the question. :)
    – perror
    Commented Jun 26, 2015 at 15:34
3

The comment of Guntram Blohm explains your situation. Since you put the breakpoint at call, the instruction is not executed yet, and you do not see the returned address is pushed in the stack yet. But if you let it execute (for example by typing si), then you will see the returned address is pushed into the stack.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.