100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

The Assembly Toolchain (Assembler, Linker)

How assembly source files travel from text to a runnable executable via an assembler, object files, and a linker.

FoundationsIntermediate9 min readJul 10, 2026
Analogies

The Assembly Toolchain

Turning a hand-written .asm file into a program the operating system can actually run requires two distinct tools working in sequence: an assembler and a linker. The assembler (such as NASM, YASM, or GNU as) reads assembly source line by line and translates each mnemonic and operand into its exact machine-code encoding, producing an object file — on Linux this is typically an ELF (Executable and Linkable Format) .o file, containing machine code plus metadata like symbol tables and relocation information, but crucially not yet a runnable program. The linker (such as GNU ld or lld) then takes one or more object files, resolves references between them (for example, a call to a function defined in a different object file or a C library), fixes up addresses via the relocation entries the assembler left behind, and produces a final executable file the OS loader can run. This two-stage split exists so that large programs can be assembled file by file — only the files that changed need reassembling — and then relinked, which is far faster than reprocessing an entire codebase from scratch on every build.

🏏

Cricket analogy: The assembler-then-linker pipeline is like each net-bowler recording their individual practice session (assembling one file), and then the head coach later stitching all those sessions into the day's full training report (linking) — only the sessions that actually happened need to be re-recorded if plans change.

The Assembler's Job

An assembler performs several concrete jobs beyond a simple mnemonic-to-opcode lookup. It resolves labels — symbolic names for addresses, such as a loop: label — by computing the actual offset those labels correspond to within the file, so a jmp loop instruction can be encoded correctly even though loop appears later in the source. It also handles directives, which are instructions to the assembler itself rather than to the CPU: section .data declares a data segment, dd 42 reserves and initializes a 4-byte value, and global _start exports a symbol so the linker can see it from outside this file. For any symbol referenced but not defined in the current file — such as a call to printf from the C standard library — the assembler cannot resolve the address yet; instead it emits a relocation entry into the object file, essentially a note saying 'fix up this location once you know where printf actually lives,' which is exactly the job the linker performs next.

🏏

Cricket analogy: Label resolution is like a scorer noting 'over 14' as a bookmark before the over is even bowled, then filling in the exact ball count once play catches up — the assembler similarly computes a label's real offset once it has scanned the whole file.

The Linker and Object Files

A single object file is not runnable on its own — it typically has undefined external symbols (like printf or a function defined in another .o file) and no fixed load-time layout. The linker's core job is symbol resolution: it scans every object file and library passed to it, builds a table matching each undefined symbol to the file that actually defines it, and errors out with an 'undefined reference' message if any symbol is never found — one of the most common errors newcomers hit when forgetting to link against a required library. After resolving symbols, the linker performs relocation: it decides the final memory layout of the combined program (which section goes at which address) and patches every instruction that referenced a symbol with that symbol's now-known final address, exactly fulfilling the placeholder notes the assembler left behind. Static linking copies the actual code of library functions into the final executable, producing a larger but self-contained binary, while dynamic linking instead records a reference to a shared library (like libc.so) to be resolved by the OS loader at program startup, producing a smaller executable that depends on that shared library being present at runtime.

🏏

Cricket analogy: Symbol resolution is like a tournament organizer matching every team's 'opponent TBD' fixture slot to an actual confirmed team before finalizing the schedule — an unmatched fixture is exactly like the linker's 'undefined reference' error.

bash
# Typical NASM + ld toolchain on Linux x86-64

# 1. Assemble: translate hello.asm into an ELF object file
nasm -f elf64 hello.asm -o hello.o

# 2. Link: resolve symbols and produce a runnable executable
ld hello.o -o hello

# 3. Run it
./hello
echo "Exit code: $?"

# If hello.asm calls a libc function like printf, link dynamically instead:
# nasm -f elf64 hello.asm -o hello.o
# gcc hello.o -o hello        # gcc invokes ld with libc linked in automatically

An 'undefined reference to symbol_name' error happens at the linking stage, not the assembling stage — it means the assembler successfully produced an object file, but the linker could never find a definition for a symbol you referenced. Check that you spelled the symbol correctly, declared it extern if defined elsewhere, and passed every required object file or library on the linker command line.

  • The assembler translates .asm source into an object file (.o) containing machine code, symbol tables, and relocation entries.
  • Assembler directives (section, global, dd) instruct the assembler itself, not the CPU.
  • Unresolved external symbols become relocation entries the assembler leaves for the linker to fix.
  • The linker resolves symbols across all object files/libraries and reports 'undefined reference' errors when a symbol is never found.
  • The linker performs relocation, assigning final memory addresses and patching every instruction that referenced them.
  • Static linking embeds library code directly in the executable; dynamic linking defers resolution to a shared library at runtime.
  • Common tools: NASM/GNU as for assembling, GNU ld/lld for linking, often invoked indirectly through gcc/clang.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#AssemblyLanguageStudyNotes#TheAssemblyToolchainAssemblerLinker#Assembly#Toolchain#Assembler#Linker#StudyNotes#SkillVeris#ExamPrep