X86_Function Call Procedures under 64 Assemblies

Posted by tkm on Sun, 21 Jun 2020 03:49:09 +0200

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

  1. bp always points to the bottom of the current top stack frame
  2. 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:

  1. 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)
  2. 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

  1. 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.
  2. 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)
  3. 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

  1. Pushq%rbp: RBP pointer holding a function
  2. 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.

  1. POPQ%rbp: Retrieves the temporary RBP pointer and restores the value of the RBP register.
  2. 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:

  1. Register pass-through when there are enough registers
  2. When the register is not enough, push the parameter onto the stack and pass the parameter in the stack memory.

Return value:

  1. 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    

Indefinite Parameter Pass-Through

Topics: git less