The CPU & Its Registers
The 16 drawers everything passes through
- › 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.
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:
| Register | Conventional role | Notes |
|---|---|---|
| rax | Accumulator; function return value | Results land here |
| rbx | Base; callee-saved | You must preserve it in functions |
| rcx | Counter; 4th argument | Loops, shifts |
| rdx | Data; 3rd argument | Often paired with rax |
| rsi | Source index; 2nd argument | String/memory source |
| rdi | Destination index; 1st argument | String/memory dest |
| rbp | Base pointer (frame pointer) | Anchors the stack frame (Day 14) |
| rsp | Stack pointer | Always points at the top of the stack (Day 11) |
| r8-r15 | General purpose | r8/r9 are the 5th/6th args |
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:
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)| Name | Width | Which bits of rax |
|---|---|---|
| rax | 64-bit | bits 0-63 (all of it) |
| eax | 32-bit | bits 0-31 (low half) |
| ax | 16-bit | bits 0-15 |
| al | 8-bit | bits 0-7 (lowest byte) |
| ah | 8-bit | bits 8-15 (second byte) |
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.
1section .text2global _start3_start:4 mov rax, 205 ; put 205 (0xCD) into the full 64-bit rax5 mov rbx, 0xFF ; put 255 into rbx6 mov ecx, 1 ; write ECX -> also zeroes the top 32 bits of rcx7 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 510 mov rax, 60 ; syscall number 60 = exit11 mov rdi, 0 ; exit code 012 syscallWhat 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.
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.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.
- › 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)