Linker Orphan Sections: Detect New Input Sections Before They Land in an Unintended Segment
A new input section can survive linking even when a custom linker script never mentions it. GNU BFD ld can create or select an output section for that orphan and place it according to target-specific rules. Treat a successful link as a reason to inspect the placement, not proof that the script accounts for every byte.
This procedure pins GNU Binutils 2.44. An executed x86-64 fixture injected a read-only metadata section into a minimal custom layout and compared placement, warnings, strict rejection and explicit segment assignment. The ELF files were inspected but never executed.
Build an input-to-output ledger
For each input section, record its object/member, name, size, alignment and flags. Then record the output section, address range, load address where relevant and containing program segment. Include linker-generated sections as well as sections emitted directly by application sources.
The BFD 2.44 orphan-section documentation explains that unmatched sections can be appended to an existing output section with the same name or placed in a newly created section. Placement commonly considers attributes such as code versus data, but the behavior is not a universal address guarantee across targets.
A wildcard like *(.text*) can cover expected function subsections without covering a new metadata family. Conversely, an overly broad wildcard can swallow a new section into an inappropriate output. Review the actual match rather than treating every wildcard as either safe or dangerous.

Choose a discovery policy deliberately
For this linker, –orphan-handling=place is the default. Warn places the section and emits a diagnostic; error rejects an orphan. Discard removes unmatched sections and is not an appropriate way to make an incomplete production script appear fully specified.
Start a migration audit with warnings if the existing image contains many legitimate unmatched sections. Classify each one, then adopt a stricter gate once the intended layout is explicit. A blanket error policy without understanding runtime-generated sections can create noisy failures that people learn to ignore.
Keep the map and warnings as build artifacts. If CI only retains the final exit status, warn mode can silently become a permission to ship arbitrary new placement. State which new-section changes require review even when the linker returns success.
Inject one controlled section
The host fixture assembled a minimal entry instruction and a four-byte .vendor_meta section marked allocatable but not writable or executable. The custom script placed text and recognized support sections but initially had no rule for .vendor_meta.
Place mode linked the metadata. Warn mode also linked it and named the orphan. Error mode rejected the unmatched section. Adding an explicit .vendor_meta output rule allowed the strict link to complete.
An early strict attempt also exposed linker-generated GOT and relocation sections. The final minimal script classified those names explicitly instead of discarding them. This is useful evidence that a source-only inventory can be incomplete even in a tiny fixture.
Check segment permissions after section placement
The Binutils readelf reference provides separate section-header and program-header views. Use both. A section’s own flags and its containing LOAD segment’s permissions answer different questions.
In the initial fixture, .vendor_meta shared the same read/execute LOAD segment as .text. Adding an explicit output-section rule preserved that segment arrangement. The metadata’s non-executable input flag did not independently create a non-executable mapping.
A second minimal script used explicit program headers and separated the metadata into a read-only LOAD segment while keeping text read/execute. Readelf confirmed the two segment assignments. This demonstrates a layout distinction in the host ELF; it is not evidence about a firmware loader’s MPU/MMU programming.
| Gate | Observed fixture result |
|---|---|
| Orphan detection | Warn reported the new section; error rejected it. |
| Explicit section placement | Strict link passed, but metadata still shared an executable segment. |
| Explicit segment policy | Metadata moved to a separate read-only LOAD segment. |
Do not transplant the minimal layout
The fixture used invented virtual addresses and no meaningful application data, startup tables or hardware memory map. Its script is a diagnostic model. A target script must preserve the board’s actual ROM/RAM regions, copy/zero tables, alignment, unwind data and loader constraints.
For bare-metal images, a program-header permission does not by itself configure hardware protection. For an operating-system executable, the loader and page alignment affect mapping. Identify which mechanism enforces the product’s intended permissions before accepting a section-level result.
Review load and run addresses separately where the firmware copies initialized data. An orphan placed in a loadable region may still be absent from a custom copy table. A correct-looking ELF address does not prove startup initializes the object as intended.
Keep collection and placement diagnoses separate
Section garbage collection can remove an input before its placement matters. Conversely, a retained registration section can still land in the wrong memory region. When both mechanisms are enabled, preserve evidence of retention and placement as separate checks.
Do not add KEEP merely because a section is an orphan. KEEP changes retention, not the complete memory-region or segment policy. Likewise, naming a section in a script does not necessarily make its data reachable when garbage collection is active.
Make new-section review reproducible
Diff the input/output ledger after compiler upgrades, new libraries and linker-script changes. Include section size, alignment and segment membership rather than comparing names alone. A familiar section can grow across a boundary or acquire different flags.
Retain the injected-section negative control with the strict build gate. For the real target, add startup and permission tests appropriate to its loader and hardware. The accepted result should explain where every required section goes and how its permissions are enforced, while leaving unexecuted target behavior explicitly pending.