Registers#

Assembly’s “variables” are CPU registers and memory addresses. Each architecture exposes a fixed set of general-purpose registers; memorise the names, the sizes, the calling convention, and which are caller-saved vs callee-saved.

For data widths and addressing modes, see Types. For calling conventions, see Functions.

x86-64 registers#

Sixteen general-purpose 64-bit registers. Each has 32-, 16-, and 8-bit aliases.

64-bit

32-bit

16-bit

8-bit / role

rax

eax

ax

al (low) / ah (high). Return value; mul / div implicit.

rbx

ebx

bx

bl. Callee-saved.

rcx

ecx

cx

cl. 4th arg (SysV); shift count.

rdx

edx

dx

dl. 3rd arg; mul / div high half.

rsi

esi

si

sil. 2nd arg; source index for rep ops.

rdi

edi

di

dil. 1st arg; dest index for rep ops.

rbp

ebp

bp

bpl. Frame pointer; callee-saved.

rsp

esp

sp

spl. Stack pointer.

r8r15

r8d

r8w

r8b …. r8 / r9 are args 5 / 6; r12–r15 callee-saved.

Special: rip (instruction pointer; used in RIP-relative addressing), rflags (status bits, see Control flow).

SIMD: xmm0..15 (128 bit, SSE), ymm0..15 (256 bit, AVX), zmm0..31 (512 bit, AVX-512). Float / double arguments live in xmm0..7 under SysV.

ARM64 (AArch64) registers#

31 general-purpose 64-bit registers plus a stack pointer.

Register

Role

x0..x7

argument and return registers

x8

indirect result location / syscall number on Linux

x9..x15

temporaries; caller-saved

x16, x17

intra-procedure-call scratch

x18

platform; not free to use

x19..x28

callee-saved

x29 (fp)

frame pointer

x30 (lr)

link register (return address)

sp

stack pointer

wN

32-bit alias for xN (writing w zeros the upper 32 bits)

SIMD / float: v0..v31 (128-bit NEON registers); s (32-bit float), d (64-bit double), q (128-bit quad) views into the same vector register.

Reading and writing#

x86-64.

mov     rax, 10                ; rax = 10
mov     ebx, eax               ; ebx = eax (upper 32 bits of rbx zeroed)
mov     ax, 0xFFFF             ; only low 16 bits modified
mov     al, 0xFF               ; only low 8 bits modified

The 32-bit write to eax zero-extends into the upper 32 bits of rax. The 8- and 16-bit writes do not. This is the single biggest x86-64 gotcha.

ARM64.

mov     x0, #10                // x0 = 10
mov     w0, w1                  // w0 = w1; upper 32 bits of x0 zeroed

Stack#

The stack grows downward on x86-64 and ARM64; rsp / sp points at the top (lowest address) of the live stack.

; x86-64: push / pop work in 8-byte units
push    rax                    ; rsp -= 8; [rsp] = rax
pop     rbx                    ; rbx = [rsp]; rsp += 8

sub     rsp, 32                ; reserve 32 bytes (4 qwords)
mov     [rsp + 0],  rdi
mov     [rsp + 8],  rsi
; … use the buffer …
add     rsp, 32                ; release
// ARM64: push / pop done with pre-/post-indexed ld/st
stp     x0, x1, [sp, #-16]!     // pre-decrement: sp -= 16; store x0/x1
ldp     x0, x1, [sp], #16        // post-increment: load; sp += 16

The stack must stay 16-byte aligned at function-call boundaries on both architectures. Misalignment crashes on some SSE instructions and on most ARM64 SIMD.

Frame pointer#

A traditional frame pointer (rbp / x29) saves the caller’s frame pointer at the top of the prologue so debuggers can walk the stack.

; x86-64 standard prologue
push    rbp
mov     rbp, rsp
sub     rsp, 32                ; local space

; epilogue
mov     rsp, rbp
pop     rbp
ret

Modern optimised builds often omit the frame pointer (the operator gets unwind data from .eh_frame instead); for hand-written assembly the operator keeps the standard frame pointer so gdb can backtrace.

Caller-saved vs callee-saved#

  • Caller-saved (volatile): the caller must save before a call if it needs the value preserved. x86-64 SysV: rax rcx rdx rsi rdi r8 r9 r10 r11. ARM64: x0..x18.

  • Callee-saved (non-volatile): the callee must preserve these across its execution. x86-64 SysV: rbx rbp r12 r13 r14 r15. ARM64: x19..x28 x29 x30.

The operator who writes assembly that other code calls must save and restore callee-saved registers in the prologue / epilogue.

Local variables#

There are no named locals; the operator reserves stack space and indexes through rbp (or rsp directly when no frame pointer is used).

; reserve 32 bytes of locals
push    rbp
mov     rbp, rsp
sub     rsp, 32

; local A at rbp-8, local B at rbp-16
mov     qword [rbp - 8], rdi
mov     qword [rbp - 16], rsi

mov     rax, qword [rbp - 8]
add     rax, qword [rbp - 16]

; epilogue
leave                          ; mov rsp, rbp; pop rbp
ret

Globals and static data#

The .data, .rodata, and .bss sections hold program-lifetime data. The operator labels them and refers by name; the assembler / linker resolves the address.

section .rodata
msg:    db "hello", 0xa
len     equ $ - msg

section .data
counter: dq 0

section .bss
buf:     resb 4096              ; 4 KB uninitialised

section .text
global _start
_start:
    lea     rsi, [rel msg]
    mov     rdx, len
    ; ...
    mov     rax, qword [rel counter]
    inc     rax
    mov     qword [rel counter], rax

[rel name] (RIP-relative addressing) is required for position-independent code (PIE binaries, shared libraries).

References#