Skip to content

Calling Conventions

arm64

ARM64 Calling Convention (AAPCS64)

How AArch64 passes arguments and return values: x0–x7 for the first eight integer args, x8 for indirect results, and the link register x30 for the return address.

AArch64 follows AAPCS64. With 31 general-purpose registers it passes far more in registers than the stack-heavy x86 ABIs, so reading argument flow is usually just a matter of reading x0x7.

Integer / pointer arguments and return

Register(s)Role
x0x7First eight integer/pointer arguments
x0 (x1)Return value (x1 too for 128-bit returns)
x8Indirect result location (address of a large struct return)
further argspassed on the stack

Floating-point and SIMD arguments use v0v7 the same way, with v0 holding the FP return value. See ARM64 registers for the w/x 32/64-bit aliasing.

Saved registers and the frame

  • Callee-saved: x19x28 (and v8v15). A function must preserve these if it uses them — you'll see them spilled in the prologue.
  • Caller-saved (temporaries): x9x15.
  • x29 = frame pointer (FP), x30 = link register (LR), sp = stack pointer (must stay 16-byte aligned).

There is no x86-style call/ret that pushes the return address to the stack. Instead BL/BLR put the return address in x30 (LR), and RET jumps to whatever x30 holds.

asm
; typical leaf-ish prologue / epilogue
stp x29, x30, [sp, #-16]!   ; save FP and LR, pre-decrement sp
mov x29, sp                  ; set up frame pointer
; ... body uses x0..x7 as incoming args ...
ldp x29, x30, [sp], #16      ; restore FP and LR, post-increment sp
ret                          ; return to address in x30 (LR)

A leaf function (one that calls nothing) need not save x30 at all, since nothing overwrites it — its absence in the prologue tells you the function makes no calls.

Reverse-engineering notes

  • Arguments are almost always in x0x7 at function entry; map them positionally to reconstruct the signature. x0 is both the first argument and the return value.
  • stp x29, x30, [sp, #-N]! is the canonical non-leaf prologue — spotting it (and the matching ldp … ; ret) is the fastest way to find function boundaries in stripped AArch64 code.
  • A function that immediately uses x8 is returning a large struct by value: the caller passed the destination address in x8.
  • Because x30 is just a register, it can be saved, overwritten and restored — enabling tail calls (b target instead of bl/ret) and making return-address corruption look different from x86's stack-based scheme.

See also: x64 Calling Conventions · x86 (32-bit) Calling Conventions · ARM64 registers · ARM64 common instructions.