
The first time you open a disassembler, x86-64 assembly can look like a machine threw punctuation at a wall. That reaction is normal. The good news is that you do not need to memorize the whole instruction set to read a function. You need a handful of registers, maybe a dozen instructions, and a way to tell Intel syntax from AT&T syntax. Once those click, disassembly becomes less like noise and more like a very literal recipe.
The registers you will actually see
x86-64 has 16 general-purpose 64-bit registers. Their names are historical, and most of them can hold almost anything. The conventions are what matter when you are reading compiled code.
- rax — often the return value. In many functions, you will see the result placed in eax or rax right before ret.
- rdi, rsi, rdx, rcx, r8, r9 — the first six integer or pointer arguments on Linux and macOS, following the System V AMD64 ABI.
- rbx, rbp, r12–r15 — callee-saved. A function must restore these before returning if it changes them.
- rsp — stack pointer. It tracks the top of the current stack frame.
- rbp — base or frame pointer. Unoptimized code often uses it as a stable anchor for local variables.
- rip — instruction pointer. It holds the address of the next instruction.
- rflags — condition flags. Comparisons and arithmetic set these, and conditional jumps read them.
Each 64-bit register has smaller aliases. eax is the low 32 bits of rax, ax is the low 16 bits, and al is the low 8 bits. Writing to a 32-bit register like eax zeroes the upper 32 bits of rax, which is a detail that surprises almost everyone at first.
Windows uses a different calling convention: the first four arguments go in rcx, rdx, r8, and r9, and the caller reserves shadow space on the stack. Always check which platform the disassembly came from.
The instructions that do most of the work
You can read a lot of assembly with a small vocabulary. These are the ones you will meet constantly.
- mov — copy a value. It does not move; it copies.
- lea — load effective address. It computes an address or simple arithmetic without touching memory.
- add, sub, imul — integer arithmetic.
- cmp, test — set flags for a later branch. cmp subtracts and discards the result. test does a bitwise AND and discards the result.
- jmp, je, jne, jl, jle, jg, jge — jump unconditionally or conditionally based on flags.
- call, ret — call a function and return from one. Return values usually arrive in rax.
- push, pop — move values on and off the stack.
- xor, and, or, shl, shr — bitwise operations and shifts. xor eax, eax is a common way to zero a register.
- movzx, movsx — move with zero-extension or sign-extension. Useful when a small value becomes a larger one.
Intel versus AT&T: same instructions, different clothes
This is the first practical hurdle. Both syntaxes describe the same machine code, but they write it differently. Intel syntax is common in Windows tools and in objdump -M intel. AT&T syntax is the default for GNU tools on Linux.
- Operand order: Intel uses destination, source. AT&T reverses it to source, destination.
- Sigils: AT&T puts % before registers and $ before immediate values. Intel uses bare names and numbers.
- Size suffixes: AT&T writes movl for 32-bit and movq for 64-bit. Intel uses DWORD PTR or QWORD PTR when the size is not obvious.
- Memory: Intel writes [base + index*scale + disp]. AT&T writes disp(base, index, scale).
- Comments: Intel uses ;. AT&T uses #.
Intel: mov rax, rbx
AT&T: movq %rbx, %rax
Intel: mov eax, DWORD PTR [rbp-4]
AT&T: movl -4(%rbp), %eax
Pick one syntax and stay with it while you learn. On Linux, objdump -d -M intel gives you Intel syntax, and GCC can emit it with -S -masm=intel.
A line-by-line walkthrough
The C function
Here is a small function that returns the larger of two integers:
int max(int a, int b) { return a > b ? a : b; }
The disassembly
Compiled without optimization, in Intel syntax, it may look like this:
max:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
mov eax, DWORD PTR [rbp-4]
cmp eax, DWORD PTR [rbp-8]
jle .L2
mov eax, DWORD PTR [rbp-4]
jmp .L3
.L2:
mov eax, DWORD PTR [rbp-8]
.L3:
pop rbp
ret
What each line does
- push rbp saves the caller’s frame pointer on the stack.
- mov rbp, rsp creates a new frame, so local variables can be addressed relative to rbp.
- mov DWORD PTR [rbp-4], edi stores the first argument, a, into a 4-byte local slot. edi is the low 32 bits of rdi.
- mov DWORD PTR [rbp-8], esi stores the second argument, b, into the next slot.
- mov eax, DWORD PTR [rbp-4] loads a into eax for comparison.
- cmp eax, DWORD PTR [
Photo: Júlio Riccó / Pexels

