In Chapter 6, to illustrate function calling conventions, we provide a walkthrough of 32-bit cdecl with our Sum program (Program 6.1). The detailed walkthrough in the book uses the GAS (AT&T) syntax on macOS, and the "Instructions in memory" in Figures 6.2 - 6.6 is the disassembly with LLVM in Xcode (e.g., Figure 6.2).
Figure 6.2
What follows are Program 6.1 stack and disassembly examples for more environments.
GAS | NASM |
---|---|
.data num1: .long 2 num2: .long 4 |
section .data num1: dd 2 num2: dd 4 |
Notice that the disassembly is in AT&T syntax by default in GDB on Linux, even when the NASM assembler (with Intel syntax) is used to assemble the code. You can set GDB to use Intel syntax with the set disassembly-flavor intel
command. Though the exact memory addresses may be different at program runtime, the code and disassembly are identical in GAS and NASM on Linux.
Refer to the following disassembly for the addresses used in Figures 6.2 - 6.6 below!
GAS | NASM |
---|---|
Breakpoint 1, 0x08048074 in _main () (gdb) disassemble _main Dump of assembler code for function _main: => 0x08048074 <+0>: mov $0xa,%eax 0x08048079 <+5>: dec %eax 0x0804807a <+6>: mov $0x5,%ebx 0x0804807f <+11>: pushl 0x80490b5 0x08048085 <+17>: pushl 0x80490b1 0x0804808b <+23>: call 0x80480a2 <_sum> 0x08048090 <+28>: add $0x8,%esp 0x08048093 <+31>: add %ebx,%eax 0x08048095 <+33>: dec %eax 0x08048096 <+34>: mov $0x1,%eax 0x0804809b <+39>: mov $0x0,%ebx 0x080480a0 <+44>: int $0x80 End of assembler dump. (gdb) disassemble _sum Dump of assembler code for function _sum: 0x080480a2 <+0>: push %ebp 0x080480a3 <+1>: mov %esp,%ebp 0x080480a5 <+3>: push %ebx 0x080480a6 <+4>: mov 0x8(%ebp),%ebx 0x080480a9 <+7>: mov 0xc(%ebp),%eax 0x080480ac <+10>: add %ebx,%eax 0x080480ae <+12>: pop %ebx 0x080480af <+13>: pop %ebp 0x080480b0 <+14>: ret End of assembler dump. |
Breakpoint 1, 0x08048080 in _main () (gdb) disassemble _main Dump of assembler code for function _main: => 0x08048080 <+0>: mov $0xa,%eax 0x08048085 <+5>: dec %eax 0x08048086 <+6>: mov $0x5,%ebx 0x0804808b <+11>: pushl 0x80490c4 0x08048091 <+17>: pushl 0x80490c0 0x08048097 <+23>: call 0x80480ae <_sum> 0x0804809c <+28>: add $0x8,%esp 0x0804809f <+31>: add %ebx,%eax 0x080480a1 <+33>: dec %eax 0x080480a2 <+34>: mov $0x1,%eax 0x080480a7 <+39>: mov $0x0,%ebx 0x080480ac <+44>: int $0x80 End of assembler dump. (gdb) disassemble _sum Dump of assembler code for function _sum: 0x080480ae <+0>: push %ebp 0x080480af <+1>: mov %esp,%ebp 0x080480b1 <+3>: push %ebx 0x080480b2 <+4>: mov 0x8(%ebp),%ebx 0x080480b5 <+7>: mov 0xc(%ebp),%eax 0x080480b8 <+10>: add %ebx,%eax 0x080480ba <+12>: pop %ebx 0x080480bb <+13>: pop %ebp 0x080480bc <+14>: ret End of assembler dump. |
GAS (eip -> 0x0804807f) |
(gdb) x/8xw $esp 0xffffd1d0: 0x00000001 0xffffd392 0x00000000 0xffffd3aa 0xffffd1e0: 0xffffd3b5 0xffffd3c7 0xffffd3f7 0xffffd40d |
NASM (eip -> 0x0804808b) |
(gdb) x/8xw $esp 0xffffd1c0: 0x00000001 0xffffd38f 0x00000000 0xffffd3a8 0xffffd1d0: 0xffffd3b3 0xffffd3c5 0xffffd3f5 0xffffd40b |
Notice the parameters (0x2
and 0x4
) on the stack.
GAS (eip -> 0x0804808b) |
(gdb) x/8xw $esp 0xffffd1c8: 0x00000002 0x00000004 0x00000001 0xffffd392 0xffffd1d8: 0x00000000 0xffffd3aa 0xffffd3b5 0xffffd3c7 |
NASM (eip -> 0x08048097) |
(gdb) x/8xw $esp 0xffffd1b8: 0x00000002 0x00000004 0x00000001 0xffffd38f 0xffffd1c8: 0x00000000 0xffffd3a8 0xffffd3b3 0xffffd3c5 |
Notice the return address (i.e., the memory address after the CALL) 0x08048090
is automatically pushed on the stack.
GAS (eip -> 0x080480a2) |
(gdb) x/8xw $esp 0xffffd1c4: 0x08048090 0x00000002 0x00000004 0x00000001 0xffffd1d4: 0xffffd392 0x00000000 0xffffd3aa 0xffffd3b5 |
NASM (eip -> 0x080480ae) |
(gdb) x/8xw $esp 0xffffd1b4: 0x0804809c 0x00000002 0x00000004 0x00000001 0xffffd1c4: 0xffffd38f 0x00000000 0xffffd3a8 0xffffd3b3 |
Notice two more values were pushed on the stack: ebp (main's frame pointer 0x0
) and ebx (0x5
).
GAS (eip -> 0x080480a5) |
(gdb) x/8xw $esp 0xffffd1bc: 0x00000005 0x00000000 0x08048090 0x00000002 0xffffd1cc: 0x00000004 0x00000001 0xffffd392 0x00000000 |
NASM (eip -> 0x080480b1) |
(gdb) x/8xw $esp 0xffffd1ac: 0x00000005 0x00000000 0x0804809c 0x00000002 0xffffd1bc: 0x00000004 0x00000001 0xffffd38f 0x00000000 |
Notice that execution jumped to the addresses implicitly pushed on the stack with the CALL instruction (Figure 6.4). Now, what remains is the need to clean up stack (the parameters that were pushed: 0x2
and 0x4
).
GAS (eip -> 0x08048090) |
(gdb) x/8xw $esp 0xffffd1c8: 0x00000002 0x00000004 0x00000001 0xffffd392 0xffffd1d8: 0x00000000 0xffffd3aa 0xffffd3b5 0xffffd3c7 |
NASM (eip -> 0x0804809c) |
(gdb) x/8xw $esp 0xffffd1b8: 0x00000002 0x00000004 0x00000001 0xffffd38f 0xffffd1c8: 0x00000000 0xffffd3a8 0xffffd3b3 0xffffd3c5 |
MASM |
---|
.386 .MODEL FLAT, C ExitProcess PROTO stdcall, dwExitCode:DWORD |
To view this disassembly in Visual Studio, set a breakpoint at the first instruction, mov eax, 10
, then click on the the "Disassembly" window tab. Under "Viewing Options," we selected "Show address" and "Show symbol names" for this example. Also, remember the addresses will be different each time you run the program.
Refer to the following disassembly for the addresses used in Figures 6.2 - 6.6 below!
MASM |
---|
000F101C mov eax,0Ah 000F1021 dec eax 000F1022 mov ebx,5 000F1027 push dword ptr [num2 (0F4004h)] 000F102D push dword ptr [num1 (0F4000h)] 000F1033 call _sum (0F1045h) 000F1038 add esp,8 000F103B add eax,ebx 000F103D dec eax 000F103E push 0 000F1040 call _ExitProcess@4 (0F1062h) __sum: 000F1045 push ebp 000F1046 mov ebp,esp 000F1048 push ebx 000F1049 mov ebx,dword ptr [ebp+8] 000F104C mov eax,dword ptr [ebp+0Ch] 000F104F add eax,ebx 000F1051 pop ebx 000F1052 pop ebp 000F1053 ret |
In the following figures, the "Address:" boxed in red is the address stored in esp. We have also boxed new values pushed on the stack as the instructions execute.
Notice the parameters (0x2
and 0x4
) on the stack.
Notice the return address (i.e., the memory address after the CALL) 0x000f1038
is automatically pushed on the stack.
Notice two more values were pushed on the stack: ebp (main's frame pointer 0x001ffa94
) and ebx (0x5
).
Notice that execution jumped to the addresses implicitly pushed on the stack with the CALL instruction (Figure 6.4). Now, what remains is the need to clean up stack (the parameters that were pushed: 0x2
and 0x4
).