How does argument passing and returning values work in C/C++ on x86 at the assembly level?

1

I'm trying to find out how, when calling a function in C/C++ the arguments to the function are passed and how the return value from the function is given back at the assembly level. I found these answers:

Assembly x86 - Calling C functions

How does argument passing work?

which say that the stack is used to pass arguments to and from functions in C/C++. However when I wrote a simple C++ test program and disassembled it in radare2, it did not appear to be using the stack to pass arguments to the function. Instead, the arguments were put in esi and edi before the function call.

While an answer on this site will be more immediately helpful, a link to documentation where I could learn more would be greatly appreciated, even though proper documentation will probably be so technical it goes over my head.

The test C++ program:

void foo(int a, int b) {
    return;
}

int main() {
    int a=5;
    foo(5,a);
    return 0;
}

The disassembled assembly from radare2:

┌ (fcn) main 37
│   int main (int argc, char **argv, char **envp);
│           ; var int32_t var_4h @ rbp-0x4
│           ; DATA XREF from entry0 @ 0x50d
│           0x00000607      55             push rbp
│           0x00000608      4889e5         mov rbp, rsp
│           0x0000060b      4883ec10       sub rsp, 0x10
│           0x0000060f      c745fc050000.  mov dword [var_4h], 5
│           0x00000616      8b45fc         mov eax, dword [var_4h]
│           0x00000619      89c6           mov esi, eax
│           0x0000061b      bf05000000     mov edi, 5
│           0x00000620      e8d5ffffff     call sym foo(int, int)      ; sym.foo_int__int
│           0x00000625      b800000000     mov eax, 0
│           0x0000062a      c9             leave
└           0x0000062b      c3             ret

What prompted this question was the following disassembled code from a beginner crackme I am trying to solve.

I am not asking for help solving this crackme, just help understanding how function arguments are passed in the below examples and where I could go to look this up in the future.

The following example from the crackme shows sym.imp.puts being called (the following two examples are hand typed so they may contain mistakes, although I did try to proofread):

; CODE XREF from main @ 0x11f8
; 0x36915
; "Wrong key!"
lea rdi, str.Wrong_key
; int puts(const char *s)
call sym.imp.puts;[oo]

puts appears to have the address to str.Wrong_key passed to it from rdi.

On the other hand this code snippet:

lea rax, [var_6ch]
mov rsi, rax
; const char *format
; "%d"
lea rdi, [0x000368ff]
mov eax, 0
; int scanf(const char *format)
call sym.imp.__isoc99_scanf;[ob]
mov eax, dword [var_6ch]
cmp eax, 1
je 0x1228

I am unable to understand what is going on in this code snippet. var_6ch is not used before this. Presumably scanf is being called like this: scanf("%d", var_6ch); But I fail to see how var_6ch or the %d string are passed to scanf.

All of the previous code samples do not appear to use the stack to pass arguments so any and all help is appreciated.

c++
c
assembly
x86
arguments
asked on Stack Overflow Sep 1, 2019 by DragonTamer • edited Sep 1, 2019 by Marco Bonelli

1 Answer

4

On x86-64 (which is the architecture you're on), the System V ABI calling convention is the most commonly used, and defines the following registers to be used for function parameters (in order of declaration): RDI, RSI, RDX, RCX, R8, R9, XMM0 to XMM07. The return register is RAX.

On the other side, on x86 32bit, since there are fewer registers, parameters are usually passed on the stack.

Of course a calling convention does not only define how to pass parameters, to know more you can take a look at the wikipedia page.

What you're seeing in that snippet of code is exactly this:

lea rax, [var_6ch]               ; get the address of some variable
mov rsi, rax                     ; rsi = second parameter
                                 ; loads the variable's address into rsi

lea rdi, [0x000368ff]            ; rdi = first parameter
                                 ; loads the address of the format string into rdi

mov eax, 0                       ; clear eax
call sym.imp.__isoc99_scanf;[ob] ; call scanf(rdi, rsi)

mov eax, dword [var_6ch]
cmp eax, 1              
je 0x1228
answered on Stack Overflow Sep 1, 2019 by Marco Bonelli • edited Sep 1, 2019 by Marco Bonelli

User contributions licensed under CC BY-SA 3.0