roadmap
week 1 · day 3

The CPU & Its Registers

The 16 drawers everything passes through

Easy 30 min 180 xp
After this you can
  • Describe the fetch-decode-execute cycle
  • Name the 16 general-purpose x86-64 registers and their roles
  • Explain the eax/rax naming and why writing eax zeroes the top half
  • Read a register dump and know what each value means

The CPU does one tiny thing, forever

A CPU (central processing unit) executes one loop for its entire life:

1. Fetch the next instruction from memory (the address is held in a special register). 2. Decode it, figure out what operation it is and what it operates on. 3. Execute it, do the add, the move, the jump. 4. Advance to the next instruction and repeat, billions of times per second.

That's it. There is no cleverness hiding underneath, just this cycle running absurdly fast. Everything you'll write in assembly is one instruction feeding this loop.

Key idea
The CPU is fast but it has almost no storage of its own. Its working memory is a tiny set of registers, think of them as ~16 drawers, each holding a single 64-bit value, that the CPU can read and write in a single clock tick. RAM is vastly larger but much slower. So the pattern of nearly all code is: load from RAM into a register, compute in registers, store the result back to RAM. Registers are where the actual work happens.

The 16 general-purpose registers

On x86-64 (the 64-bit Intel/AMD architecture your machine almost certainly runs), there are 16 general-purpose registers, each 64 bits (8 bytes) wide. They have historical names:

RegisterConventional roleNotes
raxAccumulator; function return valueResults land here
rbxBase; callee-savedYou must preserve it in functions
rcxCounter; 4th argumentLoops, shifts
rdxData; 3rd argumentOften paired with rax
rsiSource index; 2nd argumentString/memory source
rdiDestination index; 1st argumentString/memory dest
rbpBase pointer (frame pointer)Anchors the stack frame (Day 14)
rspStack pointerAlways points at the top of the stack (Day 11)
r8-r15General purposer8/r9 are the 5th/6th args
Note
Two of these are special by hard rule, not just convention: rsp always points at the top of the stack and rip (the instruction pointer, a 17th register) always points at the next instruction to fetch. You rarely touch rsp directly and you *never* write rip by hand, jumps and calls move it for you. The other 14 are yours to compute with.

rax, eax, ax, al, same register, different widths

Here's a detail that trips up everyone at first. Each 64-bit register can be accessed at four widths, because of backward compatibility going all the way back to the 16-bit 8086. Using rax as the example:

The layout of rax (64 bits)
 63 31 15 7 0
 +-------------------------------+---------------+------+------+
 | | | | |
 | (top 32 bits) | ax half | ah | al |
 +-------------------------------+---------------+------+------+
 |<--------------- rax (64-bit) ---------------->|
 |<-- eax (32) ->|
 |<-ax->|
 | ah | al | (8-bit halves of ax)
NameWidthWhich bits of rax
rax64-bitbits 0-63 (all of it)
eax32-bitbits 0-31 (low half)
ax16-bitbits 0-15
al8-bitbits 0-7 (lowest byte)
ah8-bitbits 8-15 (second byte)
Watch out
The zero-extension gotcha. Writing a 32-bit register (eax) automatically zeroes the top 32 bits of the full rax. But writing an 8- or 16-bit part (al, ax) leaves the upper bits unchanged. So mov eax, 5 makes rax exactly 5, while mov al, 5 only changes the lowest byte and can leave garbage above it. This asymmetry is a real and common source of bugs when you start reading disassembly, file it away now.

Seeing registers for real

You don't have to take this on faith, you can watch registers hold values. Here's a scrap of assembly that just puts numbers into registers. We'll write and run real programs on Day 5; today the goal is only to recognize the shapes.

registers.asm (Intel syntax), loading valuesasm
1section .text
2global _start
3_start:
4 mov rax, 205 ; put 205 (0xCD) into the full 64-bit rax
5 mov rbx, 0xFF ; put 255 into rbx
6 mov ecx, 1 ; write ECX -> also zeroes the top 32 bits of rcx
7 mov dl, 0x41 ; write only DL (low byte of rdx) = 65 = 'A'
8
9 ; exit(rax's low byte) via the kernel, details on Day 5
10 mov rax, 60 ; syscall number 60 = exit
11 mov rdi, 0 ; exit code 0
12 syscall

What each line means

`mov rax, 205`, mov dest, src copies src into dest. (Intel syntax always writes the destination first, remember it as dest = src.) After this line, all 64 bits of rax hold the value 205.

`mov rbx, 0xFF`, same idea; rbx now holds 255. Note we can write the value in hex or decimal freely, yesterday's lesson in action.

`mov ecx, 1`, we wrote the *32-bit* name ecx. rcx's low 32 bits become 1 and, per the gotcha above, the top 32 bits are forced to 0. So rcx = 1 exactly.

`mov dl, 0x41`, we wrote only dl, the lowest byte of rdx. Just that byte becomes 0x41 (65, the ASCII code for 'A'); the other 7 bytes of rdx are untouched.

The last three lines ask the operating system to end the program, you'll understand them fully on Day 5. For now, notice that even "exit" is just moving numbers into registers and firing one instruction.

Note
Intel vs AT&T syntax. You'll see two assembly notations in the wild. Intel (mov rax, 205, destination first, no sigils) is what we use, it's cleaner for learning. AT&T (movq $205, %rax, source first, % on registers, $ on constants) is GCC/gdb's default output. They describe the same instructions. We'll teach you to read both, but write Intel.
finished reading?
Your task, you write the code

Map the registers from memory

No compiler today, this is a recall drill, because you'll use these names every day for the next month. On a single sheet (physical or a text file registers.txt), (1) draw the rax box and label where al, ah, ax, eax, and rax live, (2) list all 16 general-purpose registers with their conventional role in one phrase each, and (3) write one sentence explaining why `mov eax, 0` sets all of rax to zero but `mov al, 0` might not.

deliverable: registers.txt (or a photo of your handwritten sheet)
self-review before running
  • All 16 registers listed with a role each
  • The al/ah/ax/eax/rax nesting drawn correctly
  • Your zero-extension sentence is correct and in your own words
  • You can recite rdi, rsi, rdx as the 1st/2nd/3rd argument registers (you'll need this on Day 13)
stretchLook up what 'callee-saved' vs 'caller-saved' means for rbx, rbp, r12-r15 versus rax, rcx, rdx, rsi, rdi, r8-r11. Write two sentences. Don't worry if it's fuzzy. Day 13 makes it concrete.

Self-check

01The three steps the CPU repeats forever are:
02How wide is a general-purpose register on x86-64?
03After `mov eax, 7`, what is in the full rax?
04Which register always points at the next instruction to be fetched?
0/4 correct · 0/4 checked