Day 3 of 5
⏱ ~60 minutes
How CPUs Work in 5 Days — Day 3

Registers and Memory

Registers are the CPU's working memory — the fastest storage that exists. Today you'll understand what they are, how many exist, and how the CPU moves data between registers and RAM.

x86-64 Register File

x86-64 has 16 general-purpose 64-bit registers: RAX, RBX, RCX, RDX (general), RSP (stack pointer), RBP (base pointer), RSI, RDI (source/destination index), R8–R15 (added in x86-64). You can access lower-width slices: EAX (32-bit), AX (16-bit), AL (8-bit low), AH (8-bit high). Plus: RFLAGS (status flags), RIP (instruction pointer), 16 XMM/YMM/ZMM registers for SIMD.

Load and Store Instructions

The CPU can only do arithmetic on data that's in registers. To use RAM, you must first load it into a register, operate on it, then store the result back. MOV RAX, [address] — load 8 bytes from memory into RAX. MOV [address], RAX — store RAX to memory. Modern CPUs can have dozens of outstanding load/store operations in flight simultaneously (out-of-order memory operations).

The Stack

The stack is a region of RAM used for function call frames, local variables, and return addresses. RSP always points to the top. PUSH RBX decrements RSP by 8, stores RBX. POP RBX loads from [RSP], increments RSP. CALL pushes the return address (RIP+instruction_size) and jumps to the function. RET pops the return address and jumps back. Stack frames hold local variables and saved registers — the debugger reads these to show you a stack trace.

asm
; x86-64 registers and stack in action
; Compile: nasm -f elf64 regs.asm && ld regs.o && ./a.out

section .data
    msg db "Result: ", 10, 0

section .text
    global _start

; Function: adds two arguments, returns result in RAX
; Calling convention: args in RDI, RSI
add_two:
    push rbp           ; save caller's base pointer
    mov  rbp, rsp      ; set up our stack frame
    
    mov  rax, rdi      ; rax = first argument
    add  rax, rsi      ; rax = rax + second argument
    ; rax now holds return value
    
    pop  rbp           ; restore caller's base pointer  
    ret                ; pop return address → jump back

_start:
    ; Call add_two(40, 2)
    mov rdi, 40        ; first argument
    mov rsi, 2         ; second argument
    call add_two       ; push RIP+next, jump to add_two
    ; rax = 42
    
    ; Exit
    mov rdi, 0
    mov rax, 60
    syscall
💡
When you call a function in C, the compiler generates PUSH/CALL/POP/RET instructions automatically. A stack overflow is literally RSP going past the end of the stack segment — the OS signals SIGSEGV. Recursive functions that go too deep exhaust stack space.
📝 Day 3 Exercise
Trace a Function Call in Assembly
  1. Write a C function int add(int a, int b){return a+b;} and compile with gcc -O0 -S add.c
  2. Read the assembly output. Find the push rbp, mov rbp,rsp, and pop rbp — that's the stack frame setup/teardown.
  3. Find where arguments are loaded from the stack (or registers). In x86-64 Linux, first 6 args go in RDI, RSI, RDX, RCX, R8, R9.
  4. Write a recursive factorial function and compile it. Trace how the stack grows with each recursive call.
  5. Use gdb ./program and info registers to see live register values. Run bt to see the stack trace.

Day 3 Summary

  • x86-64 has 16 general-purpose 64-bit registers — the CPU can only compute on data in registers
  • Load (memory→register) and store (register→memory) are the only way to access RAM
  • The stack is RAM used for function frames; RSP tracks the top; CALL/RET manage return addresses
  • Every function call = PUSH saved registers + CALL (pushes return addr) + POP saved registers + RET
Challenge

Disassemble a function from a real program using objdump -d -M intel /usr/bin/ls | head -100. Find a function call, a branch, and a loop. Identify the CALL/RET pair, the Jcc instruction for the branch, and the backward jump for the loop. Write down what each instruction does.

Finished this lesson?