Skip to content
Memory and Strings
step 1/5

Reading — step 1 of 5

Learn

~2 min readControl Flow and Memory

Assembly works with memory directly. Pointers are addresses; arrays are sequential memory.

Memory addressing modes

mov rax, [rbx]              ; load 8 bytes from address rbx
mov rax, [rbx + 8]          ; address rbx + 8
mov rax, [rbx + rcx*4]      ; rbx + rcx*4 — array indexing
mov rax, [rbx + rcx*4 + 8]  ; full form: base + index*scale + disp

The scale must be 1, 2, 4, or 8 (matches type sizes).

Loading a string byte-by-byte

section .data
    msg db "hello", 0

section .text
_start:
    lea rsi, [msg]          ; load address of msg into rsi
    xor rcx, rcx            ; index = 0
.loop:
    mov al, [rsi + rcx]     ; load one byte
    test al, al
    jz .done                ; null terminator — done
    ; ... process al ...
    inc rcx
    jmp .loop
.done:

lea rsi, [msg] is Load Effective Address — gets the address without dereferencing. Same as mov rsi, msg for static data.

Sizes

  • byte (8 bits) — al, bl, etc.
  • word (16 bits) — ax, bx
  • dword (32 bits) — eax, ebx
  • qword (64 bits) — rax, rbx

When writing memory, specify the size if ambiguous:

mov byte [counter], 0       ; 1 byte
mov word [counter], 100      ; 2 bytes
mov dword [counter], 1000   ; 4 bytes
mov qword [counter], 10000  ; 8 bytes

String operations

x86 has dedicated string instructions:

  • movs (move string)
  • cmps (compare strings)
  • scas (scan string)
  • lods (load string)
  • stos (store string)

Use with rep prefix for multi-byte ops. Plus rep movsb etc. Modern compilers usually inline manually for performance.

Counting bytes example

section .data
    msg db "hello world", 0

section .text
_start:
    lea rsi, [msg]
    xor rcx, rcx            ; counter
.loop:
    cmp byte [rsi + rcx], 0
    je .done
    inc rcx
    jmp .loop
.done:
    ; rcx now holds string length (excluding null)

bss vs data vs text

  • .text — code (executable)
  • .data — initialized data
  • .bss — uninitialized data (zero-initialized at startup)
section .data
    counter dq 42           ; 8 bytes, initialized to 42

section .bss
    buffer resb 1024        ; 1024 bytes, zeroed
    nums resq 100           ; 100 qwords (800 bytes)

Stack frame patterns

Functions use the stack for locals and saved registers:

my_func:
    push rbp
    mov rbp, rsp           ; save frame
    sub rsp, 32            ; allocate 32 bytes of locals
    
    mov [rbp-8], rdi       ; save first arg
    ; ... body ...
    
    mov rsp, rbp           ; deallocate
    pop rbp
    ret

This is the System V AMD64 ABI for function calls. Compilers emit this pattern automatically.

Why bare metal matters

Understanding how data lives in memory, how pointers really work, why i++ and ++i differ — knowing assembly demystifies all of that. Even if you never write production assembly, reading it helps debug optimized code, reverse-engineer binaries, and reason about CPU performance.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…