Anatomy of an Assembly file
Assembly is a low-level language that translates directly to machine code, specific to a CPU architecture (e.g., x86, ARM).
File extensions: .asm, .s
Every part of the example below is labelled and explained. This page is one of 55 annotated tours on AnatomyOf, a free, open-source project by LunarWerx Studios.
What is inside an Assembly file
Comment
Starts with ; (or # in some syntaxes), ignored by the assembler.
Everything from a ; to the end of the line is discarded before assembly begins, same idea as # in Python or // in C. Because assembly gives you no function names, no types, and no structure beyond labels, comments are doing far more load-bearing work here than in higher-level languages -- they are often the only record of *why* a register holds what it holds. Different assemblers vary: NASM and MASM use ;, while GAS (the GNU assembler, AT&T syntax) uses # for line comments and also supports C-style /* */ block comments. The transcribed examples below use NASM syntax throughout.
Section directive
Defines a section for data or code.
A directive like section .data or section .text is not a CPU instruction -- it is an instruction to the *assembler*, telling it which segment of the resulting object file the following bytes belong to. .data holds initialized variables, .bss holds uninitialized (zero-filled) storage, and .text holds the actual executable instructions. The linker later maps each section to memory with different permissions: .text is typically read-only and executable, while .data and .bss are readable and writable but not executable. Mixing code and data in the same section is how decades of buffer-overflow exp
Data definition
Allocates memory and initializes data.
Directives like db (define byte), dw (define word), dd (define doubleword), and dq (define quadword) reserve storage and give it an initial value. msg db 'Hello, World!', 0xA lays out the ASCII bytes of the string followed by a single newline byte (0xA) -- assembly has no built-in string type, just contiguous bytes you agree to interpret as text. equ defines a compile-time constant rather than allocating memory: len equ $ - msg uses $, the assembler's "current address" symbol, to compute the string's length by subtraction. That length is baked into the binary at assemble time, not calculated a
Global directive
Makes a label accessible outside the file.
By default a label is only visible within the file (translation unit) that defines it. The global directive (spelled .globl in GAS) exports a symbol so the linker can find it from other object files -- most commonly global _start, which tells the linker where execution should begin. The complementary directive is extern, which declares that a symbol is defined elsewhere and should be resolved at link time. Together they let you split a program across multiple assembled files, the same job export and import do in higher-level languages.
Label
A symbolic name for a memory address.
A label like _start: is just a name bound to "whatever address comes next" -- the assembler resolves it to a concrete offset when it builds the binary. Labels are how assembly fakes functions, loops, and jump targets, since the CPU itself has no concept of any of those, only addresses to jump to. _start is conventionally the true entry point the linker wires up (as opposed to main, which the C runtime calls only after its own startup code runs). Jumping to a label with jmp, call, or a conditional jump like jne is how assembly builds every control-flow structure other languages give you for fre
Instruction (Mnemonic)
An operation for the CPU to perform.
A mnemonic like mov, add, or int is a human-readable stand-in for a raw opcode byte the CPU actually decodes -- mov eax, 4 might assemble to b8 04 00 00 00. The assembler's whole job is this one-to-one (or occasionally one-to-few) translation from mnemonic-plus-operands to machine code, which is exactly why assembly is considered the thinnest possible layer over "what the chip really does." Instruction sets are architecture-specific. x86 and ARM may use a few familiar English-like mnemonic names, but their encodings, registers, operand rules, and calling conventions differ; code assembled for
Operands
Arguments for an instruction (registers, memory, immediate values).
Most instructions take one or two operands specifying what to operate on: mov ebx, 1 moves the immediate value 1 into the ebx register, while mov ecx, msg moves the memory address of msg into ecx. NASM syntax (used here) writes destination first, then source -- mov dst, src -- which is the opposite order from AT&T/GAS syntax's mov src, dst. Operands can be immediates (literal constants), registers, or memory references (optionally with an offset, like [ebp+8]), and a single instruction's valid operand combinations are defined precisely by the CPU's instruction set architecture -- you cannot, f
Register
A small, fast storage location within the CPU.
Registers such as eax, ebx, ecx, and edx on x86 sit directly on the CPU die, so reading and writing them is dramatically faster than touching main memory -- there is no bus round-trip. There are only a handful of them (compare that to effectively unlimited variables in a high-level language), so a huge part of writing assembly is deciding which value gets to occupy which register at any given moment. Some registers carry conventional roles baked in by the calling convention or the instruction set itself: eax traditionally holds a function's return value and, on Linux x86, the system call numbe
System call
An instruction to request a service from the kernel.
User-space code cannot write to a file descriptor or exit a process directly -- those require the kernel, which runs in a more privileged CPU mode. int 0x80 is the classic 32-bit Linux mechanism for triggering that privilege transition: it raises a software interrupt, the kernel inspects eax for the system call number, reads the remaining arguments from ebx/ecx/edx, performs the operation, and returns control to the instruction after the int. Modern 64-bit Linux prefers the faster syscall instruction over int 0x80, and the calling convention changes accordingly (arguments move through rdi, rsi
Official Assembly site · All languages on AnatomyOf
The interactive tour needs JavaScript. Enable it to hover a callout and trace it into the code.