On this page
ARM64 System Call ABI
Coverage: svc instruction → argument registers → differences from x86-64 → atomic operations (LDXR/STXR) → ARM64-specific syscalls → PAC/BTI Applicable: ARM64 (AArch64), Linux 3.7+
Overview
ARM64 (AArch64) system calls enter EL1 (kernel mode) using the svc #0 instruction. Unlike x86-64's syscall, svc does not automatically save or restore any registers—all context switching is handled in software. The ARM64 syscall ABI is more direct but requires more software coordination.
Register Conventions
# Syscall number:
x8 = syscall number (different from rax in x86!)
# Arguments (up to 6):
x0 = arg1 x1 = arg2 x2 = arg3
x3 = arg4 x4 = arg5 x5 = arg6
# Return value:
x0 = return value (>=0: success, -errno: error)
# ARM64 syscalls do not clobber any registers (except x0)
# → The kernel saves/restores all registers
# Unlike x86's rcx/r11 clobber
svc Instruction Behavior
flowchart TD
SVC["svc #0 instruction execution"]
SVC --> S1["① Save PSTATE → SPSR_EL1"]
S1 --> S2["② Save return address → ELR_EL1<br/>(Exception Link Register)"]
S2 --> S3["③ CPU switches to EL1 (kernel mode)"]
S3 --> S4["④ Jump to VBAR_EL1 + 0x400<br/>(sync exception from EL0, 64-bit)"]
S4 --> S5["⑤ Kernel exception vector → kernel_entry<br/>→ el0_svc handler"]
classDef inst fill:#e3f2fd,stroke:#1565c0
classDef step fill:#f3e5f5,stroke:#7b1fa2
classDef done fill:#e8f5e9,stroke:#2e7d32
class SVC inst
class S1,S2,S3,S4 step
class S5 done
Kernel Entry: el0_svc
// arch/arm64/kernel/entry.S
el0_svc:
// 1. Save all user registers to kernel stack (kernel_entry macro)
kernel_entry 0
// 2. Read x8 = syscall number
ldr x16, [tsk, #TSK_TI_FLAGS]
tbnz x16, #TIF_SME, el0_sve_acc // Check SME
// 3. Check if tracing is enabled (ptrace / audit)
ldr x16, [tsk, #TSK_TI_FLAGS]
tst x16, #_TIF_SYSCALL_WORK
b.ne el0_svc_naked
// 4. Call syscall handler
bl el0_svc_common
→ invoke_syscall(regs->regs[8], regs->regs[0..5], ...)
→ sys_call_table[x8](x0, x1, x2, x3, x4, x5)
// 5. regs->regs[0] = return value
// 6. kernel_exit 0 → restore all registers → eret
Key Differences from x86-64
| x86-64 | ARM64 | |
|---|---|---|
| syscall instruction | syscall | svc #0 |
| syscall nr register | rax | x8 |
| Hardware save | RCX←RIP, R11←RFLAGS | ELR_EL1, SPSR_EL1 |
| Stack switch | Manual (read per-CPU) | Manual (read SP_EL0) |
| Return instruction | sysretq | eret |
| Clobbered registers | rcx, r11 | None (all saved/restored) |
| Argument registers | rdi,rsi,rdx,r10,r8,r9 | x0,x1,x2,x3,x4,x5 |
| Typical latency | ~50-70c | ~30-50c |
ARM64-Specific Syscalls
// ARM64 has some syscalls that x86 does not:
// getcpu (get current CPU and NUMA node):
; // Usually implemented via vDSO, not a real syscall
// Memory model-related syscalls:
; // x86 also has this, but ARM implementation differs
// PAC (Pointer Authentication):
__NR_prctl PR_PAC_RESET_KEYS // ARM64 resets PAC keys
Atomic Operations: LDXR / STXR
ARM64 does not rely on the lock prefix (x86's LOCK CMPXCHG)
Instead, it uses Load-Exclusive / Store-Exclusive pairs:
1. LDXR x0, [x1] // Load and mark as exclusive
2. ... modify x0 ...
3. STXR w2, x0, [x1] // Attempt store → w2=0 success, w2=1 failure (retry)
4. cbnz w2, retry // Failure → retry
Unlike x86's LOCK CMPXCHG:
No bus locking required → exclusive access can exist on multiple addresses simultaneously
Better scalability on large NUMA systems
User-Space CPU Feature Detection
// ARM64: HWCAP flags (via getauxval or /proc/self/auxv)
unsigned long hwcap = ;
if
Debugging
# View syscall table
# Trace with strace
# HWCAP
LD_SHOW_AUXV=1 |
References
- Source Code:
arch/arm64/kernel/entry.S,arch/arm64/kernel/syscall.c - ARM64 ABI: AAPCS64 (Procedure Call Standard for ARM 64-bit)
- LWN: "System calls on ARM64", "Pointer Authentication in the kernel"
Keywords: svc, ARM64, AArch64, ELR_EL1, SPSR_EL1, LDXR/STXR, LSE, PAC, HWCAP, NEON