Part 2 · The calling contract
Who saves what
Caller-saved, callee-saved, the red zone, and the alignment rule you only notice when it is broken.
What this lesson is trying to break (2)
- A function call preserves all your registers.
- Stack alignment is a detail the hardware handles.
A call is not transparent. Some registers survive it and some do not, and which
is which is written into the ABI.
The two halves
Varies: only who is responsible for preserving the value
| Caller-saved (volatile) | Callee-saved (non-volatile) | |
|---|---|---|
| SysV AMD64 | rax rcx rdx rsi rdi r8–r11 | rbx rbp r12–r15 |
| ARM64 | x0–x18 | x19–x28, x29 |
| Who preserves it | The caller, before the call | The callee, if it uses it |
| After a call returns | May hold anything | Guaranteed unchanged |
| Good for | Short-lived values, arguments | Values live across a call |
Note that all six argument registers are caller-saved. A function that needs an argument after making a call must save it somewhere itself — which is why you see arguments copied into rbx or r12 early in functions that call other things.
This produces a readable pattern. A function that pushes rbx, r12, r13 in
its prologue is telling you: I have several values that must survive calls I am
about to make. The number of callee-saved pushes is a rough measure of how much
state a function carries across its calls.
push rbx / push r12 ... at the top, pop r12 / pop rbx at the bottom
This function makes calls and needs values to survive them.
The count tells you how much live state crosses call boundaries. A loop that calls something and accumulates a result will nearly always show this pattern, with the accumulator in a callee-saved register.
Conversely, a function using only rax, rcx, rdx and the argument registers,
with no pushes, is very likely a leaf.
The red zone
SysV grants leaf functions 128 bytes below rsp for free — no sub rsp needed.
Signal handlers and the kernel promise not to touch it.
Why only leaves: a call pushes a return address at [rsp-8], which is inside
that region. Any call destroys it.
This is the explanation for a small mystery you will hit: a function that clearly
uses stack memory (mov [rsp-8], edi) but never adjusts rsp. It is using the
red zone. Windows x64 has no red zone — it has shadow space instead, 32 bytes
that the caller allocates for the callee to spill into, which is the opposite
arrangement.
The alignment rule
On SysV, rsp must be 16-byte aligned at the point of a call. Since call
pushes 8 bytes, rsp is 8-mod-16 on function entry, and the standard
push rbp brings it back to aligned.
This exists for historical SSE reasons — some instructions required 16-byte aligned memory operands — and it survives as a guarantee library code relies on.
The reason to care: violating it does not fail where you violated it. Your function works; then some library it calls uses an aligned SSE instruction on a stack address and crashes with no obvious connection to the cause. If you ever write assembly by hand or use inline asm, this is the mistake that costs an afternoon.
A function that makes a call and has no locals still often contains:
sub rsp, 8
call something
add rsp, 8Eight bytes are allocated and never used. Why?
Alignment. On entry rsp is 8-mod-16 (the call pushed the return address).
If this function pushed nothing else, rsp would still be misaligned when it
makes its own call, breaking the contract for the callee.
So it subtracts 8 to realign. The space is genuinely unused — it exists purely to
move rsp.
You will also see this done as a pointless push rax, which is one byte of
encoding instead of four and achieves the same thing. When you see a push of a
register whose value obviously does not matter, it is almost always alignment
padding.
Reading an unfamiliar function, you see its prologue push rbx, r12 and r13,
and no sub rsp.
List everything you can infer about the function before reading a single line of its body. There are at least four inferences available.
What you should now be able to say
- Caller-saved may be clobbered by a call; callee-saved is guaranteed preserved. The ABI decides which.
- All argument registers are caller-saved, so values needed after a call get
copied into
rbx/r12-style registers — a readable signal. - The red zone is 128 bytes free to leaf functions only, and explains stack
use with no
sub rsp. rspmust be 16-byte aligned at a call, and violations crash somewhere else entirely.- A lone
sub rsp, 8or a pointlesspushis alignment padding.
Next: 2.5 — Reading a call chain.