[The World of Linkers—Lab 11] Seeing Through Indirection: GOT, Relaxation, and Static TLS
Extend static PIE with GOT references, the contracted instruction relaxation, and local-exec/initial-exec TLS. Theory 07 explains the GOT and relaxation; Theory 09 explains templates, thread pointers, and access models.
Starting point: class11 code and tests, in the private repository; access is required. Complete five interface groups in the class11 crate library. Reuse class8's live plan, class6 layout, class9 PIE fixups, and class10 merge/unwind handling. The section policy, TLS startup assembly, build support, and CLI are supplied.
Implementation tasks
| Task | Interface | Required outcome |
|---|---|---|
| C11.1 | tls_layout, Tls::tpoff | Lay out initialized and zero-filled templates and calculate TP-relative offsets |
| C11.2 | lower | Select treatment from relocation kind, target category, and instruction bytes, without modifying input |
| C11.3 | Got::offset, Got::encode | Assign shared slots in first-use order and encode constants or request load-time addresses |
| C11.4 | emit | Validate TLS reservations, fill the startup descriptor, and emit PT_TLS |
| C11.5 | lower_with | Combine live storage, merged constants, GOT, TLS, and unwind metadata in one PIE layout, returning an unserialized result |
The class11 README defines the full matrix, synthetic identities, field ranges, and diagnostic order. This class supports LE/IE and explicitly rejects GD, LD, and TLSDESC. GOT relaxation requires the specified mov byte conditions; ordinary GOTPCREL and ABS targets retain slots.
Prepared contains input identities, selected definitions, and liveness; Lowered adds placements, patches, and load-time records. Implement lower_with to retain piece mappings and rebuild live unwind records, then let the supplied Lowered::emit serialize the result. lower_prepared and lower_with_unwind are wrappers invoking this task. Alias and ordering parameters support later ICF: this class validates and uses them, without proving code equivalence.
Startup proceeds through PIE address repair, template copying, thread-pointer installation, and user entry. Ordinary pointers inside the template must become runtime addresses before copying; TLS variables themselves use TP offsets. The README's coordinate examples distinguish these values and explain merged-constant references without applying an addend twice.
Keep three address meanings separate
On x86-64, FS.base is the base used for address calculation, while %fs:0 loads a word from that address. The loaded value equals the thread pointer (TP) only when startup stores the thread-control-block self pointer at offset zero. FS.base, the memory read by %fs:0, and TP + offset are therefore distinct concepts. Input symbols also do not share one “inside the image” address class: an Image value changes with the load bias, an Absolute value does not, a TLS value is a template offset, and an unresolved Weak value is zero. lower must classify these identities before choosing a GOT, TLS, relaxation, or ordinary-fixup path.
A GOT slot stores a value that will be resolved after layout, a TLS template stores initial bytes copied for each thread, and a TP-relative offset is meaningful only after a thread pointer exists. Layout first sizes slots, the template, and reservations; lowering chooses GOT, TLS, or relaxation without mutating input bytes; startup then performs PIE self-relocation, copies the TLS template, installs TP, and enters user code. Treating an ordinary template pointer as a TP offset, or adding the load bias again after copying it, produces an apparently plausible but wrong address.
The base fixture must match class9 byte-for-byte, proving that the new paths do not perturb unrelated behavior. Add GOT, TLS, and unwind inputs separately before exercising the combined fixture; this makes a coordinate-conversion error diagnosable.
For a concrete TLS calculation, suppose .tdata contains 7 initialized bytes, .tbss needs 13 zero bytes, and the maximum alignment is 16. Then image.len() is 7 and memory_size is 20. Placing the thread pointer at template offset 32 gives the first initialized byte a TP-relative offset of -32; byte 3 of .tdata has offset -29. The final 13 bytes exist only in each thread's memory copy. Therefore PT_TLS.p_filesz is 7 and p_memsz is 20. Treating p_memsz as file length would make startup copy bytes that are not in the image.
GOT has the same phase boundary: equal Slot values allocate one 8-byte slot in first-use order, while the slot's value remains an Image, Absolute, or Tls identity until layout is known. An image slot may be emitted as zero plus a load-time Relative record; treating that zero as the final address, or adding the load bias to a TP-relative offset, produces a plausible but wrong pointer.
Trace each task to its tests
| Task | Key tests | Property established |
|---|---|---|
C11.1 tls_layout/tpoff | tls_layout_orders_initialized_before_zero_fill, tls_metadata_precedes_size_calculation | Initialized bytes precede zero-fill, and template file size is distinct from per-thread memory size. |
C11.2 lower | lowering_selects_got_relaxation_and_thread_offsets, lowering_rejects_tls_mismatches_and_models | Instruction form, TLS model, and target identity select the path; unsupported combinations are rejected. |
C11.3 Got | got_deduplicates_in_first_use_order, got_encodes_constants_and_relative_records | Equal slots are allocated once, and slot values versus load-time Relative records are produced at the correct phase. |
C11.4 emit | tls_startup_descriptor_and_program_header, emit_rejects_bad_tls_reservations | PT_TLS, the startup descriptor, and the reservation describe one template. |
C11.5 lower_with | threads_receive_independent_blocks_from_pt_tls, lowering_preserves_layout_before_startup_serialization | Thread copies are independent and combined layout remains intact before serialization. |
Establish coordinates and phase boundaries before testing startup code; a correct exit status alone cannot prove PT_TLS or GOT encoding.
Acceptance and what it establishes
From the cloned repository root on native x86-64 Linux, with GNU as and objcopy:
cargo test --locked -p class11cargo test --locked -p class11 --releasepython3 scripts/grade.py class11Contract tests check template layout, lowering choices, GOT sharing and records, the startup descriptor, and PT_TLS. Native tests check initialized/zero-filled TLS, template pointers, relaxed and retained GOT slots, and unsupported references.
Combined tests inspect the shared coordinates of merged constants, GOT, TLS, and unwind indexes, then execute under ASLR. A second-thread test builds its copy solely from PT_TLS and checks independent values. These establish backend composition and an independently usable template description. Baseline inputs with no GOT, TLS, merge, or unwind features must reproduce class9's output exactly. Passing does not establish a complete libc threading runtime.
The tls_startup.S file and the second-thread native test also require native x86-64 Linux. On macOS the build script deliberately reports class11 requires native x86-64 Linux; that means the environment does not satisfy this lab's startup contract, not that TLS layout or GOT lowering is wrong.