Skip to content

Level 1 · Chapter 1.3

Calling conventions (System V, cdecl)

How functions pass arguments and return values: call and ret, the stack frame, System V x86-64 registers, 32-bit cdecl, and who saves what.

A function call is a contract. The caller has to put the arguments somewhere the callee will look, the callee has to leave the result somewhere the caller will read it, and both have to agree on which registers may be overwritten. That contract is the calling convention. The CPU doesn't enforce it — compilers do, so that code built by different compilers can call each other.

Two conventions cover most of what you'll read on Linux and macOS: System V AMD64 for 64-bit code and cdecl for 32-bit code.

call and ret

Everything starts with two instructions:

  • call f pushes the return address — the address of the instruction right after the call — onto the stack, then jumps to f.
  • ret pops that address back into rip, so execution resumes right after the call.

The stack is how a function knows where to go back to. That's also why corrupting it is so dangerous: overwrite the return address and ret jumps wherever the new value points.

System V x86-64: arguments in registers

In 64-bit Linux and macOS, the first six integer or pointer arguments go in registers, in this order:

Argument1st2nd3rd4th5th6th
Registerrdirsirdxrcxr8r9

Further arguments are pushed on the stack. Floating-point arguments use xmm0–xmm7. The return value comes back in rax (xmm0 for floating point).

Here is add2(3, 4) in its simplest form. The demo stops right after the call:

Live · System V: add2(3, 4)
program— ▸ is the next instruction
  1. _start:
  2. mov edi, 3 ; 1st argument
  3. mov esi, 4 ; 2nd argument
  4. call add2 ; push the return address, jump
  5. ret
  6. add2:
  7. push rbp ; prologue: save the caller's frame pointer
  8. mov rbp, rsp ; and start our own frame
  9. mov eax, edi
  10. add eax, esi ; the return value goes in eax
  11. pop rbp ; epilogue: restore the caller's frame pointer
  12. ret ; pop the return address into rip
step 0
Loading emulator…

Look at the stack: call has pushed 0x401003, labelled return address → _start+3. Keep stepping:

  1. push rbp saves the caller's frame pointer just below it.
  2. mov rbp, rsp makes rbp point at that saved value. From here on, the return address is always at [rbp+8].
  3. mov eax, edi / add eax, esi compute 3 + 4 in eax.
  4. pop rbp and ret undo the frame and jump back to _start+3 with eax = 7.

The stack frame

push rbp / mov rbp, rsp is the classic prologue, and leave (or mov rsp, rbp + pop rbp) followed by ret is the epilogue. Between them, a function that needs local variables subtracts from rsp to reserve space, and addresses everything relative to rbp:

LocationHolds
[rbp+16] and upstack arguments (7th and beyond)
[rbp+8]return address
[rbp]caller's saved rbp
[rbp-4], [rbp-8], …local variables

This layout is what makes debuggers' backtraces work: each saved rbp points to the previous frame, forming a linked list up the stack. Optimized code often skips rbp altogether and addresses locals from rsp instead.

A few more System V rules you'll see the effects of:

  • Alignment: rsp must be a multiple of 16 just before a call, which is why functions sometimes subtract "too much" from rsp.
  • Red zone: a function that calls nothing may use the 128 bytes below rsp without moving rsp at all.
  • For variadic functions like printf, al holds the number of vector registers used — that's the mov eax, 0 compilers put right before call printf.

32-bit cdecl: arguments on the stack

32-bit x86 has too few registers to spare, so cdecl passes every argument on the stack, pushed right to left — so the first argument ends up closest to the return address. The return value is in eax, and the caller removes the arguments afterwards.

Live · cdecl (32-bit): add2(3, 4)
program— ▸ is the next instruction
  1. _start:
  2. push 4 ; arguments right to left…
  3. push 3 ; …so the 1st ends up on top
  4. call add2
  5. add esp, 8 ; the caller removes its 2 arguments
  6. ret
  7. add2:
  8. push ebp
  9. mov ebp, esp
  10. mov eax, DWORD PTR [ebp+8] ; 1st argument
  11. add eax, DWORD PTR [ebp+12] ; 2nd argument
  12. pop ebp
  13. ret
step 0
Loading emulator…

The demo stops just after mov ebp, esp. With ebp = 0x7fffffcc, the stack reads, from the top:

AddressRelative to ebpHolds
0x7fffffcc[ebp]saved ebp
0x7fffffd0[ebp+4]return address
0x7fffffd4[ebp+8]3 — 1st argument
0x7fffffd8[ebp+12]4 — 2nd argument

That [ebp+8] = first argument pattern is one of the most recognizable sights in 32-bit disassembly. After ret, the caller's add esp, 8 throws the two arguments away.

Other 32-bit conventions differ in the details: stdcall (used by the Win32 API) makes the callee clean up with ret 8, and fastcall passes the first two arguments in ecx and edx.

Who saves which registers

A convention also splits registers into two groups:

System V x86-64cdecl (32-bit)
Callee-saved — a function must restore them before returningrbx, rbp, r12–r15 (and rsp)ebx, esi, edi, ebp
Caller-saved — may be overwritten by any callrax, rcx, rdx, rsi, rdi, r8–r11eax, ecx, edx

So if a function uses rbx, you'll see it push rbx in the prologue and pop rbx before returning. And if a caller needs a value in rcx to survive a call, it has to save it first — or keep it in a callee-saved register.

Windows is different

64-bit Windows uses its own convention: the first four arguments go in rcx, rdx, r8, r9, the caller reserves 32 bytes of "shadow space" on the stack, and rsi/rdi are callee-saved. Before naming the arguments of a function in a disassembly, check which OS the binary targets.

Practice

In the simulator, the Function call and Recursion (factorial) examples are compiled C. Switch between 64-bit and 32-bit to watch the same function receive its arguments in edi or at [ebp+8], and follow the frames pile up in the stack view.

In this level

  1. 1.1Registers and the register file
  2. 1.2Flags, cmp and conditional jumps
  3. 1.3Calling conventions (System V, cdecl)
  4. 1.4Assembler, linker and loaderPlanned
  5. 1.5Directives, sections and macrosPlanned