The World of Linkers/ Labs/ 16 articles
4 min readPublic

[The World of Linkers—Lab 12] Make Debug Information Follow Addresses

Add debug sections, symbols, and section headers to class10's static ET_EXEC and class11's static PIE. Preserve the executable layout while making source locations, symbols, and debug references readable by GDB, addr2line, and readelf. Theory 11 explains DWARF coordinates, tombstones, and debug-file organization.

Project: class12 code and tests; private-repository access is required. Retain the complete upstream layout, including merged-piece mappings and the TLS template. The supplied link and link_pie connect the ET_EXEC and static-PIE pipelines; the CLI selects PIE with --pie.

Implementation tasks

Complete five tasks in the class12 crate library. The class12 README specifies input types, field encodings, diagnostics, and composition requirements.

TaskInterfaceResult
C12.1collect_debugGroup .debug_* by name, retaining input order and each contribution's group offset
C12.2relocate_debugPatch one field in its target coordinate system, preserving bytes on error
C12.3encode_symbolsEncode locals-first .symtab, name-sharing .strtab, and first_global
C12.4attach_sectionsAppend nonallocated data and section headers; update the ELF section-table fields
C12.5finishResolve debug references, construct output sections and symbols, and integrate the upstream result

Two constraints govern the implementation. First, debug references use distinct coordinates. Ordinary image values are VAs for ET_EXEC and RVAs for PIE; debug-section references are section offsets; DTPOFF and STT_TLS use TLS module offsets. ABS constants and unresolved WEAK zero do not undergo image-base conversion. A TLS module offset must not become the TP-relative offset used by machine code.

Second, appending metadata must preserve the entry point, program headers, and allocated contents. Only e_shoff, e_shentsize, e_shnum, and e_shstrndx may change in the original file prefix. Added nonallocated contents must not enlarge PT_LOAD. Retained symbols follow the final layout; references to code removed by GC receive tombstones. The README defines their values and the rules for merged pieces and TLS section identities.

Use a numeric check to keep the coordinates separate. If an ET_EXEC symbol has address 0x401120, the same static PIE layout represents it as RVA 0x1120; a debug section may begin at file offset 0x2a0, which is not a code address; a TLS symbol may have module offset 0x38, which is not a TP-relative offset. A relocation written into .debug_* uses the debug section's file-relative coordinate, ordinary image references use VA for ET_EXEC and RVA for PIE, and STT_TLS uses the template offset. Only after startup establishes a thread pointer can that value become a TP-relative address.

The supported subset consists of uncompressed debug sections, ordinary section numbering, and the listed relocations. Concatenate debug strings directly. Reject compressed sections, PC-relative debug relocations, references to other metadata, and outputs requiring extended section numbering with the specified errors.

Separate the image from the appended indexes

finish must not lay out the upstream image again. First borrow final addresses and piece mappings from Lowered, resolve debug relocations, symbols, and tombstones; then append .debug_*, .symtab, .strtab, and the section-header table, changing only the four permitted ELF-header fields. This lets you verify independently that the original PT_LOAD image still runs and that tools can interpret the new indexes.

GDB and addr2line test source locations and symbol addresses; readelf tests section-header and relocation structure. A program that still returns 42 does not prove debug coordinates, and a file that readelf accepts does not prove that appended metadata left the load image unchanged.

Trace each task to its tests

TaskKey testsProperty established
C12.1 collect_debugmultiple_compilation_units_keep_independent_debug_coordinatesSection-local coordinates remain independent across compilation units.
C12.2 relocate_debugsymbols_and_line_tables_agree_with_gnu_ld, rejects_unsupported_debug_inputsDebug fields use the correct coordinate and unsupported encodings are explicit errors.
C12.3 encode_symbolssymbols_and_line_tables_agree_with_gnu_ld, zero_sized_tls_symbol_keeps_its_nobits_section_identityLocal/global order, string indexes, and TLS/NOBITS identity agree.
C12.4 attach_sectionsattach_appends_data_and_section_table, attach_respects_extended_numbering_boundaryAppending metadata preserves the loaded prefix and encodes section-table boundary rules.
C12.5 finishappending_sections_preserves_the_class8_image, cli_output_is_debuggableThe program keeps its original layout while tools can read symbols, lines, and sections.

GDB, addr2line, and readelf cover different responsibilities; one readable artifact cannot replace the other evidence.

Acceptance

From the repository root, on native x86-64 Linux:

cargo test --locked -p class12
cargo test --locked -p class12 --release
python3 scripts/grade.py class12

Acceptance checks three aspects:

  • Interface contracts: grouping, alignment, coordinate conversion, failure atomicity, symbol order, and section-header encoding.
  • Tool interpretation: multi-compilation-unit breakpoints, source lines, and cross-file backtraces, compared with GNU ld's GDB sessions; live addresses and tombstones checked with addr2line and readelf.
  • Composition: preservation of the upstream file prefix, combinations of merged constants, CFI, GOT, and TLS, and CLI/library equivalence.

GDB variable checks cover the capabilities of the supplied startup. Automatic TLS-variable lookup also requires a runtime discovery protocol. The startup does not provide a complete libc/thread_db protocol, so that capability is outside acceptance.