Home Assembly

Reading x86-64 Assembly for the First Time: A Beginner's Guide

Assembly

September 14, 2026

Reading x86-64 Assembly for the First Time: A Beginner's Guide

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

  1. push rbp saves the caller’s frame pointer on the stack.
  2. mov rbp, rsp creates a new frame, so local variables can be addressed relative to rbp.
  3. 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.
  4. mov DWORD PTR [rbp-8], esi stores the second argument, b, into the next slot.
  5. mov eax, DWORD PTR [rbp-4] loads a into eax for comparison.
  6. cmp eax, DWORD PTR [

    Photo: Júlio Riccó / Pexels

Related Posts

Static vs Dynamic Binary Analysis: Which Should You Use First?
Reverse Engineering

October 12, 2026

Static vs Dynamic Binary Analysis: Which Should You Use First?

Static analysis maps what a binary could do; dynamic analysis shows what it actually does. Here is how to choose your starting workflow...

read more
Embedded Debugging Checklist: Logic Analyzer, SWD, and UART Before You Rewrite Code
Embedded Systems

October 08, 2026

Embedded Debugging Checklist: Logic Analyzer, SWD, and UART Before You Rewrite Code

A hardware-first checklist for embedded targets: verify power, clock, UART, and SWD before assuming your firmware is at fault.

read more
From Source to Silicon: A Beginner's Guide to the Compiler, Assembler, and Linker Pipeline
Architecture

October 03, 2026

From Source to Silicon: A Beginner's Guide to the Compiler, Assembler, and Linker Pipeline

Follow a C file through the preprocessor, compiler, assembler, linker, and loader, and learn the commands that leave every intermediate...

read more