Question

I have the following program:

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

I compiled it with gcc -g option.

I am setting 2 breakpoints one just before the test_function call inside main and one right after.

(gdb) list
1   void test_function(int a,int b, int c, int d){
2   int flag;
3   char buffer[10];
4   
5   flag = 31337;
6   buffer[0]='A';
7   }
8   
9   int main(){
10  test_function(1,2,3,4);
(gdb) break 10
Breakpoint 1 at 0x804843c: file stackexample.c, line 10.
(gdb) break test_function
Breakpoint 2 at 0x804840a: file stackexample.c, line 1.
(gdb) run
Starting program: /root/tests/c-tests/./stackexample 


Breakpoint 1, main () at stackexample.c:10
10  test_function(1,2,3,4);
(gdb) i r esp ebp eip
esp            0xbffff4d0   0xbffff4d0
ebp            0xbffff4e8   0xbffff4e8
eip            0x804843c    0x804843c <main+9>

According to my knowledge, 0xbffff4d0 this address is the current bottom of the stack (top-highest address) and this will be used for the creation (reference) of the new stack frame after the call of test_function.

(gdb) x/5i $eip
=> 0x804843c <main+9>:  mov    DWORD PTR [esp+0xc],0x4
   0x8048444 <main+17>: mov    DWORD PTR [esp+0x8],0x3
   0x804844c <main+25>: mov    DWORD PTR [esp+0x4],0x2
   0x8048454 <main+33>: mov    DWORD PTR [esp],0x1
   0x804845b <main+40>: call   0x8048404 <test_function>

Before the test_function call the arguments are stored with these mov instructions.

(gdb) info frame
Stack level 0, frame at 0xbffff4f0:
eip = 0x804843c in main (stackexample.c:10); saved eip 0xb7e8bbd6
source language c.
Arglist at 0xbffff4e8, args: 
Locals at 0xbffff4e8, Previous frame's sp is 0xbffff4f0
Saved registers:
ebp at 0xbffff4e8, eip at 0xbffff4ec
(gdb) cont
Continuing.

Breakpoint 2, test_function (a=1, b=2, c=3, d=4) at stackexample.c:1
1   void test_function(int a,int b, int c, int d){
(gdb) info frame
Stack level 0, frame at 0xbffff4d0:
eip = 0x804840a in test_function (stackexample.c:1); saved eip 0x8048460
called by frame at 0xbffff4f0
source language c.
Arglist at 0xbffff4c8, args: a=1, b=2, c=3, d=4
Locals at 0xbffff4c8, Previous frame's sp is 0xbffff4d0
Saved registers:
ebp at 0xbffff4c8, eip at 0xbffff4cc
(gdb) i r esp ebp eip
esp            0xbffff4a0   0xbffff4a0
ebp            0xbffff4c8   0xbffff4c8
eip            0x804840a    0x804840a <test_function+6>

So here its obvious that first frame's esp became the current starting address of this frame. Although what I dont get is in which stack frame the arguments are ??? because...

(gdb) info locals
flag = 134513420
buffer = "\377\267\364\237\004\b\350\364\377\277"

Here we cannot see the args. If we ..

(gdb) info args
a = 1
b = 2
c = 3
d = 4
(gdb) print &a
$4 = (int *) 0xbffff4d0
(gdb) print &b
$5 = (int *) 0xbffff4d4
(gdb) print &c
$6 = (int *) 0xbffff4d8
(gdb) print &d
$7 = (int *) 0xbffff4dc

So here we see that the arguments are starting from the first address that this current stack frame has which is 0xbffff4d0

And the other question is the following according to this output

(gdb) x/16xw $esp
0xbffff4a0: 0xb7fc9ff4  0x08049ff4  0xbffff4b8  0x0804830c
0xbffff4b0: 0xb7ff1080  0x08049ff4  0xbffff4e8  0x08048499
0xbffff4c0: 0xb7fca324  0xb7fc9ff4  0xbffff4e8  0x08048460
0xbffff4d0: 0x00000001  0x00000002  0x00000003  0x00000004

Address 0x08048460 is the eip = 0x804840a in test_function (stackexample.c:1); saved eip 0x8048460 and also `#1 0x08048460 in main () at stackexample.c:10 (output from backtrace)

How come and RET to main is on top (into a lower address) than the arguments ? Shouldnt ret address be in the start of the new stack frame? Sorry but I am trying to understand how stack works and I am kinda confused :S Another thing that I dont undestand is that the reference for the local variables is happening through $esp+(offset). Does the value of esp is always depending on the "current" stack frame that the execution is?

Was it helpful?

Solution

Your disassembled program looks like this on my system:

gcc -m32 -c -o stackexample.o stackexample.c
objdump -d -M intel stackexample.o


test_function:
    push   ebp
    mov    ebp,esp
    sub    esp,0x10
    mov    DWORD PTR [ebp-0x4],0x7a69
    mov    BYTE PTR [ebp-0xe],0x41
    leave
    ret

main:
    push   ebp
    mov    ebp,esp
    sub    esp,0x10
    mov    DWORD PTR [esp+0xc],0x4
    mov    DWORD PTR [esp+0x8],0x3
    mov    DWORD PTR [esp+0x4],0x2
    mov    DWORD PTR [esp],0x1
    call   test_function
    leave
    ret

Let's start from the beginning.

Stack is arranged from top to bottom in memory. The top of the stack has the lowest address.

esp is the Stack Pointer. It always points to the top of the stack.

ebp is the Base Pointer. It points to the bottom of current stack frame. It is used for referencing current function's arguments and local variables.

These instructions

push   ebp
mov    ebp,esp

can be found at the top of every function. They do the following:

  • save caller's Base Pointer
  • setup current function's Base Pointer by assigning it to Stack Pointer. At this point Stack Pointer points to bottom of current stack frame, so by assigning Base Pointer to it, Base Pointer will show current bottom. Stack Pointer can increase/decrease during function's execution, so you use Base Pointer to reference the variables. Base Pointer also servers for saving/storing caller's Stack Pointer.

leave is equivalent to

mov    esp, ebp
pop    ebp

which is the exact opposite of the instructions above:

  • restore caller's Stack Pointer
  • restore caller's Base Pointer

Now to answer your questions

in which stack frame the arguments are ???

Arguments are stored in caller's stack frame. However you can use Base Pointer to access them. info locals does not display the information about the function arguments as part of gdb's specification:

http://visualgdb.com/gdbreference/commands/info_locals

How come and RET to main is on top (into a lower address) than the arguments ? Shouldnt ret address be in the start of the new stack frame

That's because arguments are stored in caller's frame. When test_function is called, the stack already has the arguments stored, so the returned address is stored higher (aka lower address) than the arguments.

reference for the local variables is happening through $esp+(offset).

As far as I know, referencing local variables can happen both using the Base Pointer and the Stack Pointer - whichever is convenient for your compiler (not really sure).

Does the value of esp is always depending on the "current" stack frame that the execution is?

Yes. Stack Pointer is THE most important stack register. It points to the top of the stack.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top