Skip to main content

Calling conventions

x86

Let's take another simple example.

main.c
int add(int a, int b)
{
return a + b;
}


int main()
{
return add(1, 2);
}

When we call a function in C, the arguments are pushed on the stack.

This can be verified by disassembling the binary obtained from compilating the code.

# compile
gcc main.c -m32 -o main
# disassemble directly in bash
objdump -d -M intel main

Which gives :

00001194 <main>:
1194: 55 push ebp
1195: 89 e5 mov ebp,esp
1197: e8 13 00 00 00 call 11af <__x86.get_pc_thunk.ax>
119c: 05 58 2e 00 00 add eax,0x2e58
11a1: 6a 02 push 0x2 # second argument
11a3: 6a 01 push 0x1 # first argument
11a5: e8 d3 ff ff ff call 117d <add> # call add()
11aa: 83 c4 08 add esp,0x8
11ad: c9 leave
11ae: c3 ret

So on the x86 architecture, arguments in a function call are always pushed on the stack, in reverse order.

So that when you actually get inside the function, the stack layout will look like :

pwndbg> stack 20
00:0000ebp esp 0xffffd238 —▸ 0xffffd248 ◂— 0x0
01:00040xffffd23c —▸ 0x565561aa (main+22) ◂— add esp, 8
02:00080xffffd240 ◂— 0x1
03:000c│ 0xffffd244 ◂— 0x2
04:00100xffffd248 ◂— 0x0

Rule

Generally, with a function defined like :

int func(int arg1, int arg2, int arg3)
{
int local_var1;
int local_var2;
int local_var3;
return arg1 + arg2 + arg3;
}

Your stack layout will look like this when the function starts :