Assembly Source, Object Files, Linking & Debugging
Assembly source is not an executable: the assembler encodes sections and unresolved references into an object file, the linker lays out and relocates those objects, and debug metadata lets tools reconnect machine addresses to symbols and source.
Assembly source names instructions, labels, constants, sections, and external symbols. A CPU can execute none of those names. The toolchain must turn them into bytes at assigned addresses while retaining enough structure to resolve cross-file references and, when requested, explain those bytes to a debugger.
The artifact pipeline #
| Artifact | What is fixed | What may remain unresolved |
|---|---|---|
source (.asm, .s) |
symbolic intent, labels, directives | instruction bytes, addresses, external definitions |
object (.o, .obj) |
encoded bytes, section-relative offsets, local symbols | final section addresses, external symbols, relocations |
archive (.a, .lib) |
indexed collection of objects | which members will be extracted |
| executable or shared object | section/segment layout, resolved references allowed by the link model, entry metadata | dynamic imports and load address when applicable |
| process image | mapped pages, runtime relocations, initialized registers/stack | future control flow and mutable state |
The stages are not interchangeable. A successful assembly proves that each source file is syntactically encodable. It does not prove that every external symbol exists, that sections fit the target memory map, or that execution begins at the intended entry point.
What the assembler emits #
For a relocatable format, the assembler normally emits:
- sections such as executable code, initialized data, and zero-initialized storage;
- symbols with a name, binding, section, and section-relative value;
- relocations describing fields whose final numeric value depends on link placement or another symbol;
- debug information mapping instructions and variables back to source when enabled;
- encoded instruction and data bytes whose values are already known.
GNU as describes an address as a section plus an offset. If an instruction refers to an external symbol, the assembler cannot invent its final address; it places a relocation record beside a provisional field. NASM similarly chooses output behavior from the requested format: a flat binary contains bytes only, while ELF, COFF, Mach-O, or OMF can carry sections, symbols, relocations, and supported debug formats.
What the linker decides #
The linker combines object files and selected archive members, resolves symbol references, assigns addresses to output sections, applies link-time relocations, and writes an executable, shared library, firmware image, or another relocatable object.
Order matters for archive extraction because a traditional linker pulls a member when it satisfies an unresolved reference already known at that point. A linker script or platform default also decides the memory layout, alignment, entry symbol, permissions, and which input sections are retained or discarded.
Undefined-symbol errors are therefore not assembler errors. Duplicate definitions, incompatible object formats, relocation overflow, a missing entry point, or a section that exceeds a firmware region belong to link layout and symbol resolution.
Flat binary versus relocatable object #
A flat binary is already a byte image for an assumed origin. It has no standard symbol table or relocation records for another linker to consume. It is useful when the consumer defines an exact layout: a boot sector, ROM region, DOS .COM image, or embedded payload.
A relocatable object preserves uncertainty explicitly. Sections can move, symbols can be supplied by another file, and debug data can survive into the final image. OMF was common in 16-bit DOS toolchains; ELF dominates many Unix-like systems; COFF/PE and Mach-O serve Windows and Apple platforms. The semantic stages remain the same even though container fields differ.
NASM still documents both 16-bit DOS .EXE/.COM production and current ELF, COFF, Mach-O, and OMF output. That makes old TASM/TLINK-style exercises historically recognizable without making those tools the durable topic.
A reproducible modern pass #
global add_two
section .text
add_two:
mov eax, edi
add eax, esi
retnasm -f elf64 -g -F dwarf add.asm -o add.o
readelf -SWrs add.o
objdump -drwC -Mintel add.o
ld -r add.o -o combined.oThe useful inspection order is deliberate:
readelf -Sshows sections and their attributes.readelf -sshows which symbols are defined, local, global, or undefined.readelf -rorobjdump -rshows unresolved address patches.objdump -dconnects encoded bytes to disassembly.- A linker map shows final placement and why an output section has its size.
ld -r performs a partial link and deliberately keeps a relocatable result. A normal final link also needs a valid platform entry and runtime environment; this isolated function is not a complete process by itself.
Debugging reconnects several views #
A debugger coordinates at least four representations:
- machine registers and memory;
- instruction addresses and disassembly;
- symbols and stack-unwind rules;
- source lines, variables, and types from debug information.
Stripping debug sections does not remove executable instructions, but it removes much of that correspondence. Separate debug files preserve it outside the deployed binary and can be matched by a debug link or build ID. Optimized code complicates the mapping even with perfect metadata: instructions may be reordered, variables may live briefly in registers or disappear, and one source line may map to several noncontiguous ranges.
Failure localization #
| Evidence | Likely failing stage |
|---|---|
| syntax error or invalid operand combination | assembler/parser or ISA encoding |
| undefined or multiply defined symbol | linker symbol resolution |
| relocation truncated or section overlaps region | linker layout/model |
| correct file on disk, wrong bytes in memory | loader, deployment, mapping, or self-modifying runtime |
| breakpoint moves or variable is unavailable | optimization/debug mapping, not necessarily wrong execution |
| disassembly starts with nonsense | wrong architecture/mode, wrong start address, or data decoded as code |
The fastest diagnosis names the artifact and stage: “foo.o contains an unresolved PC-relative relocation” is actionable; “assembly failed” is not.