Lesson 2 of 9 · 22m
Reading x86-64 Without Panicking
Fifteen instructions cover ninety percent of real disassembly. Learn the vocabulary, the calling convention, and the stack frame — the pattern-matching does the rest.
You do not need the 5,000-page Intel manual. Compiled code uses a small vocabulary obsessively; a dozen instructions cover ninety percent of what a disassembler shows you. Learn the vocabulary, the calling convention, and the shape of a stack frame — then let pattern-matching carry the load.
The working vocabulary
mov rax, [rdi+0x18] ; load the value at address rdi+0x18
lea rax, [rdi+0x18] ; compute the address itself, no dereference
push rbp / pop rbp ; save / restore on the stack
call 0x401040 / ret ; call a function / return to caller
cmp eax, 0x2a ; set flags = eax - 0x2a, discard the result
test eax, eax ; flags from eax AND eax — the classic null check
jz / jnz / jl / jge ; conditional jumps on those flags
jmp 0x401050 ; unconditional jump
xor eax, eax ; idiom: zero a register
nop ; do nothing — alignment padding and patch spacelea shows up constantly doing arithmetic in disguise — lea eax, [rdi+rdi*4] is x*5 wearing a trench coat. Compilers love it because it's a one-instruction multiply-add that doesn't touch flags.
The System V calling convention
On Linux x86-64, integer arguments go rdi, rsi, rdx, rcx, r8, r9 in order; the return value lands in rax. rbx, rbp, and r12–r15 are callee-saved — when a prologue pushes them, the function intends to use them and will restore them on the way out. This one fact lets you read any call site instantly: the register loads immediately before it are the arguments.
Anatomy of a function
push rbp
mov rbp, rsp ; establish frame pointer
sub rsp, 0x30 ; reserve 48 bytes of locals
mov [rbp-0x14], edi ; first arg stored into a local
mov [rbp-0x20], rsi ; second arg (looks like a pointer)
...
mov eax, [rbp-0x14] ; compute return value into rax
leave ; = mov rsp,rbp ; pop rbp
ret[rbp-0x14] is a local variable — Ghidra will name it local_14. Count the locals, watch which get written versus only read, and the function's working memory maps itself.
Two syntaxes, one machine
GDB defaults to AT&T order (mov %rdi,%rax — source first); Ghidra, IDA, and x64dbg show Intel order (mov rax,rdi — destination first). One-time fix: set disassembly-flavor intel, preferably in ~/.gdbinit so you never see AT&T again.
Field notes
test eax,eax+jzis a zero/null check;cmpfollowed byjae/jbmeans the source compared unsigned,jge/jlmeans signed — the jump mnemonic preserves type information the decompiler will guess at.- Windows x64 uses a different convention:
rcx, rdx, r8, r9, plus 32 bytes of caller-reserved shadow space. Know which world your binary lives in. - At
-O2, frame pointers are usually omitted — locals become[rsp+...]and their offsets shift asrspmoves. Same concept, harder to track; the decompiler earns its keep here.