Calling Conventions and ABI Cheat Sheet
Where the arguments go, who saves what, how the stack must look, and how to enter the kernel, for the
architectures you meet when reversing, debugging or writing shellcode. Everything sits on one page so it can be compared side by side.
Overview (function calls)
| ABI | Integer / pointer args | FP args | Return | Callee-saved | Stack at call | Below SP |
| x86 cdecl | stack, right to left | stack | eax, edx:eax, st(0) | ebx esi edi ebp | 16 B (Linux), 4 B (Windows) | — |
| x86-64 System V | rdi rsi rdx rcx r8 r9 | xmm0–xmm7 | rax, rdx:rax, xmm0, xmm1 | rbx rbp r12–r15 | 16 B | 128 B red zone |
| Windows x64 | rcx rdx r8 r9 | xmm0–xmm3 (by position) | rax, xmm0 | rbx rbp rdi rsi r12–r15 xmm6–xmm15 | 16 B + 32 B shadow space | none |
| ARM AAPCS | r0–r3 | s0–s15 / d0–d7 (hard-float) | r0, r0:r1, s0/d0 | r4–r11 d8–d15 | 8 B | none |
| AArch64 AAPCS64 | x0–x7 | v0–v7 | x0, x0:x1, v0–v3 | x19–x28 x29 d8–d15 | 16 B (always) | none |
| RISC-V LP64D | a0–a7 | fa0–fa7 | a0, a0:a1, fa0, fa1 | s0–s11 fs0–fs11 | 16 B | none |
| MIPS o32 | a0–a3 + 16 B home area | $f12, $f14 | v0, v0:v1, $f0 | s0–s7 fp | 8 B | none |
| MIPS n64 | a0–a7 | $f12–$f19 | v0, v0:v1, $f0 | s0–s7 fp gp | 16 B | none |
| PowerPC64 ELFv2 | r3–r10 | f1–f13 | r3, r3:r4, f1 | r14–r31 f14–f31 v20–v31 | 16 B | 288 B red zone |
"Callee-saved" (non-volatile) registers must hold the same value when the function returns, so a function that uses them saves them in its prologue.
Every other general purpose register is caller-saved (volatile): it can be destroyed by any call. The stack pointer is always preserved.
System calls
| OS / arch | Instruction | Number | Arguments | Return | Clobbered / error | execve / exit |
| Linux x86 | int 0x80 | eax | ebx ecx edx esi edi ebp | eax | -errno in eax | 11 / 1 |
| Linux x86-64 | syscall | rax | rdi rsi rdx r10 r8 r9 | rax | rcx, r11 destroyed; -errno in rax | 59 / 60 |
| Linux ARM (EABI) | svc #0 | r7 | r0–r6 | r0 | -errno in r0 | 11 / 1 |
| Linux AArch64 | svc #0 | x8 | x0–x5 | x0 | -errno in x0 | 221 / 93 |
| Linux RISC-V | ecall | a7 | a0–a5 | a0 | -errno in a0 | 221 / 93 |
| Linux MIPS o32 | syscall | v0 | a0–a3, then stack (sp+16...) | v0 | a3 = 1 on error, v0 = errno | 4011 / 4001 |
| Linux MIPS n64 | syscall | v0 | a0–a5 | v0 | a3 = 1 on error, v0 = errno | 5057 / 5058 |
| Linux PowerPC64 | sc | r0 | r3–r8 | r3 | CR0.SO set on error, r3 = errno | 11 / 1 |
| macOS x86-64 | syscall | rax = 0x2000000 + nr | rdi rsi rdx r10 r8 r9 | rax (rdx) | carry flag set on error, rax = errno | 59 / 1 |
| macOS arm64 | svc #0x80 | x16 | x0–x5 | x0 (x1) | carry flag set on error, x0 = errno | 59 / 1 |
| FreeBSD x86-64 | syscall | rax | rdi rsi rdx r10 r8 r9 | rax (rdx) | carry flag set on error, rax = errno | 59 / 1 |
| Windows x64 | syscall | eax (SSN) | r10 rdx r8 r9, then stack (after 32 B shadow) | rax (NTSTATUS) | numbers change between builds | — |
On Linux, a return value between -4095 and -1 is an error (-errno); libc turns it into -1 and sets errno.
syscall on x86-64 saves the return address in rcx and RFLAGS in r11, which is why the 4th argument moves from rcx to r10.
On Windows, system service numbers are not stable: user code goes through ntdll.dll (mov r10, rcx ; mov eax, SSN ; syscall).
x86 (32-bit)
| Convention | Arguments | Stack cleanup | Used by | MSVC decoration |
| cdecl | All on the stack, pushed right to left | Caller (add esp, N) | C default on Linux, BSD and Windows; required for variadic functions | _func |
| stdcall | All on the stack, right to left | Callee (ret N) | Win32 API (WINAPI, CALLBACK) | _func@N |
| fastcall | First two 32-bit args in ecx, edx, rest on the stack | Callee | MSVC __fastcall; GCC __attribute__((fastcall)) | @func@N |
| thiscall | this in ecx, rest on the stack | Callee | MSVC C++ member functions (GCC passes this as the first stack argument instead) | C++ mangled |
| regparm(3) | eax, edx, ecx, then stack | Caller | Linux kernel (i386) | — |
Registers
- Callee-saved:
ebx, esi, edi, ebp, esp
- Caller-saved:
eax, ecx, edx
- Return:
eax, 64-bit in edx:eax, float/double in st(0)
ebx holds the GOT address in Linux PIC code
Stack frame
- At function entry:
[esp] = return address, [esp+4] = first argument
- After
push ebp; mov ebp, esp: args at [ebp+8], [ebp+12]..., locals at [ebp-4]...
- Linux (System V i386 since GCC 4.5):
esp is 16-byte aligned before the call
Structs
- Returned through a hidden pointer pushed as the first argument
- System V i386: the callee pops that pointer (
ret $4) and returns it in eax
- MSVC returns structs of 8 bytes or less in
edx:eax
x86-64 System V (Linux, BSD, macOS, Solaris)
| Register | Role | Saved by |
| rdi, rsi, rdx, rcx, r8, r9 | Integer / pointer arguments 1–6 | Caller |
| rax | Return value; number of vector registers used by a variadic call (al) | Caller |
| rdx | Second return register (128-bit values, small structs) | Caller |
| r10 | Static chain pointer (nested functions); 4th syscall argument | Caller |
| r11 | Scratch (PLT stubs, destroyed by syscall) | Caller |
| rbx, rbp, r12–r15 | General purpose; rbp is the optional frame pointer | Callee |
| rsp | Stack pointer | Callee |
| xmm0–xmm7 | FP / vector arguments; xmm0, xmm1 return | Caller |
| xmm8–xmm15 | Scratch | Caller |
| fs | Thread pointer on Linux (fs:0x28 = stack canary on glibc); macOS uses gs | — |
Stack
rsp is 16-byte aligned before call, so rsp + 8 is aligned at function entry
- Extra arguments go on the stack, right to left, 8-byte slots: 7th argument at
[rsp+8] on entry
- Red zone: 128 bytes below
rsp that leaf functions can use without moving rsp. Kernel code is built with -mno-red-zone.
- A misaligned stack is the usual reason why
system() crashes on a movaps in ROP chains: add a ret gadget.
Aggregates
- Structs up to 16 bytes are split into 8-byte chunks, each classified INTEGER (GPR) or SSE (xmm)
- Larger structs are passed in memory (copied on the stack)
- Large return values: the caller passes a buffer in
rdi, the callee returns it in rax
Variadic and misc
al = upper bound on the number of vector registers used (that is why you see xor eax, eax before printf)
- The direction flag is clear on entry and must be clear on return
long double is 80-bit x87, passed in memory and returned in st(0)
Windows x64
| Register | Role | Saved by |
| rcx, rdx, r8, r9 | Arguments 1–4 (integer, pointer, or structs of 1, 2, 4, 8 bytes) | Caller |
| xmm0–xmm3 | Arguments 1–4 when they are float/double: slots are positional, f(int, double) uses rcx and xmm1 | Caller |
| rax / xmm0 | Return value | Caller |
| r10, r11 | Scratch | Caller |
| rbx, rbp, rdi, rsi, r12–r15 | General purpose (note: rdi/rsi are callee-saved, unlike System V) | Callee |
| xmm6–xmm15 | Vector registers (low 128 bits) | Callee |
| gs | TEB pointer in user mode (gs:[0x30] = TEB, gs:[0x60] = PEB) | — |
Shadow space
- The caller always reserves 32 bytes above the return address (home space) for the 4 register arguments, even if the callee takes fewer
- 5th argument at
[rsp+0x28] on entry
- No red zone: anything below
rsp can be overwritten at any time
Aggregates
- Structs that are not 1, 2, 4 or 8 bytes are passed by reference (pointer to a caller-made copy)
- Return values larger than 8 bytes: hidden buffer pointer in
rcx, returned in rax
- Variadic: float arguments are also copied to the matching integer register
Unwinding
- Non-leaf functions must have unwind data in
.pdata/.xdata
- Prologues follow a fixed shape (push non-volatiles,
sub rsp, optional frame pointer) so the OS can unwind without frame pointers
ARM 32-bit (AAPCS / EABI)
| Register | Role | Saved by |
| r0–r3 | Arguments and return (r0, 64-bit in r0:r1) | Caller |
| r4–r8, r10 | Variables | Callee |
| r9 | Platform register (TLS, static base) or a callee-saved variable | Callee / platform |
| r11 (fp) | Frame pointer in ARM state | Callee |
| r7 | Frame pointer in Thumb code and on Apple platforms; syscall number on Linux | Callee |
| r12 (ip) | Intra-procedure scratch (veneers, PLT) | Caller |
| r13 (sp) | Stack pointer, 8-byte aligned at public interfaces | Callee |
| r14 (lr) | Return address | — |
| r15 (pc) | Program counter (reads as current instruction + 8 in ARM, + 4 in Thumb) | — |
| s0–s15 / d0–d7 | FP arguments and return (hard-float, armhf) | Caller |
| s16–s31 / d8–d15 | FP variables | Callee |
64-bit arguments use an even/odd register pair (r0:r1 or r2:r3), skipping a register if needed. With soft-float (armel), FP values travel in core registers.
Bit 0 of a branch target selects the instruction set: bx/blx to an odd address switches to Thumb. A function returns with bx lr or pop {..., pc}.
AArch64 (AAPCS64)
| Register | Role | Saved by |
| x0–x7 | Arguments and return (x0, 128-bit in x0:x1) | Caller |
| x8 | Indirect result location (pointer to the buffer for large return values); syscall number on Linux | Caller |
| x9–x15 | Temporaries | Caller |
| x16, x17 (ip0, ip1) | Intra-procedure scratch: veneers and PLT stubs use them; x16 is the syscall number on macOS | Caller |
| x18 | Platform register: reserved on Apple platforms, TEB on Windows, shadow call stack on Android; a temporary elsewhere | Platform |
| x19–x28 | Variables | Callee |
| x29 (fp) | Frame pointer: points to the saved {fp, lr} pair | Callee |
| x30 (lr) | Return address, written by bl/blr | — |
| sp | Stack pointer, always 16-byte aligned | Callee |
| v0–v7 | FP / SIMD arguments and return (up to 4 for homogeneous aggregates) | Caller |
| v8–v15 | Only the low 64 bits (d8–d15) are preserved | Callee (low half) |
| v16–v31 | Temporaries | Caller |
| tpidr_el0 | Thread pointer on Linux (mrs x0, tpidr_el0); Apple uses tpidrro_el0 | — |
Linux / generic
- Structs up to 16 bytes go in up to two registers; larger ones are copied by the caller and passed by reference
- Homogeneous float aggregates (up to 4 members) go in
v registers
- Variadic arguments follow the normal rules
- No red zone
Apple arm64 differences
x18 is reserved and must not be used
- Variadic arguments are always passed on the stack, in 8-byte slots
- Stack arguments are packed by natural alignment instead of 8-byte slots
- The caller sign/zero-extends arguments narrower than 32 bits
- The frame pointer
x29 must always point to a valid frame record
Windows arm64 differences
x18 points to the TEB in user mode
- Variadic: floating point arguments are passed in integer registers
- Unwind codes in
.pdata/.xdata, as on x64
Pointer authentication
paciasp signs lr with sp as modifier at entry; autiasp / retaa checks it on return
- The PAC lives in the unused top bits of the pointer
- arm64e (Apple) signs return addresses, function pointers and vtable entries
RISC-V (LP64D / ILP32D)
| Register | ABI name | Role | Saved by |
| x1 | ra | Return address | Caller |
| x2 | sp | Stack pointer, 16-byte aligned | Callee |
| x3, x4 | gp, tp | Global pointer (linker relaxation), thread pointer; never allocated | — |
| x10–x17 | a0–a7 | Arguments; a0, a1 return values; a7 = syscall number on Linux | Caller |
| x5–x7, x28–x31 | t0–t6 | Temporaries | Caller |
| x8, x9, x18–x27 | s0–s11 | Variables; s0 is the frame pointer | Callee |
| f10–f17 | fa0–fa7 | FP arguments; fa0, fa1 return values | Caller |
| f8, f9, f18–f27 | fs0–fs11 | FP variables | Callee |
| f0–f7, f28–f31 | ft0–ft11 | FP temporaries | Caller |
Aggregates up to 2×XLEN bytes are passed in registers; larger ones are passed
by reference. Variadic FP arguments go in integer registers, and 2×XLEN-aligned
variadic values use an even/odd register pair. On RV64, 32-bit values are kept sign-extended in registers, even
unsigned int.
The whole instruction reference is on the
RISC-V page.
MIPS (o32 and n64)
| Register | Name | Role | Saved by |
| $0 | zero | Always zero | — |
| $1 | at | Assembler temporary (pseudo-instruction expansion) | Caller |
| $2, $3 | v0, v1 | Return values; v0 = syscall number | Caller |
| $4–$7 | a0–a3 | Arguments (n64: $4–$11 are a0–a7) | Caller |
| $8–$15, $24 | t0–t7, t8 | Temporaries (n64 names $12–$15 t0–t3) | Caller |
| $16–$23 | s0–s7 | Variables | Callee |
| $25 | t9 | Address of the called function in PIC code (jalr $t9) | Caller |
| $26, $27 | k0, k1 | Reserved for the kernel | — |
| $28 | gp | Global pointer (GOT access); recomputed from t9 in PIC prologues | o32: caller, n64: callee |
| $29 | sp | Stack pointer | Callee |
| $30 | fp / s8 | Frame pointer or variable | Callee |
| $31 | ra | Return address (written by jal/jalr) | Caller |
o32: the caller always reserves 16 bytes of stack for a0–a3, so the 5th argument is at 16($sp). FP arguments use $f12 and $f14, results come back in $f0.
n64: 8 argument registers, FP arguments in $f12–$f19, no home area, 16-byte stack alignment.
Remember the branch delay slot: the instruction after a branch or jump is executed before the jump takes effect.
PowerPC64 (ELFv2, little endian)
| Register | Role | Saved by |
| r0 | Scratch; syscall number | Caller |
| r1 | Stack pointer (back chain stored at 0(r1)) | Callee |
| r2 | TOC pointer (global data access) | Special |
| r3–r10 | Arguments; r3 (and r4) return | Caller |
| r11, r12 | Scratch; r12 holds the function address at a global entry point | Caller |
| r13 | Thread pointer | Reserved |
| r14–r31 | Variables | Callee |
| f1–f13 | FP arguments; f1 return | Caller |
| f14–f31, v20–v31 | FP and vector variables | Callee |
| lr, ctr | Return address; count register (indirect calls via mtctr / bctrl) | Caller |
| cr2–cr4 | Condition register fields | Callee |
288-byte red zone below r1, 16-byte stack alignment. Functions have a global entry point that computes r2 from r12, and a local entry point that skips it.
Typical prologues and epilogues
What a non-leaf function with a frame pointer and a few locals looks like when compiled. Handy to spot function boundaries in a raw dump.
x86
push ebp
mov ebp, esp
sub esp, 0x18
...
leave ; mov esp, ebp ; pop ebp
ret
x86-64
push rbp
mov rbp, rsp
push rbx
sub rsp, 0x18 ; keep rsp 16-byte aligned
...
add rsp, 0x18
pop rbx
pop rbp
ret
ARM (Thumb-2)
push {r4, r7, lr}
add r7, sp, #4 ; frame pointer
sub sp, #12
...
add sp, #12
pop {r4, r7, pc} ; return by popping pc
AArch64
stp x29, x30, [sp, #-32]!
mov x29, sp
str x19, [sp, #16]
...
ldr x19, [sp, #16]
ldp x29, x30, [sp], #32
ret
RISC-V (RV64)
addi sp, sp, -32
sd ra, 24(sp)
sd s0, 16(sp)
addi s0, sp, 32 ; frame pointer
...
ld ra, 24(sp)
ld s0, 16(sp)
addi sp, sp, 32
ret
MIPS o32 (PIC)
lui $gp, %hi(_gp_disp)
addiu $gp, $gp, %lo(_gp_disp)
addu $gp, $gp, $t9
addiu $sp, $sp, -32
sw $ra, 28($sp)
...
lw $ra, 28($sp)
jr $ra
addiu $sp, $sp, 32 ; delay slot