Free lesson — no signup, everything runs in your browser

Registers & your first instructions

In the next ten minutes you'll learn what a register actually is, execute real x86-64 instructions one at a time, and write your first assembly program — checked live as you go.

1.1What a register actually is

First, thirty seconds on what you're actually learning. Your CPU doesn't run C or Python — it runs machine code, raw bytes like 48 31 c0. Assembly is nothing more than a human-readable spelling of those exact bytes: 48 31 c0 is xor rax, rax, one-to-one. That's why assembly is worth learning — it isn't another language sitting on top of the machine. It's the machine, with names.

Strip away everything else and a CPU is a machine that does one thing: it reads an instruction, changes some numbers, and moves to the next instruction. The numbers it changes live in registers — a small set of storage slots built directly into the processor. Each one holds a single 64-bit value. That's it. Not a variable, not an object — a box with 64 bits in it.

x86-64 gives you sixteen general-purpose registers. The four you'll use constantly are rax, rbx, rcx, and rdx. They're almost interchangeable, though conventions give them habits — rax tends to hold results, rcx tends to count.

Here's the part text tutorials always fumble: registers nest. The same physical box answers to different names depending on how much of it you want. Click the names below and watch which part of the 64-bit register lights up:

bit 63bit 0

Same register, five names. Old 8-bit code from 1978 wrote to al; your 64-bit code writes to rax; both touch the same silicon. This is why x86 is called backwards-compatible — and why its register names look weird.

So where does everything else live? Sixteen registers is 128 bytes of storage — your program's data obviously doesn't fit there. Everything else lives in memory (RAM): billions of numbered byte-sized slots the CPU reaches by address. The trade is speed. A register is read in well under a nanosecond; a trip to RAM can cost a hundred times that. So the entire game of assembly — and of the compilers you'll later reverse-engineer — is: pull data from memory into registers, do the real work there, write results back. This lesson stays inside the registers, where the work happens. Memory addressing gets its own lesson once the instructions feel natural.

1.2mov — your first instruction

mov dest, src copies a value into a register. The value can be a number you write directly (an immediate) or the contents of another register. Note the direction: data flows right to left, like an assignment. mov rax, 5 means rax = 5.

Step through this. Watch the register panel — values that just changed flash green:

Three instructions, one value flowing through three registers. Notice mov copies — after line 2, rax still holds 0x2a. Nothing "moves out."

1.3add and sub — arithmetic

add and sub work on the destination in place: add rax, 50 means rax = rax + 50. Unlike mov, arithmetic instructions also update the CPU's flags — single-bit facts about the last result. You can see one below: ZF, the zero flag, which turns 1 whenever a result equals zero. Flags are how assembly makes decisions, and they're the whole subject of lesson 2. For now, just watch it.

Quick check. Read this program and predict the final value of raxbefore running anything. This is the actual skill you're building: executing code in your head.

mov  rax, 8
add  rax, rax
sub  rax, 6
inc  rax

1.4xor — and your first idiom

xor is bitwise exclusive-or. Useful on its own, but you'll see one specific line at the top of nearly every real function you ever disassemble:

xor  rax, rax

Any value xor'd with itself is zero — so this zeroes rax. Why not mov rax, 0? Same result, but the xor encodes into fewer bytes of machine code, so compilers always pick it. When you can read that line and think "oh, that's just = 0," you've started reading assembly like a native. Prove both work:

1.5Now you write it

Below is a live machine with an empty program. It assembles whatever you type and runs it for real. Instructions available: mov, add, sub, xor, inc (+1), dec (−1). Registers: rax rbx rcx rdx rsi rdi. Numbers can be decimal (42) or hex (0x2a). Comments start with ;.

Three goals. The machine checks them every time you run:

sandbox / write your ownready
[ ]Get the value 0x2a (decimal 42) into rax
[ ]Put the same non-zero value into rbx and rcx — using a register-to-register mov, not two immediates
[ ]Zero out rdx the compiler's way — with xor, not mov
[ ]Stretch: get 42 into rax again — without typing a single number. Registers only. (Hint: add rax, rax doubles. 42 = 32 + 8 + 2.)
> all three. you just wrote x86-64 assembly. welcome to ring 3.
> ★ stretch complete — building constants from doubling and adding is exactly how compilers multiply. You reinvented shift-and-add.

Stuck on goal 2? Load a value into rbx first, then copy it: mov rcx, rbx. That's how data actually travels between registers.

Subtracted below zero and got 0xffffffffffffffff? Not a bug — registers hold raw 64-bit patterns with no minus sign, so values wrap around. That pattern is how the machine represents −1 (two's complement — it gets a proper explanation in a later lesson, and it's cooler than it sounds).