x86-64 registers and flags, the parts that matter
You don't need the whole Intel manual to read disassembly. You need the sixteen general-purpose registers, a handful of flags, and how comparisons drive jumps.
The general-purpose registers
There are sixteen 64-bit registers: rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, and r8 to r15. Each can also be used at smaller sizes:
64-bit 32-bit 16-bit 8-bit
rax eax ax al
rcx ecx cx cl
rsi esi si sil
rsp esp sp spl
r8 r8d r8w r8bThe others follow the same pattern. Some have traditional roles: rsp is the stack pointer, rbp is often the frame pointer, rax holds return values, rcx is a counter for string and shift instructions. In the System V calling convention, arguments arrive in rdi, rsi, rdx, rcx, r8, r9.
rip is the instruction pointer. You never move into it directly, but you'll see it constantly as [rip+0x2fca]: position-independent code addressing data relative to the current instruction.
The zero-extension rule
This one surprises people:
- writing a 32-bit register clears the upper 32 bits of the 64-bit register:
mov eax, 1leavesrax = 1 - writing an 8- or 16-bit register leaves the other bits alone:
mov al, 1changes only the lowest byte
That's why compilers happily use mov eax, edi or xor eax, eax even for 64-bit values: they are shorter to encode, and the upper half is zeroed for free. It's also why mov edi, edi appears in jump-table code: it's a zero-extension, not a no-op.
Flags
Arithmetic instructions set bits in the rflags register. Four matter for reading code:
- ZF, zero flag: the result was zero
- SF, sign flag: the result's top bit was set (negative, if signed)
- CF, carry flag: an unsigned overflow or borrow happened
- OF, overflow flag: a signed overflow happened
cmp and test
Two instructions exist only to set flags:
cmp a, bcomputesa - b, sets flags, and throws the result awaytest a, bcomputesa & b, sets flags, and throws the result away
test eax, eax is the idiomatic "is eax zero / negative?": ANDing a value with itself sets ZF if it's zero and SF if its top bit is set.
Conditional jumps
After a cmp a, b, the jump tells you how the program compared them:
je / jz a == b ZF = 1
jne / jnz a != b ZF = 0
jl / jnge a < b signed SF != OF
jle a <= b signed ZF = 1 or SF != OF
jg a > b signed ZF = 0 and SF = OF
jge a >= b signed SF = OF
jb / jc a < b unsigned CF = 1
jbe a <= b unsigned CF = 1 or ZF = 1
ja a > b unsigned CF = 0 and ZF = 0
jae / jnc a >= b unsigned CF = 0
js / jns result negative / not negativeThe mnemonic gives away the type: l/g (less/greater) are signed, b/a (below/above) are unsigned. If you see ja after comparing an index with a bound, the index is unsigned, which is exactly the check a switch statement compiles to.
The same conditions appear in setcc (store 0/1 into a byte, like sete al) and cmovcc (conditional move, like cmovle eax, edi). Same suffixes, same meanings.
A worked line
cmp DWORD PTR [rbp-0x4], 0xff
jle 0x24"If the local at rbp-0x4 is less than or equal to 255, signed, jump to 0x24." Translating each cmp + jcc pair into one sentence like this is most of the work of reading control flow.