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)

ABIInteger / pointer argsFP argsReturnCallee-savedStack at callBelow SP
x86 cdeclstack, right to leftstackeax, edx:eax, st(0)ebx esi edi ebp16 B (Linux), 4 B (Windows)—
x86-64 System Vrdi rsi rdx rcx r8 r9xmm0–xmm7rax, rdx:rax, xmm0, xmm1rbx rbp r12–r1516 B128 B red zone
Windows x64rcx rdx r8 r9xmm0–xmm3 (by position)rax, xmm0rbx rbp rdi rsi r12–r15 xmm6–xmm1516 B + 32 B shadow spacenone
ARM AAPCSr0–r3s0–s15 / d0–d7 (hard-float)r0, r0:r1, s0/d0r4–r11 d8–d158 Bnone
AArch64 AAPCS64x0–x7v0–v7x0, x0:x1, v0–v3x19–x28 x29 d8–d1516 B (always)none
RISC-V LP64Da0–a7fa0–fa7a0, a0:a1, fa0, fa1s0–s11 fs0–fs1116 Bnone
MIPS o32a0–a3 + 16 B home area$f12, $f14v0, v0:v1, $f0s0–s7 fp8 Bnone
MIPS n64a0–a7$f12–$f19v0, v0:v1, $f0s0–s7 fp gp16 Bnone
PowerPC64 ELFv2r3–r10f1–f13r3, r3:r4, f1r14–r31 f14–f31 v20–v3116 B288 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 / archInstructionNumberArgumentsReturnClobbered / errorexecve / exit
Linux x86int 0x80eaxebx ecx edx esi edi ebpeax-errno in eax11 / 1
Linux x86-64syscallraxrdi rsi rdx r10 r8 r9raxrcx, r11 destroyed; -errno in rax59 / 60
Linux ARM (EABI)svc #0r7r0–r6r0-errno in r011 / 1
Linux AArch64svc #0x8x0–x5x0-errno in x0221 / 93
Linux RISC-Vecalla7a0–a5a0-errno in a0221 / 93
Linux MIPS o32syscallv0a0–a3, then stack (sp+16...)v0a3 = 1 on error, v0 = errno4011 / 4001
Linux MIPS n64syscallv0a0–a5v0a3 = 1 on error, v0 = errno5057 / 5058
Linux PowerPC64scr0r3–r8r3CR0.SO set on error, r3 = errno11 / 1
macOS x86-64syscallrax = 0x2000000 + nrrdi rsi rdx r10 r8 r9rax (rdx)carry flag set on error, rax = errno59 / 1
macOS arm64svc #0x80x16x0–x5x0 (x1)carry flag set on error, x0 = errno59 / 1
FreeBSD x86-64syscallraxrdi rsi rdx r10 r8 r9rax (rdx)carry flag set on error, rax = errno59 / 1
Windows x64syscalleax (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)

ConventionArgumentsStack cleanupUsed byMSVC decoration
cdeclAll on the stack, pushed right to leftCaller (add esp, N)C default on Linux, BSD and Windows; required for variadic functions_func
stdcallAll on the stack, right to leftCallee (ret N)Win32 API (WINAPI, CALLBACK)_func@N
fastcallFirst two 32-bit args in ecx, edx, rest on the stackCalleeMSVC __fastcall; GCC __attribute__((fastcall))@func@N
thiscallthis in ecx, rest on the stackCalleeMSVC C++ member functions (GCC passes this as the first stack argument instead)C++ mangled
regparm(3)eax, edx, ecx, then stackCallerLinux 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)

RegisterRoleSaved by
rdi, rsi, rdx, rcx, r8, r9Integer / pointer arguments 1–6Caller
raxReturn value; number of vector registers used by a variadic call (al)Caller
rdxSecond return register (128-bit values, small structs)Caller
r10Static chain pointer (nested functions); 4th syscall argumentCaller
r11Scratch (PLT stubs, destroyed by syscall)Caller
rbx, rbp, r12–r15General purpose; rbp is the optional frame pointerCallee
rspStack pointerCallee
xmm0–xmm7FP / vector arguments; xmm0, xmm1 returnCaller
xmm8–xmm15ScratchCaller
fsThread 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

RegisterRoleSaved by
rcx, rdx, r8, r9Arguments 1–4 (integer, pointer, or structs of 1, 2, 4, 8 bytes)Caller
xmm0–xmm3Arguments 1–4 when they are float/double: slots are positional, f(int, double) uses rcx and xmm1Caller
rax / xmm0Return valueCaller
r10, r11ScratchCaller
rbx, rbp, rdi, rsi, r12–r15General purpose (note: rdi/rsi are callee-saved, unlike System V)Callee
xmm6–xmm15Vector registers (low 128 bits)Callee
gsTEB 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)

RegisterRoleSaved by
r0–r3Arguments and return (r0, 64-bit in r0:r1)Caller
r4–r8, r10VariablesCallee
r9Platform register (TLS, static base) or a callee-saved variableCallee / platform
r11 (fp)Frame pointer in ARM stateCallee
r7Frame pointer in Thumb code and on Apple platforms; syscall number on LinuxCallee
r12 (ip)Intra-procedure scratch (veneers, PLT)Caller
r13 (sp)Stack pointer, 8-byte aligned at public interfacesCallee
r14 (lr)Return address—
r15 (pc)Program counter (reads as current instruction + 8 in ARM, + 4 in Thumb)—
s0–s15 / d0–d7FP arguments and return (hard-float, armhf)Caller
s16–s31 / d8–d15FP variablesCallee
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)

RegisterRoleSaved by
x0–x7Arguments and return (x0, 128-bit in x0:x1)Caller
x8Indirect result location (pointer to the buffer for large return values); syscall number on LinuxCaller
x9–x15TemporariesCaller
x16, x17 (ip0, ip1)Intra-procedure scratch: veneers and PLT stubs use them; x16 is the syscall number on macOSCaller
x18Platform register: reserved on Apple platforms, TEB on Windows, shadow call stack on Android; a temporary elsewherePlatform
x19–x28VariablesCallee
x29 (fp)Frame pointer: points to the saved {fp, lr} pairCallee
x30 (lr)Return address, written by bl/blr—
spStack pointer, always 16-byte alignedCallee
v0–v7FP / SIMD arguments and return (up to 4 for homogeneous aggregates)Caller
v8–v15Only the low 64 bits (d8–d15) are preservedCallee (low half)
v16–v31TemporariesCaller
tpidr_el0Thread 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)

RegisterABI nameRoleSaved by
x1raReturn addressCaller
x2spStack pointer, 16-byte alignedCallee
x3, x4gp, tpGlobal pointer (linker relaxation), thread pointer; never allocated—
x10–x17a0–a7Arguments; a0, a1 return values; a7 = syscall number on LinuxCaller
x5–x7, x28–x31t0–t6TemporariesCaller
x8, x9, x18–x27s0–s11Variables; s0 is the frame pointerCallee
f10–f17fa0–fa7FP arguments; fa0, fa1 return valuesCaller
f8, f9, f18–f27fs0–fs11FP variablesCallee
f0–f7, f28–f31ft0–ft11FP temporariesCaller
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)

RegisterNameRoleSaved by
$0zeroAlways zero—
$1atAssembler temporary (pseudo-instruction expansion)Caller
$2, $3v0, v1Return values; v0 = syscall numberCaller
$4–$7a0–a3Arguments (n64: $4–$11 are a0–a7)Caller
$8–$15, $24t0–t7, t8Temporaries (n64 names $12–$15 t0–t3)Caller
$16–$23s0–s7VariablesCallee
$25t9Address of the called function in PIC code (jalr $t9)Caller
$26, $27k0, k1Reserved for the kernel—
$28gpGlobal pointer (GOT access); recomputed from t9 in PIC prologueso32: caller, n64: callee
$29spStack pointerCallee
$30fp / s8Frame pointer or variableCallee
$31raReturn 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)

RegisterRoleSaved by
r0Scratch; syscall numberCaller
r1Stack pointer (back chain stored at 0(r1))Callee
r2TOC pointer (global data access)Special
r3–r10Arguments; r3 (and r4) returnCaller
r11, r12Scratch; r12 holds the function address at a global entry pointCaller
r13Thread pointerReserved
r14–r31VariablesCallee
f1–f13FP arguments; f1 returnCaller
f14–f31, v20–v31FP and vector variablesCallee
lr, ctrReturn address; count register (indirect calls via mtctr / bctrl)Caller
cr2–cr4Condition register fieldsCallee
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