Issue
I'm just learning some low level analysis of the programs. In 32 bit compilation with gcc, I found that the stack frame is created in the following order:
- Push the function arguments in reverse order.
- Save the return address
- Save the frame pointer
- Create the local variables
So the address of the arguments should be highest, as stack grows in reverse order. But when I tried the same with a 64 bit compilation, I cant understand how it's created, it's just the opposite of what I found in 32 bit compilation. Here is the code and memory details:
void test(int a, int b, int c, int d)
{
int flag;
char buf[10];
num = 100;
}
int main()
{
test(1, 2, 3, 4);
}
Right now for simplicity let's take only the arguments and return address.
32-bit compilation:
0xffffd130: 0x00000001 0xffffd1f4 0xffffd1fc 0xf7e3ad1d
0xffffd140: 0xffffd158 0x0804842d 0x00000001 0x00000002
0xffffd150: 0x00000003 0x00000004 0x00000000 0xf7e22933
0xffffd160: 0x00000001 0xffffd1f4 0xffffd1fc 0xf7fdb6b0
0x08048421 <+30>: mov DWORD PTR [esp],0x1
0x08048428 <+37>: call 0x80483f0 <test>
0x0804842d <+42>: leave
Here everything is proper. I can see the arguments at higher address, immediately followed by the return address 0x0804842d
which is at lower address. Now,
64-bit compilation:
0x7fffffffdf80: 0x00000004 0x00000003 0x00000002 0x00000001
0x7fffffffdf90: 0x00400530 0x00000000 0x00400400 0x00000000
0x7fffffffdfa0: 0xffffdfb0 0x00007fff 0x0040052a 0x00000000
0x7fffffffdfb0: 0x00000000 0x00000000 0xf7a3baf5 0x00007fff
0x0000000000400525 <+24>: call 0x4004f0 <test>
0x000000000040052a <+29>: pop rbp
0x000000000040052b <+30>: ret
Here I can see that the arguments are at a lower address and the return address 0x0040052a
is at a higher address. What is the problem here? is the stack growing in an opposite direction (lower to higher address) or the creation of stack frame is different from the above mentioned sequence? Please help me to understand. Thanks.
Solution
On x86-64 the standard way of passing arguments is through the use of registers, not the stack (unless you got more than 6). See http://www.x86-64.org/documentation/abi.pdf
I highly recommend not doing any kind of experimentation without reading proper documents first (like the one I just linked).
Anyway, you could easily see the arguments were not passed on the stack if you disassembled main:
0x0000000000400509 <+0>: push %rbp
0x000000000040050a <+1>: mov %rsp,%rbp
0x000000000040050d <+4>: mov $0x4,%ecx
0x0000000000400512 <+9>: mov $0x3,%edx
0x0000000000400517 <+14>: mov $0x2,%esi
0x000000000040051c <+19>: mov $0x1,%edi
0x0000000000400521 <+24>: callq 0x4004f0 <test>
0x0000000000400526 <+29>: pop %rbp
0x0000000000400527 <+30>: retq
And you could also see how they end up on the stack within test:
0x00000000004004f0 <+0>: push %rbp
0x00000000004004f1 <+1>: mov %rsp,%rbp
0x00000000004004f4 <+4>: mov %edi,-0x14(%rbp)
0x00000000004004f7 <+7>: mov %esi,-0x18(%rbp)
0x00000000004004fa <+10>: mov %edx,-0x1c(%rbp)
0x00000000004004fd <+13>: mov %ecx,-0x20(%rbp)
0x0000000000400500 <+16>: movl $0x64,-0x4(%rbp)
0x0000000000400507 <+23>: pop %rbp
0x0000000000400508 <+24>: retq
Answered By - employee of the month