Updated in 2025-09.
Modern compilers create metadata sections to support instrumentation,
debugging, profiling, and other features. These sections should be
retained when their associated code is kept and discarded when their
code is removed-but achieving this requires careful linker
considerations. This post explores the evolution of solutions, from
early workarounds to modern mechanisms like section groups and
SHF_LINK_ORDER
, examining their trade-offs and real-world
applications.
Metadata sections: properties and categories
Metadata sections usually have the following property:
- Relocations within a metadata section refer to its associated text section or any other auxiliary metadata sections. In many applications, a metadata section doesn't need additional auxiliary sections.
- Text sections don't have relocations that point to their metadata section. In some cases, a text section might reference its metadata section, but this means that the text section can't be inlined into another.
This assembly code snippet demonstrates a metadata section referencing its associated text section.
1 | .section .text.foo,"ax",@progbits |
Categories by memory and reference patterns
These sections aren't loaded into memory at runtime and serve development or analysis purposes:
.debug_*
(DWARF debugging information).stack_sizes
(static stack size information)
Non-SHF_ALLOC
metadata sections must use absolute
relocation types since there's no program counter concept for sections
not loaded into memory:
1 | # Without 'w', text relocation. |
Allocatable sections not referenced by code
These sections are loaded into memory but aren't directly referenced by executable code through relocations:
.eh_frame
sections containing unwind tables.gcc_except_table
sections with language-specific exception handling data__patchable_function_entries
sections generated by-fpatchable-function-entry=
.sframe
sections containing unwind tables
These sections are typically accessed through encapsulation symbols
(__start_section
/__stop_section
) or through
runtime registry mechanisms rather than direct code references.
Allocatable sections referenced by code
These sections are both loaded into memory and referenced by executable code:
__llvm_prf_cnts/__llvm_prf_data
sections fromclang -fprofile-generate
and-fprofile-instr-generate
__sancov_bools
sections fromclang -fsanitize-coverage=inline-bool-flags
__sancov_cntrs
sections fromclang -fsanitize-coverage=inline-8bit-counters
__sancov_guards
sections fromclang -fsanitize-coverage=trace-pc-guard
Functions directly reference these sections through relocations, creating bidirectional dependencies between code and metadata.
For SHF_ALLOC
sections, PC-relative relocations are
recommended. Using absolute relocations (with width equal to the word
size) generates R_*_RELATIVE
dynamic relocations, requiring the section to be writable:
1 | .section .meta.foo,"a",@progbits |
C identifier name sections
The runtime typically needs to access all metadata sections collectively. To facilitate this, metadata section names usually consist of pure C-like identifier characters (alphanumeric characters in the C locale plus underscore) to take advantage of a powerful linker feature.
Using the section name meta
as an example:
- If
__start_meta
is not defined, the linker defines it to the start of the output sectionmeta
. - If
__stop_meta
is not defined, the linker defines it to the end of the output sectionmeta
.
These __start_meta
and __stop_meta
symbols
are called encapsulation symbols, enabling runtime code
to iterate over all instances of a metadata section type.
According to C11 7.1.3 [Reserved identifiers]
:
All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use.
No other identifiers are reserved. If the program declares or defines an identifier in a context in which it is reserved (other than as allowed by 7.1.4), or defines a reserved identifier as a macro name, the behavior is undefined.
While Clang's -Wreserved-identifier
warns about this
usage, compilers don't treat this as undefined behavior.
Garbage collection for metadata sections
Users expect intuitive garbage collection behavior for metadata sections:
- When
.text.foo
is retained, its associatedmeta
section should be retained. - When
.text.foo
is discarded, themeta
section should be discarded as well.
This requires understanding how different section configurations interact with linker garbage collection.
Scenario 1: meta
sections without the
SHF_ALLOC
flag. {nonalloc}
Non-SHF_ALLOC
sections without the
SHF_LINK_ORDER
flag and not part of a section group are
retained during garbage collection, even if their associated text
sections are discarded.
Note: Some linkers do not implement the SHF_LINK_ORDER
or section group exclusions.
Scenario 2: meta
sections with the
SHF_ALLOC
flag, but no references from
.text.foo
to meta
.
{alloc-noreloc}
The meta
sections are discarded because they appear
unreferenced by other sections.
This creates a correctness issue where live text sections lose their associated metadata, potentially breaking instrumentation or debugging functionality.
Scenario 3: meta
sections with the
SHF_ALLOC
flag, with .text.foo
referencing
meta
. {alloc-reloc}
Traditional garbage collection semantics work correctly-both sections are retained or discarded together.
To address the problems in Scenarios 1 and 2, we can use section groups to explicitly establish the relationship between text sections and their metadata. This approach ensures they are treated as a unit during linker garbage collection.
Implementation strategy
- If the text section is already part of a section group, place the metadata section in the same group.
- Otherwise, create a zero flag section group (supported in
LLVM>=13.0.0 with
comdat noduplicates
for ELF).
1 | # Zero flag section group (without the GRP_COMDAT flag) |
I have a separate blog post for COMDAT and section group.
A section group requires an extra section header (usually named
.group
), consuming 40 bytes on ELFCLASS32 platforms and 64
bytes on ELFCLASS64 platforms.
This size overhead becomes significant in applications with many small functions, prompting the search for more efficient representations.
Note: While AArch64 and x86-64 define ILP32 ABIs using
ELFCLASS64
, they could technically use
ELFCLASS32
for small code model with regular ABIs if the
kernel permits.
Another solution is the SHF_LINK_ORDER
flag, which
provides a more lightweight mechanism for establishing section
dependencies. This approach, its benefits, limitation, and
implementation details are covered comprehensively in the dedicated
SHF_LINK_ORDER
chapter.
Metadata sections referenced through encapsulation symbols
Let's examine a popular metadata section design pattern that creates significant garbage collection challenges:
This pattern involves metadata sections with these properties:
- Allocatable: The metadata sections have the SHF_ALLOC flag
- Runtime accessible: The metadata sections use C identifier names, enabling runtime collection via encapsulation symbols with references.
- Unidirectional references: Text sections don't contain relocations pointing to their metadata sections
Without special handling, this design would result in all metadata sections being discarded during garbage collection, even when their associated text sections are retained:
1 | #--- a.s |
Since the meta sections aren't directly referenced by relocations, they would be garbage collected despite their text sections being retained.
If you read on, you will learn that .section meta,"aR"
can make the metadata section unconditionally retained while
.section meta,"ao",@progbits,.text.bar
can make the section
dependent on .text.bar
.
Historical context: glibc static linking issue
In 2006, BZ3400
reported that stdio flushing failed with static linking when
--gc-sections
was enabled. The problem persisted and was
reported again in 2010 (BZ11133).
Linker response: A controversial solution
To address this crisis, linkers implemented a special rule:
- gold's special rule (2010) gold first implemented a
rule that
__start_section
/__stop_section
references from live sections retain all matching sections (commit). - GNU ld's adoption (2015): GNU ld followed suit, implementing the same rule (PR19161, PR19167).
The unfortunate special rule is: If a live section has a
__start_meta
or __stop_meta
reference,
all meta
input section will be retained by
ld.bfd --gc-sections
.
This implementation choice proved problematic, preventing effective garbage collection of unused metadata sections. As Alan Modra noted in a 2010 comment:
I think this is a glibc bug. There isn't any good reason why a reference to a __start_section/__stop_section symbol in an output section should affect garbage collection of input sections, except of course that it works around this glibc --gc-sections problem. I can imagine other situations where a user has a reference to __start_section but wants the current linker behaviour.
As a concrete example, metadata sections for profile-guided optimization and coverage could not be garbage collected by linkers.
The rule created significant issues for optimization-related metadata:
Profile-Guided Optimization: Metadata sections for
PGO and coverage couldn't be garbage collected: - Functions reference
their __llvm_prf_cnts
sections and may reference
__llvm_prf_data
for value profiling -
__llvm_prf_data
sections reference their text sections and
associated counters - The __start_
references retain all
PGO metadata, which in turn retain all instrumented text sections -
Result: With GNU ld and Gold, no instrumented text
sections could be discarded
LLD ported but refined the GNU ld rule, making it somewhat less problematic:
__start_/__stop_ references from a live input section retains all non-SHF_LINK_ORDER C identifier name sections.
This refinement allowed SHF_LINK_ORDER
sections to be
garbage collected normally, though many metadata sections remained
non-collectible.
SHF_GNU_RETAIN: a game changer
GNU binutils 2.36 (January 2021) introduced the R
flag
representing SHF_GNU_RETAIN
on FreeBSD and Linux. This flag
provides a clean solution for ensuring critical sections survive garbage
collection.
When all meta
sections are discarded by
--gc-sections
, you encounter:
- LLD:
error: undefined symbol: __start_meta
- GNU ld:
undefined reference to '__start_meta'
Traditional approach uses undefined weak symbols: 1
[[gnu::weak]] extern const char __start_meta[], __stop_meta[];
SHF_GNU_RETAIN
enables another approach, as you can
define an empty section within the runtime 1
.section meta,"aR",@progbits
In C and C++ source code, with GCC>=11 or Clang>=13 (https://reviews.llvm.org/D97447), you can write:
1
2
3
4
5
__attribute__((retain,used,section("meta")))
static const char dummy[0];
In a macro, you may use:
1 | _Pragma("GCC diagnostic push") |
-Wattributes
ignores warnings for older compilers. The
used
attribute, when attached to a function or variable
definition, indicates that there may be references to the entity which
are not apparent in the source code. On COFF and Mach-O targets (Windows
and Apple platforms), the used
attribute prevents symbols
from being removed by linker section GC. On ELF targets, GNU
ld/gold/ld.lld may remove the definition if it is not otherwise
referenced.
The retain
attributed was introduced in GCC 11 to set
the SHF_GNU_RETAIN
flag on ELF targets.
GCC 11 has an open issue that __has_attribute(retain)
returning 1 does not guarantee SHF_GNU_RETAIN
is set (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99587).
Before SHF_GNU_RETAIN
, a common solution exploited GC
roots:
1 | asm(".pushsection .init_array,\"aw\",@init_array\n" \ |
This approach works because:
SHT_INIT_ARRAY
sections are GC roots- An empty
SHT_INIT_ARRAY
doesn't affect output - The artificial reference via
.reloc
keepsmeta
sections live
Note: Support for R_*_NONE
relocations was added to
LLVM 9.0.0 for multiple architectures.
The glibc
static linking fix with SHF_GNU_RETAIN
In 2021-04, I fixed the original glibc issue https://sourceware.org/PR27492 by marking
__libc_*
sections as `SHF_GNU_RETAIN.
Eliminating
the special linker rule: -z start-stop-gc
With SHF_GNU_RETAIN
available, the problematic special
linker rule could finally be eliminated:
LLD Implementation: Added
-z start-stop-gc
to completely drop the special rule (D96914).
GNU LD Implementation: Alan Modra and I implemented
ld.bfd -z start-stop-gc
(PR27451).
In April 2021, LLD enabled -z start-stop-gc
by default
with special recognition for __libc_
sections as a glibc
compatibility workaround. I acknowledges that the transition period was
too short, leading to compatibility issues. A comprehensive guide for
common problems is available at: https://lld.llvm.org/ELF/start-stop-gc.html
SHF_LINK_ORDER
The SHF_LINK_ORDER
ELF section flag has emerged as a
prominent mechanism for marking metadata sections as dependent on their
associated text sections, enabling the desired linker garbage collection
semantics. It's more size efficient than using section groups.
The path to adopting SHF_LINK_ORDER
for metadata
sections involved extensive community discussion and compromise.
In a
generic-abi thread, Cary Coutant initially proposed a new section
flag called SHF_ASSOCIATED
specifically for metadata
dependencies. However, HP-UX and Solaris representatives objected to
introducing a new generic flag. Reconsidering the problem, Cary Coutant
collaborated with Jim Dehnert and recognized that the existing (but
rarely used) SHF_LINK_ORDER
flag had semantics closely
aligned with metadata garbage collection requirements. The ordering
requirement of SHF_LINK_ORDER
had to be retained as used by
Solaris (previously used its own SHF_ORDERED
extension
before migrating to the ELF standard's SHF_LINK_ORDER
) and
ARM EHABI.
The final agreement extended SHF_LINK_ORDER
with
additional metadata garbage collection semantics rather than replacing
its original functionality.
Updated Semantics
This flag adds special ordering requirements for link editors. The requirements apply to the referenced section identified by the sh_link field of this section's header. If this section is combined with other sections in the output file, the section must appear in the same relative order with respect to those sections, as the referenced section appears with respect to sections the referenced section is combined with.
Note: A typical use of this flag is to build a table that references text or data sections in address order.
In addition to adding ordering requirements,
SHF_LINK_ORDER
indicates that the section contains metadata describing the referenced section. When performing unused section elimination, the link editor should ensure that both the section and the referenced section are retained or discarded together. Furthermore, relocations from this section into the referenced section should not be taken as evidence that the referenced section should be retained.
Interestingly, ARM EHABI had been using SHF_LINK_ORDER
for similar purposes before the formal specification update.
The ARM Exception Handling ABI uses .ARM.exidx*
sections
as index tables.
A .ARM.exidx
section contains a sequence of 2-word
pairs. The first word is 31-bit PC-relative offset to the start of the
region. The idea is that if the entries are ordered by the start
address, the end address of an entry is implicitly the start address of
the next entry and does not need to be explicitly encoded. For this
reason the section uses SHF_LINK_ORDER
for the ordering
requirement. The GC semantics are very similar to the metadata
sections'.
Before GNU Assembler 2.35, SHF_LINK_ORDER
could only be
produced by ARM-specific assembly directives, not through
user-customized sections.
The earlier assembly example can be rewritten to leverage
SHF_LINK_ORDER
:
1 | #--- a.s |
- Each
meta
section explicitly links to its text section via thesh_link
field. meta
sections are not retained or discarded following the associated text section.- Metadata sections maintain the same relative order as their linked text sections.
SHF_LINK_ORDER implementation details
Mixed unordered and ordered sections
The behavior is well-defined when output sections contain homogeneous section kinds:
- Only non-
SHF_LINK_ORDER
sections: Input sections maintain their original input order - Only
SHF_LINK_ORDER
sections: Input sections are ordered according to their linked-to sections
However, the specification was unclear about handling output sections
containing both kinds. Early LLD versions would report
error: incompatible section flags for .rodata
, and GNU ld
had similar diagnostics. This created issues for legitimate use cases
like: 1
.init.data : { ... KEEP(*(__patchable_function_entries)) ... }
The mixed section restriction made it impractical to add
SHF_LINK_ORDER
to existing metadata sections, as new object
files with the flag couldn't link with older object files lacking
it.
Solution: I consulted with Ali Bahrami, who clarified Solaris linker behavior:
The Solaris linker puts sections without SHF_LINK_ORDER at the end of the output section, in first-in-first-out order, and I don't believe that's considered to be an error.
Following Solaris's lead, LLD allows bitrary mixing (D84001) and places
SHF_LINK_ORDER
sections before
non-SHF_LINK_ORDER
sections.
Discarded linked-to section at compile time
When the linked-to section is eliminated by compiler optimizations,
the integrated assembler allows SHF_LINK_ORDER
sections
with sh_link=0
. LLD treats these as regular unordered
sections (D72904).
Discarded
linked-to section due to ld --gc-sections
When --gc-sections
discards the linked-to section, the
associated SHF_LINK_ORDER
section should also be discarded
as a dependent. However, this seemingly straightforward behavior relies
on a critical constraint:
A SHF_LINK_ORDER
section should only be referenced by
its linked-to section.
Breaking this rule can result in linker errors like:
1 | error: ... sh_link points to discarded section ... |
This error occurs when the SHF_LINK_ORDER
section is
retained by relocations from other live sections, even though its
linked-to section has been discarded.
Common violations:
- Function inlining after
meta
creation - Incomplete linker implementation of
__start_
/__stop_
symbol reference orSHF_LINK_ORDER
Ensure post-inlining SHF_LINK_ORDER creation
Consider this scenario involving allocatable sections referenced by code:
1 | .globl _start |
Note: The assembler automatically creates separate sections when sections have different linked-to sections.
If meta
sections are created before inlining occurs,
function inlining can break the fundamental SHF_LINK_ORDER
assumption. When foo
is inlined into bar
, the
result looks like:
1 | # After inlining: multiple sections reference the same metadata |
The inlining leads to a linker rerror.
_start
callsbar
but notfoo
..text.bar
(caller) is retained, while.text.foo
(callee) is discarded- The
meta
section forfoo
still links to the now-discarded.text.foo
- This creates an invalid
sh_link
reference, leading to a linker error{{.*}}:(meta): sh_link points to discarded section {{.*}}:(.text.foo)
This constraint has implications for Link-Time Optimization (LTO):
Since LTO heavily relies on inlining across translation units,
meta
sections cannot be created before the LTO phase
completes. For scenarios where early metadata generation is required,
consider:
- Prevent metadata section before LTO. Generate then during LTO backend compilation after all inlining passes.
- Prevent direct code references to the metadata section.
For better garbage collection performance, it's preferable to follow the constraint for all metadata sections, not just those referenced by code. This is to prevent the following scenario:
_start
callsbar
and keeps.text.bar
(caller) live..text.bar
contains an inline copy offoo
and keepsmeta
section forfoo
live.- The
meta
section, in turn, references.text.foo
. This creates an unintended dependency, causing the code forfoo
to be included, even though it's never called by any live code.
Additional implementation considerations
Identical Code Folding
During identical code folding with --icf={safe,all}
,
SHF_LINK_ORDER
sections are excluded from the initial
folding process. Instead, a SHF_LINK_ORDER
section is
discarded if its linked-to section is discarded as a duplicate.
Relocatable linking
During relocatable linking, SHF_LINK_ORDER
sections
cannot be combined by name, because the final
input-section-to-output-section relationship is only known in the final
link step. Relocatable linking with a linker script can override this
rule, e.g. Linux kernel's module utilizes
__patchable_function_entries : { *(__patchable_function_entries) }
Section comparison using addresses
When the linker compare two SHF_LINK_ORDER
sections that
link to different output sections, it should use the virtual addresses
of the output sections rather than their section indices for ordering
decisions. This is because output section addresses may not
monotonically increase as the output section index increases. See https://reviews.llvm.org/D79286.
Case study
-fpatchable-function-entry=
The __patchable_function_entries
sections follow the
allocatable sections (no code reference) pattern. It represents the
perfect scenario for SHF_LINK_ORDER
. Each function section
has exactly one corresponding metadata section, with no inlining
complications to consider.
While section groups could handle this case, they would introduce
unnecessary size overhead for a scenario that
SHF_LINK_ORDER
handles elegantly.
LLVM profile-guided optimization and code coverage
clang -fprofile-generate
and -fprofile-instr-generate
A function needs __llvm_prf_cnts
,
__llvm_prf_data
and in some cases
__llvm_prf_vals
. Inlining may happen.
A function references its __llvm_prf_cnts
and may
reference its __llvm_prf_data
if value profiling applies.
The __llvm_prf_data
references the text section, the
associated __llvm_prf_cnts
and the associated
__llvm_prf_vals
.
Since inlining can cause multiple text sections to reference the same
metadata sections, SHF_LINK_ORDER
's fundamental
assumption—that each section is referenced only by its linked-to
section—breaks down.
Strategy: Place all related sections
(__llvm_prf_cnts
, __llvm_prf_data
,
__llvm_prf_vals
) in a single section group to ensure
unified retention/discard behavior.
- Existing COMDAT groups: Reuse the group if the text section is already in a COMDAT
- New groups: Create zero-flag section groups for standalone sections. LLVM 13.0.0+ uses zero-flag section groups for this purpose
Users of GNU ld>=2.37 can experiment with
-z start-stop-gc
to discard unused metadata sections.
For Windows, the cnts section is named .lprfc$M
and the
data section is named .lprfd$M
. The garbage collection
story is problematic due to link.exe limitation: when an
IMAGE_COMDAT_SELECT_ASSOCIATIVE
section defines an external
symbol, MSVC link.exe may report spurious duplicate symbol errors
(error LNK2005
), even for associative sections that would
ultimately be discarded. While lld-link doesn't have this limitation, a
portable implementation needs to work around MSVC link.exe.
For a COMDAT .lprfd$M
, its symbol must be external
(linkonce_odr), otherwise references to a non-prevailing symbol would
cause an error. Due to the limitation, .lprfd$M
has to
reside in its own COMDAT, no sharing with .lprfc$M
.
Different COMDAT groups mean that the liveness of one
.lprfc$M
does not make its associative
.lprfd$M
live. Since a .lprfd$M
may be
unreferenced, we have to conservatively assume all COMDAT
.lprfd$M
live. Since .lprfc$M
input sections
parallel .lprfd$M
input sections, we have to conservatively
assume all COMDAT .lprfc$M
live. For an external symbol, we
use a /INCLUDE:
directive in .drectve
to mark
it as a GC root. As a result, .drectve
may have many
/INCLUDE:
directives, just to work around the link.exe
limitation.
Note: for ELF we can use R_*_NONE
to establish an
artificial dependency edge between two sections. I don't think PE-COFF
provides a similar feature.
LLVM sanitizer coverage
clang -fsanitize-coverage=
clang -fexperimental-sanitize-metadata=
clang -fexperimental-sanitize-metadata=atomics
instruments functions and creates !pcsections
metadata for
functions and atomic instructions. The address of an instrumented atomic
instruction is recorded in a section named sanmd_atomics
.
The sanmd_atomics
section has the
SHF_LINK_ORDER
flag and links to the text section.
.eh_frame
This is a monolithic allocatable section not referenced by code. Invented more than 20 years ago, it is an anti-pattern, which should be avoided by modern metadata sections.
.sframe
As of Sep 2025, as --gsframe
creates a monolithic
section, violating COMDAT section group rules. I have filed https://sourceware.org/bugzilla/show_bug.cgi?id=33370. I
feel sad that SFrame didn't learn lessons from metadata section design,
struggled with linker COMDAT and garbage collection.
Miscellaneous considerations
The ELF provides mechanisms to support the desired linker garbage
collection behavior, and it's essential to follow its rules.
Specializing this behavior requires significant code within the linker
and every consumer, as seen with .eh_frame
.
Achieving desired garbage collection behavior might require multiple sections. In the future, we should explore section header size reduction opportunities by changing the format. (Light ELF: exploring potential size reduction)
ARM Compiler 5's approach to DWARF Version 3 debug information provides valuable insights into the trade-offs between different metadata management strategies. As Peter Smith explained:
We found that splitting up the debug into fragments works well as it permits the linker to ensure that all the references to local symbols are to sections within the same group, this makes it easy for the linker to remove all the debug when the group isn't selected.
This approach did produce significantly more debug information than gcc did. For small microcontroller projects this wasn't a problem. For larger feature phone problems we had to put a lot of work into keeping the linker's memory usage down as many of our customers at the time were using 32-bit Windows machines with a default maximum virtual memory of 2Gb.
While the COMDAT overhead might tempt developers to try
SHF_LINK_ORDER
for debug information, the ordering
requirements make this approach impractical.
Consider these typical debug information fragments:
1 | header [a.o common] |
While DW_TAG_*
entries associated with concrete sections
could theoretically use SHF_LINK_ORDER
, the result would
place all linked sections before common parts, disrupting the logical
structure that debug information consumers expect.
Mach-O platform similarity
Apple's ld64 linker provides functionality similar to ELF's
__start_
/__stop_
symbols through
section$start$__DATA$__data
and
section$end$__DATA$__data
symbols. Notably, ld64's behavior
resembles ld.lld -z start-stop-gc
, suggesting convergent
evolution toward more precise garbage collection semantics across
different object file formats.