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.
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
User contributions licensed under CC BY-SA 3.0