Felix We Felix We - 1 month ago 5
C Question

RSP points not to the top of the stack?

I have a problem understanding how the stack works. First my little code:

void func1 ( int z ) {
int i = 1;
}

int main ( ) {
func1 ( 89 );
return 0;
}


I am using:
Ubuntu 16.04 64-bit,
gcc version 5.4.0,
gdb version 7.11.1.

I was debugging with GDB, to see how the compiler pushes function arguments on the stack.

When I examine the stack at the point of the where RSP points, I get this:

(gdb) x/10xw $rsp
0x7fffffffdf20: 0xffffdf30 0x00007fff 0x00400525 0x00000000
0x7fffffffdf30: 0x00400530 0x00000000 0xf7a2e830 0x00007fff
0x7fffffffdf40: 0x00000000 0x00000000


When I print out the address of newest created variable, I get this:

(gdb) p &i
$4 = (int *) 0x7fffffffdf14


When I print out the address of the variable, which was hand over to the function, I get this:

(gdb) p &z
$5 = (int *) 0x7fffffffdf0c


The stack is growing to lower numbers.

So I thought that
RSP
always points to the top of the stack, meaning that when I i call this command
x/10xw $rsp
I am able to see all the variables from the function, but I can't see them from there.

The first address after this command is way higher than the address of the variable
z
. Because of that I was guessing that
RSP
points not on the top of the stack.

What is also wondering me, is that the address of
i
is higher than the address of
z
.
Since
i
were later pushed to the stack than
z
,
i
must be a lower address than z in my opinion.

I hope someone can explain me why this is so.

EDIT: I have found the answer!
It was an optimization from the compiler. In
func1()
the
RSP
register had not pointed to the "top" of the stack because it was not necessary. It were just necessary if in
func1()
a other function were called. So the compiler saw that and didn't decrement the
RSP
register.

Here ismy assembler code with no function call in
func1()
:

0x00000000004004d6 <+0>: push rbp
0x00000000004004d7 <+1>: mov rbp,rsp
0x00000000004004de <+8>: mov DWORD PTR [rbp-0x14],edi
0x00000000004004e1 <+11>: mov DWORD PTR [rbp-0x4],0x1
0x00000000004004e8 <+18>: mov eax,0x0
0x00000000004004f3 <+29>: leave
0x00000000004004f4 <+30>: ret


So you can see no
SUB
call for decrementing
RSP
.

Now the code from
func1()
with a function call:

0x00000000004004d6 <+0>: push rbp
0x00000000004004d7 <+1>: mov rbp,rsp
0x00000000004004da <+4>: sub rsp,0x20
0x00000000004004de <+8>: mov DWORD PTR [rbp-0x14],edi
0x00000000004004e1 <+11>: mov DWORD PTR [rbp-0x4],0x1
0x00000000004004e8 <+18>: mov eax,0x0
0x00000000004004ed <+23>: call 0x4004f5 <func2>
0x00000000004004f2 <+28>: nop
0x00000000004004f3 <+29>: leave
0x00000000004004f4 <+30>: ret


So you can see the
SUB
call for decrementing
RSP
. So
RSP
can point to the "top".

Answer

The convention on x86 is that the stack grows "downwards" towards decreasing addresses.

The "top" of the stack is simply the location where something was most recently pushed; it's not based on the relative values of the addresses. A stack can grow "upwards" or "downwards" in the address space - heck, for some implementations (such as a linked list), the addresses don't even have to be sequential.

This page has a fair explanation with diagrams.