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-64ARM64
syscall instructionsyscallsvc #0
syscall nr registerraxx8
Hardware saveRCX←RIP, R11←RFLAGSELR_EL1, SPSR_EL1
Stack switchManual (read per-CPU)Manual (read SP_EL0)
Return instructionsysretqeret
Clobbered registersrcx, r11None (all saved/restored)
Argument registersrdi,rsi,rdx,r10,r8,r9x0,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):
getcpu(&cpu, &node, NULL);  // Usually implemented via vDSO, not a real syscall

// Memory model-related syscalls:
mlock2(start, len, MLOCK_ONFAULT);  // 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)
#define HWCAP_FP       (1 << 0)   // Floating point
#define HWCAP_ASIMD    (1 << 1)   // NEON/ASIMD
#define HWCAP_AES      (1 << 3)   // AES instructions
#define HWCAP_PMULL    (1 << 4)   // Polynomial multiplication (GCM)
#define HWCAP_SHA1     (1 << 5)
#define HWCAP_SHA2     (1 << 6)
#define HWCAP_CRC32    (1 << 7)   // CRC32 instructions
#define HWCAP_ATOMICS  (1 << 8)   // LSE atomic operations (ARMv8.1)
#define HWCAP_SVE      (1 << 22)  // Scalable Vector Extension

unsigned long hwcap = getauxval(AT_HWCAP);
if (hwcap & HWCAP_AES) { /* use AES instructions */ }

Debugging

# View syscall table
cat /usr/include/asm-generic/unistd.h  # Generic table
cat /usr/include/asm/unistd.h          # ARM64-specific

# Trace with strace
strace -e trace=write,read ./a.out

# HWCAP
LD_SHOW_AUXV=1 /bin/true | grep HWCAP

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