Assembly Introduction
What Is Assembly?
A processor does not read if, for, or printf. It reads numbers — a stream of bytes in memory where each byte is an instruction code. That byte stream is machine code, or native code, and it is the only thing the CPU can execute.
Machine code is unreadable by humans, so every processor family gives each instruction code a mnemonic — a short word. Byte 0xB8 becomes mov eax, .... A program written with those mnemonics is assembly language, and the tool that turns it into bytes is an assembler.
From Source to Machine Code
There are two different ways to reach machine code, and knowing which one you are looking at tells you a lot:
- Compiler — translates a high-level language (C, Rust, Go) to assembly automatically. The assembly is a build artifact you inspect but rarely edit.
- Assembler — translates the assembly you wrote into machine code. You choose the registers and the instructions.
Both paths meet at the same place, because a compiler is literally a program that emits assembly. Here is what a C compiler produces for a tiny function when you ask it for x86-64 output:
; C source: long add(long a, long b) { return a + b; }
; The compiler emits this assembly for x86-64 (System V ABI):
add:
lea rax, [rdi + rsi] ; rdi = a, rsi = b (first two integer arguments)
ret ; result is returned in rax
Notice how much the compiler decided for you: which registers hold the arguments, which register holds the result, and even that lea is a cheaper way to add than add. When you write assembly by hand, those decisions are yours — and so is the responsibility for getting them right.
Why Learn Assembly?
Almost nobody ships a large program written in assembly. People learn it for four concrete reasons, and every lesson in this track serves at least one of them:
- Performance intuition. Once you see that a cache miss costs hundreds of instructions, "optimize later" becomes a measurable habit instead of a slogan.
- Debugging. When a crash gives you a stack trace and a disassembly window, reading registers is the difference between guessing and knowing.
- Systems work. Bootloaders, interrupt handlers, context switches, and cryptography primitives are written in assembly because no compiler can be trusted with the exact instruction sequence.
- Security. Reverse engineering, exploit analysis, and understanding how malware hides all begin with reading machine code.
The Machine Model
Before writing a single instruction, build a mental picture of the machine. Almost every assembly question reduces to one of three parts: what the CPU is doing, what it remembers, and where it keeps everything.
The CPU: Fetch, Decode, Execute
The processor repeats one cycle forever, billions of times per second:
- Fetch — read the next instruction bytes from the address in the instruction pointer (
ripon x86-64,pcon ARM/RISC-V). - Decode — work out which operation those bytes mean and what operands it needs.
- Execute — perform the operation, writing a result back to a register or memory.
That is the whole machine. Everything else — pipelines, caches, out-of-order execution, branch prediction — exists only to make this cycle faster, and every one of them exists because the original three steps are wasteful.
Registers Are the CPU's Hands
A register is a tiny, extremely fast storage slot inside the CPU. x86-64 has sixteen general-purpose 64-bit registers; ARM64 has thirty-one. Registers are the only operands an instruction can touch directly — everything else must be loaded into a register first. A register read or write costs effectively nothing (a fraction of a nanosecond), which is why assembly code is obsessed with keeping values in registers rather than in memory.
mov rax, 5 ; put the number 5 into register rax
mov rbx, 7 ; put the number 7 into register rbx
add rax, rbx ; rax = rax + rbx -> rax now holds 12
mov rcx, rax ; copy the result into rcx
No memory is touched here. Four instructions, four register operations, done in a handful of clock cycles.
Memory, Addresses, and the Stack
Memory (RAM) is one enormous flat array of bytes. Each byte has an address, a number that starts at 0 and counts upward. When an instruction needs a value from memory, it must name that address — or name registers whose contents produce the address.
Two regions matter most in beginner programs:
- Static data — your constants, strings, and variables declared with
db/dd/dq. They live at fixed addresses known at assembly time. - The stack — a region that grows downward as the program runs. It stores return addresses, saved registers, and local storage. The stack pointer register (
rsp) always points to its current top.
Keep the picture simple: registers are fast and scarce, memory is slow and plentiful, and the stack is memory used with a strict discipline — push to save, pop to restore.
Assembly Dialects Around the World
Assembly is not one language. Every processor family has its own instruction set, its own register names, and usually its own assembler syntax. These are the four you will meet in this track:
| Family | Found in | Assembler | Character |
|---|---|---|---|
| x86-64 | Desktops, laptops, most servers | NASM, GNU as, MASM | CISC — large instruction set, variable-length instructions |
| ARM64 (AArch64) | Phones, tablets, Apple silicon, Raspberry Pi | GNU as, LLVM | RISC — fixed 32-bit instructions, load/store architecture |
| RISC-V | Embedded, education, open hardware | GNU as, LLVM | RISC — open standard, modular extensions |
| WebAssembly | Browsers, edge runtimes, plugins | wat2wasm, LLVM | Virtual stack machine — not a physical CPU |
RISC vs CISC, in One Paragraph
CISC (Complex Instruction Set Computer) gives you many powerful instructions, each doing several jobs at once, encoded in variable-length bytes. RISC (Reduced Instruction Set Computer) gives you a small set of simple instructions, each one word long, and expects you to combine them. x86-64 is CISC; ARM64 and RISC-V are RISC. The practical consequence for you as a programmer: on x86 you constantly ask "can this be done in one instruction?", while on ARM you ask "how do I sequence these simple steps?"
WebAssembly is a different animal — it is a portable bytecode designed to compile to, not a CPU. Its text format, WAT, looks like assembly but describes a stack machine: there are no named registers at all. It is included here because it is the dialect most web developers will actually read.
Two Syntaxes for One CPU
Here is the trap that catches every beginner: the same instruction can be written two ways. The x86-64 CPU has one machine code, but two competing textual spellings for it:
| Aspect | Intel syntax (NASM, MASM) | AT&T syntax (GNU as) |
|---|---|---|
| Operand order | dest, src | src, dest |
| Register prefix | none — rax | % — %rax |
| Immediate prefix | none — 10 | $ — $10 |
| Memory operand | [rbx + 8] | 8(%rbx) |
| Size suffix | mov qword [rbx], 1 | movq $1, (%rbx) |
| Comment | ; | # |
The same two instructions, side by side — identical bytes come out of the assembler:
; Intel / NASM — destination first, no prefixes
mov rax, [rbx + 8] ; load the 64-bit value at address (rbx + 8)
add rax, 10 ; add the constant 10
# AT&T / GNU as — source first, % registers, $ immediates
movq 8(%rbx), %rax # load the value at address (rbx + 8)
addq $10, %rax # add the constant 10
Which Syntax Should You Learn?
Learn Intel syntax with NASM first, which is what this track uses. It reads left to right like an assignment, it needs fewer punctuation marks, and the NASM manual is one of the clearest documents in low-level computing. AT&T syntax matters because that is what Linux kernel source, GCC's -S output, and most tutorials written before 2010 use — so you will need to recognize it, even if you rarely type it.
If you already use GDB, remember that GDB's default disassembly is AT&T. Switch it with set disassembly-flavor intel, and it will match everything on this site.
How This Track Is Organized
The lessons follow the order in which a machine actually makes decisions, so nothing is used before it is explained:
- Phase 1 — Fundamentals: what assembly is, the toolchain, and the number/memory model.
- Phase 2 — Registers & Data: the CPU's working storage, how bytes are declared, and how addresses are computed.
- Phase 3 — Instructions & Logic: the instruction set, arithmetic, and how to branch.
- Phase 4 — Program Structure: the stack, subroutines, and the assembler's macro system.
- Phase 5 — Systems & Flavors: talking to the operating system, debugging, and comparing dialects.
- Phase 6 — Practice: runnable demo programs and small projects.
- Phase 7 — Reference: assemblers, simulators, and manuals you can return to.
Common Pitfalls
"Assembly is portable"
It is not, and pretending otherwise is the fastest way to waste an afternoon. x86-64 assembly does not run on ARM, ARM assembly does not run on RISC-V, and even two x86 assemblers disagree about syntax. Portability comes from the high-level language above assembly, not from assembly itself.
"Assembly is only for geniuses"
Assembly is small, not hard. x86-64 has a large instruction set, but day-to-day programming uses perhaps thirty instructions: move, add, compare, branch, call, return. The difficulty is not complexity — it is the lack of safety nets that a compiler normally provides.
"Hand-written assembly is always faster"
Modern compilers are extremely good. They know instruction latencies, prefer SIMD, and reorder code around pipeline stalls. Hand-written assembly usually wins only in narrow, well-measured hotspots — and it loses badly when the author has not profiled first. Write assembly to understand the machine; keep it only when a measurement proves it helps.
Summary
- Machine code is the only language a CPU executes; assembly is its human-readable spelling.
- An assembler turns your mnemonics into machine code — you are the optimizer.
- Assembly is not portable: it is tied to one processor family and often to one operating system's calling convention.
- This track teaches x86-64 with NASM as the primary dialect, then shows the same ideas in ARM64, RISC-V, and WebAssembly.
Next: Toolchain & First Program — install the tools and assemble something that actually runs.