stores reserve
Start by handwriting a simple C program: main.c
#include <stdio.h> void empty(); int add(int i, int j); void myPrint(int num); void testParams(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k); int main() { // Testing empty functions empty(); // Testing a function that passes two parameters int i = 3; int j = 4; int k = add(i, j); // Testing functions that pass multiple parameters testParams(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); // Test the print function (indefinite parameters) myPrint(k); return 0; } void empty() { return; } int add(int i, int j) { int k = i + j; return k; } void myPrint(int num) { printf("%s %s %s: %d\n", "hello", "world", "print", num); } void testParams(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k) { return; }
Then clang main.c is executed to generate a.out file by default.Looking at a.out, you can see that a.out is an x86_Files in Mach-O format under 64 architecture.
➜ Function Call Procedures git:(master) ✗ clang main.c ➜ Function Call Procedures git:(master) ✗ ls a.out main.c ➜ Function Call Procedures git:(master) ✗ file a.out a.out: Mach-O 64-bit executable x86_64 ➜ Function Call Procedures git:(master) ✗
View Mach-O
We use tools to analyze Mach-O files: MachOView
Expansion: uTEXT_Text, we can see that our custom functions in main.c are listed here in the order of assembly instructions.
RBP and RSP
Analyzing a function call is essentially a jump in instructions and a change in the memory area of the stack during the execution of the analyzer
There are two very important pointer registers during function calls: bp and sp
- bp always points to the bottom of the current top stack frame
- sp points to the top of the stack of the current topmost stack frame (in principle, you need to point to the top of the stack, but not always because of code optimization)
Call procedures for empty functions
Next let's look at x86_in debug modeThe calling procedure of the function under 64.The program first enters the main function, then calls the empty function, which is very simple and exits without doing anything.
1 // Call lldb to start debugging a.out ➜ Function Call Procedures git:(master) ✗ lldb a.out (lldb) target create "a.out" Current executable set to 'a.out' (x86_64). 2 // Set breakpoint to main (lldb) breakpoint set --name main Breakpoint 1: where = a.out`main, address = 0x0000000100000e20 3 // run program (lldb) run Process 42767 launched: '/Users/guosai/practice_C/Function Call Procedures/a.out' (x86_64) Process 42767 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 frame #0: 0x0000000100000e20 a.out`main a.out`main: -> 0x100000e20 <+0>: pushq %rbp 0x100000e21 <+1>: movq %rsp, %rbp 0x100000e24 <+4>: subq $0x40, %rsp 0x100000e28 <+8>: movl $0x0, -0x4(%rbp) Target 0: (a.out) stopped. (lldb)
You can see that the program is stuck in the first instruction of the main function, pushq%rbp.Then let's look at the assembly code for the main and empty functions (using the disassemble --name command)
a.out`main: -> 0x100000e20 <+0>: pushq %rbp 0x100000e21 <+1>: movq %rsp, %rbp 0x100000e24 <+4>: subq $0x40, %rsp 0x100000e28 <+8>: movl $0x0, -0x4(%rbp) 0x100000e2f <+15>: callq 0x100000f60 ; empty 0x100000e34 <+20>: movl $0x3, -0x8(%rbp) 0x100000e3b <+27>: movl $0x4, -0xc(%rbp) 0x100000e42 <+34>: movl -0x8(%rbp), %edi 0x100000e45 <+37>: movl -0xc(%rbp), %esi 0x100000e48 <+40>: callq 0x100000eb0 ; add 0x100000e4d <+45>: movl %eax, -0x10(%rbp) 0x100000e50 <+48>: movl $0x1, %edi 0x100000e55 <+53>: movl $0x2, %esi 0x100000e5a <+58>: movl $0x3, %edx 0x100000e5f <+63>: movl $0x4, %ecx 0x100000e64 <+68>: movl $0x5, %r8d 0x100000e6a <+74>: movl $0x6, %r9d 0x100000e70 <+80>: movl $0x7, (%rsp) 0x100000e77 <+87>: movl $0x8, 0x8(%rsp) 0x100000e7f <+95>: movl $0x9, 0x10(%rsp) 0x100000e87 <+103>: movl $0xa, 0x18(%rsp) 0x100000e8f <+111>: movl $0xb, 0x20(%rsp) 0x100000e97 <+119>: callq 0x100000ed0 ; testParams 0x100000e9c <+124>: movl -0x10(%rbp), %edi 0x100000e9f <+127>: callq 0x100000f20 ; myPrint 0x100000ea4 <+132>: xorl %eax, %eax 0x100000ea6 <+134>: addq $0x40, %rsp 0x100000eaa <+138>: popq %rbp 0x100000eab <+139>: retq 0x100000eac <+140>: nopl (%rax)
a.out`empty: 0x100000f60 <+0>: pushq %rbp 0x100000f61 <+1>: movq %rsp, %rbp 0x100000f64 <+4>: popq %rbp 0x100000f65 <+5>: retq
To analyze the calling process of an empty function, it is mainly to analyze several instructions (or combinations of instructions)
Instructions (combinations of instructions) | Effect |
---|---|
callq value | Jump to a function |
Pushq%rbp and movq%rsp,%rbp | Staging bp pointer and opening new stack frame |
POPQ%rbp and retq | End of function, return the previous level function |
callq
callq is the call instruction (q represents the length of bytes to be processed, q is 8 bytes, l is 4 bytes, w is 2 bytes).The call directive indicates that the CPU should jump to an address to execute a new function.
Now let's execute the step directive four times and leave the CPU on the fifth directive: callq 0x100000f60 (to skip the empty function). Before analyzing this directive, let's look at the status of registers in the CPU (register read command)
a.out`main: 0x100000e20 <+0>: pushq %rbp 0x100000e21 <+1>: movq %rsp, %rbp 0x100000e24 <+4>: subq $0x40, %rsp 0x100000e28 <+8>: movl $0x0, -0x4(%rbp) -> 0x100000e2f <+15>: callq 0x100000f60 ; empty 0x100000e34 <+20>: movl $0x3, -0x8(%rbp) 0x100000e3b <+27>: movl $0x4, -0xc(%rbp) ............ General Purpose Registers: rax = 0x0000000100000e20 a.out`main rbx = 0x0000000000000000 rcx = 0x00007ffeefbff5a8 rdx = 0x00007ffeefbff3a8 rdi = 0x0000000000000001 rsi = 0x00007ffeefbff398 rbp = 0x00007ffeefbff370 rsp = 0x00007ffeefbff330 r8 = 0x0000000000000000 r9 = 0x0000000000000000 r10 = 0x0000000000000000 r11 = 0x0000000000000000 r12 = 0x0000000000000000 r13 = 0x0000000000000000 r14 = 0x0000000000000000 r15 = 0x0000000000000000 rip = 0x0000000100000e2f a.out`main + 15 rflags = 0x0000000000000206 cs = 0x000000000000002b fs = 0x0000000000000000 gs = 0x0000000000000000
You can see at this point:
- rbp = 0x00007ffeefbff370, address of bottom of main stack frame is 0x00007ffeefbff370
- rsp = 0x00007ffeefbff330, address of top of main stack frame is 0x00007ffeefbff330 (sp is less than dp because stack is growing down)
From this, we can figure out that the stack frame size of main is 0x40, which fits the third instruction: subq $0x40,%rsp
- Rip points to the address of the instruction currently waiting to be executed, where rip = 0x0000100000e2f, which is exactly (0x100000e2f <+15>: callq 0x100000f60)
So what does the callq 0x100000f60 directive do?Let's first draw a conclusion:
- The call directive first stacks the next directive push of the current directive (save the address of the directive for now, and wait until the called function returns to find it, and continue executing)
- Find the address of the next instruction that needs to be executed (that is, empty) and skip execution.
First, let's verify point 2. The current jump instruction is callq 0x100000f60, and the address of empty's first instruction is also 0x100000f60 (0x100000f60 <+0>: pushq%rbp).Here 0x100000f60 is the value lldb calculated for us, the algorithm is: destination value = offset value + address of next instruction, this logic is position independent code
Then let's verify the first point by looking at the values stored near the top of the stack (rsp = 0x00007ffeefbff330) in current memory (memory read directive)
memory read --size 4 --format x --count 16 0x00007ffeefbff310 0x7ffeefbff310: 0x00000000 0x00000000 0xefbff398 0x00007ffe 0x7ffeefbff320: 0xefbff380 0x00007ffe (0x00000000 0x00000001) 0x7ffeefbff330: 0x00000001 0x0000000e 0x00000000 0x00000000 0x7ffeefbff340: 0x00000000 0x00000000 0x00000000 0x00000000 (lldb)
The above (0x00000000 0x00000001) indicates that the existing value of 0x7ffeefbff328 address storage is 0000000100000 (small-end mode), which will be overwritten to return address 0x100000e34.
Then execute the step.Watch again for changes in registers and memory values:
memory read --size 4 --format x --count 16 0x00007ffeefbff310 0x7ffeefbff310: 0x00000000 0x00000000 0xefbff398 0x00007ffe 0x7ffeefbff320: 0xefbff380 0x00007ffe (0x00000e34 0x00000001) 0x7ffeefbff330: 0x00000001 0x0000000e 0x00000000 0x00000000 0x7ffeefbff340: 0x00000000 0x00000000 0x00000000 0x00000000 (lldb) register read General Purpose Registers: rax = 0x0000000100000e20 a.out`main rbx = 0x0000000000000000 rcx = 0x00007ffeefbff5a8 rdx = 0x00007ffeefbff3a8 rdi = 0x0000000000000001 rsi = 0x00007ffeefbff398 rbp = 0x00007ffeefbff370 rsp = 0x00007ffeefbff328 r8 = 0x0000000000000000 r9 = 0x0000000000000000 r10 = 0x0000000000000000 r11 = 0x0000000000000000 r12 = 0x0000000000000000 r13 = 0x0000000000000000 r14 = 0x0000000000000000 r15 = 0x0000000000000000 rip = 0x0000000100000f60 a.out`empty rflags = 0x0000000000000206 cs = 0x000000000000002b fs = 0x0000000000000000 gs = 0x0000000000000000
You can see three changes
- The value of 0x7ffeefbff328 address memory (0x00000000 0x00001) becomes (0x00000e34 0x00000001, or 0000000100000e34 in small-end mode), which is exactly the address of the next instruction in callq 0x100000f60, 0x100000e34.
- Because a new value is on the stack, the value of rsp also needs to be updated to the new top of the stack, that is, 0x00007ffeefbff330 - 8 = 0x00007ffeefbff328 (pointers on the push stack account for 8 bytes)
- Rip points to the new instruction address: the first instruction of the empty function: rip = 0x0000000100000f60 a.out`empty
After executing the call command, execution logic jumps out of main to execute empty.
pushq %rbp & movq %rsp, %rbp
Both lines of functions need to be executed at the beginning of all functions to temporarily store the rbp pointer of the last function and point updates of the rbp pointer to the new stack frame
- Pushq%rbp: RBP pointer holding a function
- Movq%rsp,%rbp:rbp pointer update pointing to new stack frame
Step 1: Verify pushq%rbp, first look at the current state of registers and memory:
(lldb) memory read --size 4 --format x --count 16 0x00007ffeefbff310 0x7ffeefbff310: 0x00000000 0x00000000 0xefbff398 0x00007ffe 0x7ffeefbff320: (0xefbff380 0x00007ffe) 0x00000e34 0x00000001 0x7ffeefbff330: 0x00000001 0x0000000e 0x00000000 0x00000000 0x7ffeefbff340: 0x00000000 0x00000000 0x00000000 0x00000000 (lldb) register read General Purpose Registers: rax = 0x0000000100000e20 a.out`main rbx = 0x0000000000000000 rcx = 0x00007ffeefbff5a8 rdx = 0x00007ffeefbff3a8 rdi = 0x0000000000000001 rsi = 0x00007ffeefbff398 rbp = 0x00007ffeefbff370 rsp = 0x00007ffeefbff328 r8 = 0x0000000000000000 r9 = 0x0000000000000000 r10 = 0x0000000000000000 r11 = 0x0000000000000000 r12 = 0x0000000000000000 r13 = 0x0000000000000000 r14 = 0x0000000000000000 r15 = 0x0000000000000000 rip = 0x0000000100000f60 a.out`empty rflags = 0x0000000000000206 cs = 0x000000000000002b fs = 0x0000000000000000 gs = 0x0000000000000000
The value in the 0x7ffeefbff320 address (0x00000001 0x0000000e) is overwritten by the current RBP value (rbp = 0x00007ffeefbff370), then rsp decreases by "1" to 0x00007ffeefbff320.ok, let's go through the following step s and see the results:
(lldb) memory read --size 4 --format x --count 16 0x00007ffeefbff310 0x7ffeefbff310: 0x00000000 0x00000000 0xefbff398 0x00007ffe 0x7ffeefbff320: (0xefbff370 0x00007ffe) 0x00000e34 0x00000001 0x7ffeefbff330: 0x00000001 0x0000000e 0x00000000 0x00000000 0x7ffeefbff340: 0x00000000 0x00000000 0x00000000 0x00000000 (lldb) register read General Purpose Registers: rax = 0x0000000100000e20 a.out`main rbx = 0x0000000000000000 rcx = 0x00007ffeefbff5a8 rdx = 0x00007ffeefbff3a8 rdi = 0x0000000000000001 rsi = 0x00007ffeefbff398 rbp = 0x00007ffeefbff370 rsp = 0x00007ffeefbff320 r8 = 0x0000000000000000 r9 = 0x0000000000000000 r10 = 0x0000000000000000 r11 = 0x0000000000000000 r12 = 0x0000000000000000 r13 = 0x0000000000000000 r14 = 0x0000000000000000 r15 = 0x0000000000000000 rip = 0x0000000100000f61 a.out`empty + 1 rflags = 0x0000000000000206 cs = 0x000000000000002b fs = 0x0000000000000000 gs = 0x0000000000000000
As expected, now: rsp = 0x00007ffeefbff320, address 0x7ffeefbff320 stores a value of 0x00007ffeefbff370 (small-end mode).
Step 2: movq%rsp,%rbp, assigns the value of RSP to rbp, so that RBP points to the bottom of the new stack frame, which represents the opening of a new stack frame, that is, the stack frame of the empty function.Execute the step and see the result:
General Purpose Registers: rax = 0x0000000100000e20 a.out`main rbx = 0x0000000000000000 rcx = 0x00007ffeefbff5a8 rdx = 0x00007ffeefbff3a8 rdi = 0x0000000000000001 rsi = 0x00007ffeefbff398 rbp = 0x00007ffeefbff320 // The value of rbp has been updated and is now the same as rsp rsp = 0x00007ffeefbff320 r8 = 0x0000000000000000 r9 = 0x0000000000000000 r10 = 0x0000000000000000 r11 = 0x0000000000000000 r12 = 0x0000000000000000 r13 = 0x0000000000000000 r14 = 0x0000000000000000 r15 = 0x0000000000000000 rip = 0x0000000100000f64 a.out`empty + 4 rflags = 0x0000000000000206 cs = 0x000000000000002b fs = 0x0000000000000000 gs = 0x0000000000000000
This is the logic that the CPU jumps to new functions and new stack frames when an empty function is called.Since there is no implementation in the empty function, it will be returned immediately.
popq %rbp & retq
The assembly instructions for all functions also end with these two instructions, which mean retrieving the temporary rbp pointer and restoring the value of the rbp register, then taking out the address of the temporary return instruction and jumping execution.
- POPQ%rbp: Retrieves the temporary RBP pointer and restores the value of the RBP register.
- Remove the address of the temporary return instruction and jump to execution.
First step verification: POPQ%rbp.Look at the state of the registers and memory before performing this step:
(lldb) register read General Purpose Registers: rax = 0x0000000100000e20 a.out`main rbx = 0x0000000000000000 rcx = 0x00007ffeefbff5a8 rdx = 0x00007ffeefbff3a8 rdi = 0x0000000000000001 rsi = 0x00007ffeefbff398 rbp = 0x00007ffeefbff320 rsp = 0x00007ffeefbff320 r8 = 0x0000000000000000 r9 = 0x0000000000000000 r10 = 0x0000000000000000 r11 = 0x0000000000000000 r12 = 0x0000000000000000 r13 = 0x0000000000000000 r14 = 0x0000000000000000 r15 = 0x0000000000000000 rip = 0x0000000100000f64 a.out`empty + 4 rflags = 0x0000000000000206 cs = 0x000000000000002b fs = 0x0000000000000000 gs = 0x0000000000000000 (lldb) memory read --size 4 --format x --count 16 0x00007ffeefbff310 0x7ffeefbff310: 0x00000000 0x00000000 0xefbff398 0x00007ffe 0x7ffeefbff320: 0xefbff370 0x00007ffe 0x00000e34 0x00000001 0x7ffeefbff330: 0x00000001 0x0000000e 0x00000000 0x00000000 0x7ffeefbff340: 0x00000000 0x00000000 0x00000000 0x00000000
The current stack top address is: rsp = 0x00007ffeefbff320, and the value stored at the stack top address is: 0xefbff370 x00007ffe, which is the base address of the stack frame of the last function we temporarily saved before.Then execute the step to see the result:
(lldb) register read General Purpose Registers: rax = 0x0000000100000e20 a.out`main rbx = 0x0000000000000000 rcx = 0x00007ffeefbff5a8 rdx = 0x00007ffeefbff3a8 rdi = 0x0000000000000001 rsi = 0x00007ffeefbff398 rbp = 0x00007ffeefbff370 rsp = 0x00007ffeefbff328 r8 = 0x0000000000000000 r9 = 0x0000000000000000 r10 = 0x0000000000000000 r11 = 0x0000000000000000 r12 = 0x0000000000000000 r13 = 0x0000000000000000 r14 = 0x0000000000000000 r15 = 0x0000000000000000 rip = 0x0000000100000f65 a.out`empty + 5 rflags = 0x0000000000000206 cs = 0x000000000000002b fs = 0x0000000000000000 gs = 0x0000000000000000
Now rbp = 0x00007ffeefbff370, the rbp register is restored, and rsp adds "1" (because the pop command is executed, the stack length is reduced), at this time:
Step 2: retq, which takes out the current top of the stack value, which is the address of the temporary instruction that needs to be returned (at this point 0x100000e34).First look at the current status:
(lldb) memory read --size 4 --format x --count 16 0x00007ffeefbff310 0x7ffeefbff310: 0x00000000 0x00000000 0xefbff398 0x00007ffe 0x7ffeefbff320: 0xefbff370 0x00007ffe (0x00000e34 0x00000001) 0x7ffeefbff330: 0x00000001 0x0000000e 0x00000000 0x00000000 0x7ffeefbff340: 0x00000000 0x00000000 0x00000000 0x00000000 (lldb) register read General Purpose Registers: rax = 0x0000000100000e20 a.out`main rbx = 0x0000000000000000 rcx = 0x00007ffeefbff5a8 rdx = 0x00007ffeefbff3a8 rdi = 0x0000000000000001 rsi = 0x00007ffeefbff398 rbp = 0x00007ffeefbff370 rsp = 0x00007ffeefbff328 r8 = 0x0000000000000000 r9 = 0x0000000000000000 r10 = 0x0000000000000000 r11 = 0x0000000000000000 r12 = 0x0000000000000000 r13 = 0x0000000000000000 r14 = 0x0000000000000000 r15 = 0x0000000000000000 rip = 0x0000000100000f65 a.out`empty + 5 rflags = 0x0000000000000206 cs = 0x000000000000002b fs = 0x0000000000000000 gs = 0x0000000000000000
rsp = 0x00007ffeefbff328, rsp executes the current stack top, the current stack top stored value is (0x00000e34 0x00000001).Then execute the step to see the result:
(lldb) disassemble a.out`empty: 0x100000f60 <+0>: pushq %rbp 0x100000f61 <+1>: movq %rsp, %rbp 0x100000f64 <+4>: popq %rbp -> 0x100000f65 <+5>: retq (lldb) step Process 45685 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100000e34 a.out`main + 20 a.out`main: -> 0x100000e34 <+20>: movl $0x3, -0x8(%rbp) 0x100000e3b <+27>: movl $0x4, -0xc(%rbp) 0x100000e42 <+34>: movl -0x8(%rbp), %edi 0x100000e45 <+37>: movl -0xc(%rbp), %esi Target 0: (a.out) stopped. (lldb) register read General Purpose Registers: rax = 0x0000000100000e20 a.out`main rbx = 0x0000000000000000 rcx = 0x00007ffeefbff5a8 rdx = 0x00007ffeefbff3a8 rdi = 0x0000000000000001 rsi = 0x00007ffeefbff398 rbp = 0x00007ffeefbff370 rsp = 0x00007ffeefbff330 r8 = 0x0000000000000000 r9 = 0x0000000000000000 r10 = 0x0000000000000000 r11 = 0x0000000000000000 r12 = 0x0000000000000000 r13 = 0x0000000000000000 r14 = 0x0000000000000000 r15 = 0x0000000000000000 rip = 0x0000000100000e34 a.out`main + 20 rflags = 0x0000000000000206 cs = 0x000000000000002b fs = 0x0000000000000000 gs = 0x0000000000000000
As you can see, after retq execution, the CPU points to the instruction address that needs to be returned: 0x100000e34 (rip = 0x0000000100000e34); rsp continues with "1".The stack frame of the function restores the state of the main function before:
So far, the call and return of an empty function is over. Let's summarize the whole process again:
Function Call Procedures
Pass-Through & Return Value
There are two ways to pass a function call:
- Register pass-through when there are enough registers
- When the register is not enough, push the parameter onto the stack and pass the parameter in the stack memory.
Return value:
- The return value is stored in the register eax and returned to the upper level function.
Register Pass-Through
We take the add function as an example:
// Testing a function that passes two parameters int i = 3; int j = 4; int k = add(i, j); int add(int i, int j) { int k = i + j; return k; }
a.out`main: -> 0x100000e20 <+0>: pushq %rbp 0x100000e21 <+1>: movq %rsp, %rbp 0x100000e24 <+4>: subq $0x40, %rsp 0x100000e28 <+8>: movl $0x0, -0x4(%rbp) 0x100000e2f <+15>: callq 0x100000f60 0x100000e34 <+20>: movl $0x3, -0x8(%rbp) 0x100000e3b <+27>: movl $0x4, -0xc(%rbp) 0x100000e42 <+34>: movl -0x8(%rbp), %edi // Parameter: Number 3 put in edi 0x100000e45 <+37>: movl -0xc(%rbp), %esi // Parameter: Number 4 put in esi 0x100000e48 <+40>: callq 0x100000eb0 ; add .......... a.out`add: 0x100000eb0 <+0>: pushq %rbp 0x100000eb1 <+1>: movq %rsp, %rbp 0x100000eb4 <+4>: movl %edi, -0x4(%rbp) // Remove parameter 3 from edi 0x100000eb7 <+7>: movl %esi, -0x8(%rbp) // Remove parameter 4 from esi 0x100000eba <+10>: movl -0x4(%rbp), %esi 0x100000ebd <+13>: addl -0x8(%rbp), %esi // Calculate 3 + 4 0x100000ec0 <+16>: movl %esi, -0xc(%rbp) 0x100000ec3 <+19>: movl -0xc(%rbp), %eax // The result of 3 + 4 is put into eax and returned as a return parameter 0x100000ec6 <+22>: popq %rbp 0x100000ec7 <+23>: retq 0x100000ec8 <+24>: nopl (%rax,%rax)
Stack memory parameters
We take the testParams function as an example:
// Testing functions that pass multiple parameters testParams(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); void testParams(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k) { return; }
a.out`main: ........ 0x100000e50 <+48>: movl $0x1, %edi // The numbers 1, 2, 3, 4, 5, 6 are placed in six registers 0x100000e55 <+53>: movl $0x2, %esi 0x100000e5a <+58>: movl $0x3, %edx 0x100000e5f <+63>: movl $0x4, %ecx 0x100000e64 <+68>: movl $0x5, %r8d 0x100000e6a <+74>: movl $0x6, %r9d 0x100000e70 <+80>: movl $0x7, (%rsp) // Numbers 7, 8, 9, 10, 11 are placed in stack memory (main's stack frame space) 0x100000e77 <+87>: movl $0x8, 0x8(%rsp) 0x100000e7f <+95>: movl $0x9, 0x10(%rsp) 0x100000e87 <+103>: movl $0xa, 0x18(%rsp) 0x100000e8f <+111>: movl $0xb, 0x20(%rsp) 0x100000e97 <+119>: callq 0x100000ed0 ; testParams ........ a.out`testParams: 0x100000ed0 <+0>: pushq %rbp 0x100000ed1 <+1>: movq %rsp, %rbp 0x100000ed4 <+4>: pushq %r14 0x100000ed6 <+6>: pushq %rbx 0x100000ed7 <+7>: movl 0x30(%rbp), %eax // Read parameters from stack memory and place them in the stack frame space of testParams 0x100000eda <+10>: movl 0x28(%rbp), %r10d 0x100000ede <+14>: movl 0x20(%rbp), %r11d 0x100000ee2 <+18>: movl 0x18(%rbp), %ebx 0x100000ee5 <+21>: movl 0x10(%rbp), %r14d 0x100000ee9 <+25>: movl %edi, -0x14(%rbp) 0x100000eec <+28>: movl %esi, -0x18(%rbp) 0x100000eef <+31>: movl %edx, -0x1c(%rbp) 0x100000ef2 <+34>: movl %ecx, -0x20(%rbp) 0x100000ef5 <+37>: movl %r8d, -0x24(%rbp) 0x100000ef9 <+41>: movl %r9d, -0x28(%rbp) 0x100000efd <+45>: movl %eax, -0x2c(%rbp) 0x100000f00 <+48>: movl %r10d, -0x30(%rbp) 0x100000f04 <+52>: movl %r11d, -0x34(%rbp) 0x100000f08 <+56>: movl %ebx, -0x38(%rbp) 0x100000f0b <+59>: movl %r14d, -0x3c(%rbp) 0x100000f0f <+63>: popq %rbx 0x100000f10 <+64>: popq %r14 0x100000f12 <+66>: popq %rbp 0x100000f13 <+67>: retq 0x100000f14 <+68>: nopw %cs:(%rax,%rax) 0x100000f1e <+78>: nop