← SEARCHABLE ARCHIVE
Assembly · 2026-08-15

How to Read x86-64 Registers and Instructions

From partial registers, operand sizes, MOV and LEA to machine-code encoding

INTERACTIVE READING MODE →

How to Read x86-64 Registers and Instructions

These blog posts are written as lessons. Actual code can differ by processor generation, operating system, ABI, assembler, compiler, and build options, so always identify the target environment.

At first, rax, eax, ax, and al look like four registers, and mov seems to have dozens of unrelated forms. The useful skill is not memorizing every mnemonic. It is tracking what width is read, where the value comes from, where it goes, and which machine state changes.

This article uses Intel syntax to connect general-purpose and partial registers, operand sizes, mov, extension operations, lea, arithmetic, and basic machine-code encoding.

Keep three layers separate

C expression       total += values[i];
Assembly notation  add rax, qword ptr [rcx + rdx*8]
Machine bytes      48 03 04 D1

C expresses types and intent. Assembly is a human-readable representation of instructions and operands. Machine code is the byte stream decoded by the processor. There is no guaranteed one-to-one mapping between a source line and an instruction.

The general-purpose registers

x86-64 exposes sixteen 64-bit general-purpose registers: rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, and r8 through r15. rip tracks instruction execution. Some uses are architectural—stack instructions implicitly use rsp—while argument registers are selected by the platform ABI.

RegisterCommon role
raxInteger return value and temporary arithmetic
rcx, rdxGeneral values, shift count, or ABI arguments
rsi, rdiGeneral values, string operations, or System V arguments
rbpGeneral value or optional frame pointer
rspCurrent top of the stack
r8–r15Additional x86-64 general-purpose registers

Partial names share one storage location

rax, eax, ax, and al name overlapping parts of the same register.

NamePortion accessed
raxAll 64 bits
eaxLow 32 bits
axLow 16 bits
alLow 8 bits
ahBits 8–15, the legacy high-byte name

The critical rule is that writing a 32-bit general-purpose register in 64-bit mode clears the upper 32 bits.

mov rax, 0FFFFFFFFFFFFFFFFh
mov eax, 7
; RAX = 0000000000000007h

An 8- or 16-bit write leaves the remaining bits unchanged.

mov rax, 1122334455667788h
mov al, 0AAh
; RAX = 11223344556677AAh

That is why a compiler may zero eax before using setcc al. The legacy names ah, bh, ch, and dh cannot be encoded in an instruction that needs a REX prefix; avoiding them usually makes new code simpler.

Operand width is part of the operation

mov al,  byte ptr  [rcx]    ; 1 byte
mov ax,  word ptr  [rcx]    ; 2 bytes
mov eax, dword ptr [rcx]    ; 4 bytes
mov rax, qword ptr [rcx]    ; 8 bytes

The same address is used, but the access size differs. An incorrect width can read or overwrite an adjacent field, cross a page boundary, or violate a structure-layout contract. When the width is not implied, state it explicitly:

mov dword ptr [rcx], 1

mov copies; it does not consume the source

mov rax, rbx
mov rax, qword ptr [rcx]
mov qword ptr [rcx], rax
mov eax, 42

A normal mov does not clear its source. Most general mov forms also cannot copy directly from one memory operand to another.

; mov [rdi], [rsi]     ; not a general MOV form
mov rax, [rsi]
mov [rdi], rax

Do not turn this into a universal claim that x86 instructions can touch only one memory location. String and specialized instructions exist, and read-modify-write operations have their own forms. Check the instruction reference.

Extension reveals signedness

The byte 0xFF can mean unsigned 255 or signed -1.

movzx eax, byte ptr [rcx]   ; 000000FFh, 255
movsx eax, byte ptr [rcx]   ; FFFFFFFFh, -1 in 32 bits
movsxd rax, eax             ; sign-extend a signed 32-bit value to 64 bits

movzx fills new upper bits with zero; movsx copies the original sign bit. Assembly does not retain C type names, so extension, comparison, and branching patterns are evidence of signedness, not perfect source reconstruction.

lea calculates an address without reading memory

lea rax, [rcx + rdx*4 + 16]

rax receives the effective address. The memory stored at that address is not read.

lea rax, [rcx + 8]          ; rax = rcx + 8
mov rax, [rcx + 8]          ; rax = the eight-byte value stored there

Compilers also use lea for flag-preserving integer arithmetic.

lea eax, [rcx + rcx*2]      ; eax = rcx * 3

The brackets borrow the addressing grammar; they do not force a memory access in lea.

Two-operand arithmetic overwrites its destination

add rax, rdx     ; rax = rax + rdx
sub rcx, 8       ; rcx = rcx - 8
and eax, 0FFh    ; eax = eax & 0xFF
xor edx, edx     ; edx = 0, and therefore all of rdx is zero

These operations also update condition flags. lea normally does not. When a later jcc, setcc, or cmovcc appears, find the instruction that actually produced the flags it consumes.

Do not mix Intel and AT&T syntax

; Intel: destination first
mov rax, qword ptr [rbx + 8]

# AT&T: source first, % on registers, size suffix
movq 8(%rbx), %rax

GDB, objdump, GCC, and Clang can display either syntax. The CPU operation is the same, but reversing the operands reverses the data flow, so identify the syntax before reading a listing.

x86-64 instructions have variable length

An x86 instruction can occupy from one to fifteen bytes.

48 89 D8                mov rax, rbx
B8 2A 00 00 00          mov eax, 42
48 8B 44 D1 10          mov rax, [rcx + rdx*8 + 16]

An encoding may contain legacy, REX, VEX, or EVEX prefixes; an opcode; ModR/M; SIB; a displacement; and an immediate. You do not need to memorize the bytes. The structure matters when checking instruction boundaries, relocations, patches, or a disassembler that started one byte late.

Trace a complete tiny function

One possible compilation of int transform(int x) { return x * 3 + 5; } under the System V ABI is:

transform:
    lea eax, [rdi + rdi*2]
    add eax, 5
    ret

edi contains the input and eax carries the integer return value. lea computes three times the input, add adds five, and the write to eax clears the upper half of rax. Do not assume that hardware wraparound proves equivalent C semantics for every input: signed C overflow is undefined behavior.

A practical reading checklist

For each instruction, ask:

  1. Is this Intel or AT&T syntax?
  2. What is the operand width?
  3. Which state is read and which state is overwritten?
  4. Does a 32-bit write clear an upper half?
  5. How is each effective address calculated?
  6. Does the instruction produce or consume flags?
  7. Does the ABI assign an argument, return, or preservation role?

Common mistakes

  • Treating eax and rax as independent registers.
  • Assuming mov al, 1 makes all of rax equal to one.
  • Believing lea dereferences its bracketed operand.
  • Reading mov like C++ move semantics that empties a source object.
  • Inferring exact source types and intent from disassembly alone.

Exercise

Compile these functions with GCC or Clang using -O2 -S -masm=intel, or with MSVC using /O2 /FAs.

#include <stdint.h>

uint64_t widen_u8(const uint8_t* p) { return *p; }
int64_t  widen_i8(const int8_t* p)  { return *p; }
uintptr_t offset(const int* p, size_t i) {
    return (uintptr_t)&p[i];
}

Find the zero/sign extension difference, determine whether offset reads memory or only computes an address, and note whether the return path writes eax or rax.

Summary

Reading x86-64 begins with state transitions. Partial registers overlap, a 32-bit write clears the upper half, mov copies, extension exposes integer interpretation, and lea computes an effective address without loading from it. The next article applies this model to arrays, structures, stack movement, and function frames.

Official references