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

[The World of Linkers—Lab 06] File Bytes, Memory Bytes, and Segment Permissions

Add read-only constants, initialized data, BSS, and COMMON storage, with RX, R, and RW load mappings. Track stored bytes separately from required memory. Theory 05 and Theory 06 explain layout and zero-fill.

Starting point: class6 code and tests, in the private repository; access is required. Reuse class1–3 parsing and selection and class5 relocation arithmetic. Implement three interfaces in the class6 crate library. The CLI is supplied.

Implementation tasks

TaskInterfaceRequired result
C6.1planValidate contributions and COMMON; produce storage, placements, and three load-segment plans
C6.2emitPreserve relocated payloads and serialize the ELF header, three PT_LOAD entries, and PT_GNU_STACK
C6.3linkClassify inputs, select global definitions, allocate storage, bind addresses, and apply relocations

class6 README specifies types, packing order, and diagnostics. Contribution identity remains (file, section). Initialized storage requires memory_size equal to bytes length; BSS requires empty bytes but may need nonzero memory. Validate all contribution and COMMON metadata before packing or resource checks.

Text begins after 288 header bytes. ReadOnly and Data groups start on page boundaries. The file cursor stops after initialized Data; a separate memory cursor continues through BSS and COMMON. These have runtime addresses without patchable file offsets. Global selection has already merged COMMON requirements: plan must not merge them again. A COMMON overridden by a strong definition receives no separate allocation.

Accept the four exact type/flag combinations listed in the handout. Allocated TLS, merge, or group sections are outside this stage, even when empty. A stored pointer in Data may target BSS, but BSS cannot hold a stored relocation field. emit consumes a valid plan and preserves relocated payloads rather than copying original inputs over them. The entry must lie inside a nonempty Text contribution; headers and alignment padding are not code.

Use a small layout to check the two cursors: initialized Data has 12 file bytes, while BSS needs 20 memory bytes. After Data is written, the file grows by those 12 bytes; BSS advances the same memory cursor by 20 without adding file bytes. Consequently the Data load segment can have p_filesz < p_memsz, with the difference supplied as zero-filled memory by the kernel. Appending BSS to the file might still look readable, but it would erase the file/memory boundary this class is meant to express.

Use a small layout to check the two cursors: initialized Data has 12 file bytes, while BSS needs 20 memory bytes. After Data is written, the file grows by those 12 bytes; BSS advances the same memory cursor by 20 without adding file bytes. Consequently the Data load segment can have p_filesz < p_memsz, with the difference supplied as zero-filled memory by the kernel. Appending BSS to the file might still look readable, but it would erase the file/memory boundary this class is meant to express.

Acceptance and what it establishes

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

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

Contract tests independently inspect file size, memory size, empty contributions, boundaries, entry validity, and segment fields. Native tests exercise globals, BSS zero-fill across pages, shared COMMON, strong definitions overriding COMMON, and a Data pointer targeting BSS.

Independent permission probes must receive SIGSEGV when writing Text/ReadOnly or executing Data, alongside structural checks of segment flags. A crash alone does not establish correct permissions. Field inspection and successful normal execution help distinguish protection failures from layout, entry, or relocation errors.