Skip to content

06 — Memory and Addressing Modes

Assembly gives you direct control over memory. Understanding sections, how data is declared, and the different ways to reference memory is essential for writing any non-trivial program.


Program Sections

Every NASM program is divided into sections (also called segments):

section .text    ; executable code (read-only at runtime)
section .data    ; initialized data (read-write)
section .bss     ; uninitialized data (zero-filled by OS)
section .rodata  ; read-only data (strings, constants)

.data — Initialized Data

section .data
    num     dq 42           ; define quadword (8 bytes) = 42
    pi      dd 3.14159      ; define dword float
    char    db 'A'          ; define byte = 65
    message db "Hello", 10  ; string with newline
    arr     dw 1, 2, 3, 4   ; array of 4 words (2 bytes each)

.bss — Uninitialized (Reserved) Data

section .bss
    buffer  resb 256    ; reserve 256 bytes
    count   resq 1      ; reserve 1 quadword (8 bytes)
    table   resd 100    ; reserve 100 doublewords

The OS zero-fills .bss at program load — you don't pay binary file space for it.

Data Definition Directives

Directive Size Bits C equivalent
db byte 8 char
dw word 16 short
dd doubleword 32 int, float
dq quadword 64 long, double
resb n n bytes char x[n]
resw n n words short x[n]
resd n n dwords int x[n]
resq n n qwords long x[n]

The $ and $$ Symbols

  • $ — current address (position of the current instruction/data)
  • $$ — start of the current section
message db "Hello, World!", 10
msg_len equ $ - message    ; = number of bytes in message

Addressing Modes

An addressing mode specifies how an operand's effective address is computed.

1. Immediate — Constant Value

mov rax, 42           ; rax = 42 (literal value)
mov rax, 0xFF         ; rax = 255
add rax, 8            ; rax += 8

The value is encoded directly in the instruction bytes.

2. Register — Register Value

mov rax, rbx          ; rax = rbx (copy register)
add rax, rcx          ; rax += rcx

3. Direct Memory — Label/Address

mov rax, [num]        ; rax = value at address `num`
mov [num], rax        ; store rax into address `num`

Brackets [ ] mean dereference — go to that address and read/write memory.

4. Register Indirect — Register as Pointer

mov rsi, num          ; rsi = address of `num` (pointer)
mov rax, [rsi]        ; rax = value at address in rsi
mov [rsi], rbx        ; write rbx to address in rsi

5. Base + Displacement

mov rax, [rsi + 8]    ; value 8 bytes after rsi
mov rax, [rbp - 8]    ; local variable at rbp minus 8
mov [rsi + 16], rcx   ; write to rsi+16

6. Base + Index (Scaled)

; General form: [base + index*scale + displacement]
; scale must be 1, 2, 4, or 8

mov rax, [rsi + rcx*8]       ; rax = arr[rcx]  (64-bit elements)
mov rax, [rsi + rcx*4]       ; rax = arr[rcx]  (32-bit elements)
mov rax, [rbx + rdi*8 + 16]  ; full form

This is how array indexing is compiled:

long arr[10];
long x = arr[i];      mov rax, [arr + rdi*8]

Memory Access Size Specifiers

When the size is ambiguous, use explicit specifiers:

mov byte  [rsi], 0x41    ; write 1 byte
mov word  [rsi], 0x4142  ; write 2 bytes
mov dword [rsi], 0       ; write 4 bytes
mov qword [rsi], rax     ; write 8 bytes

LEA — Load Effective Address

LEA computes an address without accessing memory — it puts the address itself into the destination.

lea rax, [rsi + rcx*8]    ; rax = rsi + rcx*8  (no memory read)
lea rdi, [msg]            ; rdi = address of msg

LEA vs MOV

; Assume rsi = 0x1000, rcx = 3

lea rax, [rsi + rcx*8]    ; rax = 0x1000 + 3*8 = 0x1018  (address)
mov rax, [rsi + rcx*8]    ; rax = *(0x1018)              (value at that address)

Use LEA to: - Get the address of a local variable: lea rdi, [rbp - 8] - Perform fast multiply/add: lea rax, [rax + rax*4] = rax * 5 - Load a string's address into a register


Stack Memory

The stack is a region of memory managed via RSP. It grows downward.

push rax       ; RSP -= 8; [RSP] = rax
pop  rbx       ; rbx = [RSP]; RSP += 8

; Equivalent manual operations:
sub rsp, 8     ;   (same as push rax)
mov [rsp], rax

mov rbx, [rsp] ;   (same as pop rbx)
add rsp, 8

Accessing local variables via RBP:

push rbp          ; save caller's frame pointer
mov  rbp, rsp     ; set our frame pointer
sub  rsp, 32      ; allocate 32 bytes of local storage

mov  qword [rbp - 8],  rax   ; local var 1
mov  qword [rbp - 16], rbx   ; local var 2

; ... function body ...

mov  rsp, rbp     ; deallocate locals
pop  rbp          ; restore caller's frame pointer
ret

RIP-Relative Addressing

In 64-bit position-independent code, data is often addressed relative to RIP:

; NASM generates this automatically for labels in .data/.rodata
lea rdi, [rel message]    ; RIP-relative load of address

This allows the binary to load at any address (ASLR).


Memory Access Patterns

section .data
    array  dq 10, 20, 30, 40, 50    ; array of 5 qwords

section .text
global _start

_start:
    lea rsi, [array]         ; rsi = base address of array

    mov rax, [rsi]           ; rax = array[0] = 10
    mov rax, [rsi + 8]       ; rax = array[1] = 20
    mov rax, [rsi + 16]      ; rax = array[2] = 30

    ; Index with register
    mov rcx, 3
    mov rax, [rsi + rcx*8]   ; rax = array[3] = 40

    ; Modify array element
    mov qword [rsi + 8], 99  ; array[1] = 99

Common Mistakes

; WRONG: cannot have two memory operands
mov [dest], [src]      ; error! use a register as intermediary

; CORRECT:
mov rax, [src]
mov [dest], rax

; WRONG: size mismatch
mov [byte_var], rax    ; writing 8 bytes to a 1-byte variable

; CORRECT:
mov [byte_var], al     ; write only the low byte

; WRONG: forgetting brackets
mov rax, msg           ; rax = address of msg (not the value!)
                       ; this is the same as: lea rax, [msg]
; CORRECT (to get the value):
mov rax, [msg]

Key Takeaways

  • [addr] = dereference: read/write at that address
  • mov rax, label = put the address in rax (like &var in C)
  • mov rax, [label] = read the value at that address (like var in C)
  • lea rax, [expr] = compute address without memory access
  • Addressing mode: [base + index*scale + disp] — scale must be 1/2/4/8
  • Use explicit size specifiers (byte, word, dword, qword) when needed

Next: 07 — Basic Instructions