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
Addressing Modes¶
An addressing mode specifies how an operand's effective address is computed.
1. Immediate — Constant Value¶
The value is encoded directly in the instruction bytes.
2. Register — Register Value¶
3. Direct Memory — Label/Address¶
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:
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 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 addressmov rax, label= put the address in rax (like&varin C)mov rax, [label]= read the value at that address (likevarin 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