Part 4: x86-64 Addressing Modes, Dereferencing & Scale-Index-Base (SIB)
Lesson Overview & Architectural Focus
In assembly language, instructions compute, transfer data, and access memory using specific patterns known as addressing modes. Unlike RISC architectures that require separate load and store operations, x86-64 is a CISC architecture capable of calculating complex memory addresses directly inside instruction operands. In Part 4, we master immediate, register, and direct addressing, demystify pointer dereferencing with square brackets [...], break down RIP-relative addressing for Position-Independent Executables (PIE), and dissect the full hardware Scale-Index-Base (SIB) formula.
1. The Core Addressing Modes in x86-64
An operand in an x86-64 instruction can be specified in one of several fundamental ways:
| Addressing Mode | Assembly Syntax | Data Source | Execution Speed |
|---|---|---|---|
| Immediate | mov rax, 100 | Hardcoded literal embedded directly in instruction byte stream | Fastest (0 cycles) |
| Register | mov rbx, rax | Direct transfer from one CPU register to another | Fastest (< 1 cycle) |
| Direct (Absolute) | mov rax, [0x401000] | Direct 64-bit absolute memory pointer address | Memory Latency |
| Register Indirect | mov rax, [rbx] | Pointer dereference: reads memory address stored in RBX | Memory Latency |
| Base + Displacement | mov rax, [rbp - 16] | Stack frame locals and structure/object field member offsets | Memory Latency |
| Scale-Index-Base (SIB) | mov rax, [rdi + rcx*8 + 32] | Hardware-accelerated array and multidimensional matrix indexing | Memory Latency |
2. The Scale-Index-Base (SIB) Effective Address Formula
The most versatile memory addressing mode in x86-64 is the Scale-Index-Base (SIB) mode. The CPU hardware Address Generation Unit (AGU) calculates the physical Effective Address (EA) in a single clock cycle according to the formula:
Base & Index Registers
Any 64-bit general purpose register (RAX, RBX, RCX, RDX, RSI, RDI, RBP, R8-R15) can serve as the Base. Any GPR except RSP can serve as the Index (RSP is reserved as the stack pointer).
Scale Factors (1, 2, 4, 8)
The hardware multiplier (Scale) is restricted to powers of two matching standard primitive data types: 1 (bytes), 2 (16-bit words), 4 (32-bit dwords/floats), and 8 (64-bit qwords/pointers).
3. RIP-Relative Addressing for Position-Independent Code (PIC)
In 64-bit Linux, modern binaries are compiled as Position Independent Executables (PIE) to support Address Space Layout Randomization (ASLR). Absolute 64-bit memory addresses require 8-byte immediate encodings and break when sections load at randomized addresses.
To solve this, x86-64 introduced RIP-relative addressing: memory references calculate signed 32-bit displacements relative to the current Instruction Pointer (RIP). In NASM, this is expressed using the rel keyword or the default rel directive:
default rel ; Automatically enables RIP-relative addressing for all labels
section .data
table_base dq 0x10, 0x20, 0x30, 0x40
section .text
; Correct modern PIC pointer load:
lea rdi, [rel table_base] ; Resolves address offset relative to RIP
mov rax, [rdi + rcx*8] ; Access table element via SIB
4. MOV vs. LEA: The Pointer Arithmetic Secret
A common confusion for new assembly programmers is the distinction between MOV and LEA (Load Effective Address):
mov rax, [rbx + 16]: Dereferences memory. Fetches the 8-byte value stored at the memory locationrbx + 16and loads that value into RAX.lea rax, [rbx + 16]: Does NOT access memory! It calculates the math formula inside the brackets (rbx + 16) and stores the resulting numerical address into RAX.
Optimizing compilers frequently exploit LEA as an ultra-fast arithmetic shortcut to perform addition and multiplication by 2, 4, 8, or 9 in a single instruction without touching CPU status flags (e.g. lea rax, [rdi + rdi*8] computes $x imes 9$).
Summary & Next Step
Addressing modes unlock the ability to traverse complex data structures, arrays, and objects in memory. In Part 5, we master data allocation directives: DB, DW, DD, DQ, uninitialized memory reservations in .bss, compile-time constants with EQU, and NASM preprocessor directives.
Related reading
Part 11: Linux 64-Bit System Calls, NASM Macros & Dynamic Memory Allocation
Learn low-level systems programming: Linux 64-bit SYSCALL table, multi-argument NASM %macro directives, file I/O operations, and sys_mmap memory allocation.
By Md. Harun-Or-Roshid
Part 10: Stack Frames, Procedures & The System V AMD64 ABI in Assembly
Master assembly subroutines: CALL, RET, stack frames, RBP base pointers, 16-byte stack alignment, and the official System V AMD64 ABI calling convention.
By Md. Harun-Or-Roshid
Part 9: Assembly Arrays, String Primitives & Hardware Streaming (REP MOVSB)
Master memory buffers and strings in x86-64 assembly: arrays, strides, and hardware block primitives including MOVSB, LODSB, STOSB, SCASB, and REP.
By Md. Harun-Or-Roshid
Part 8: Conditional Branching, Jump Instructions & Fast Loops in Assembly
Learn how conditional jumps work in x86-64 assembly using CMP, TEST, JE, JNE, JG, JL, JA, JB, and write high-performance loops with branch prediction.
By Md. Harun-Or-Roshid