Metadata sections, COMDAT and SHF_LINK_ORDER

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
2
3
4
.section .text.foo,"ax",@progbits

.section .meta.foo,"a",@progbits
.quad .text.foo-. # PC-relative relocation

Categories by memory and reference patterns

Non-allocatable sections

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
2
3
4
# Without 'w', text relocation.
.section .meta.foo,"",@progbits
.quad .text.foo # link-time constant
# Absolute relocation types have different treatment in SHF_ALLOC and non-SHF_ALLOC sections.

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 from clang -fprofile-generate and -fprofile-instr-generate
  • __sancov_bools sections from clang -fsanitize-coverage=inline-bool-flags
  • __sancov_cntrs sections from clang -fsanitize-coverage=inline-8bit-counters
  • __sancov_guards sections from clang -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
2
3
4
5
6
.section .meta.foo,"a",@progbits
.quad .text.foo-. # link-time constant

# Without 'w', text relocation.
.section .meta.foo,"aw",@progbits
.quad .text.foo # R_*_RELATIVE dynamic relocation if -pie or -shared

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 section meta.
  • If __stop_meta is not defined, the linker defines it to the end of the output section meta.

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 associated meta section should be retained.
  • When .text.foo is discarded, the meta 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Zero flag section group (without the GRP_COMDAT flag)
.section .text.foo,"aG",@progbits,foo
.globl foo
foo:

.section .meta.foo,"a?",@progbits
.quad .text.foo-.


# GRP_COMDAT section group, common with C++ inline functions and template instantiations
.section .text.foo,"aG",@progbits,foo,comdat
.globl foo
foo:

.section .meta.foo,"a?",@progbits
.quad .text.foo-.

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#--- a.s
.globl _start
_start:
leaq __start_meta(%rip), %rdi
leaq __stop_meta(%rip), %rsi
call bar

.section .text.foo,"ax",@progbits
.globl foo; foo: ret

.section meta,"a"
# Describes .text.foo
.byte 0

#--- b.s
.section .text,"ax",@progbits
.globl bar; bar: call foo; ret

.section meta,"a"
# Describes .text
.byte 1

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
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wattributes"
__attribute__((retain,used,section("meta")))
static const char dummy[0];
#pragma GCC diagnostic pop

In a macro, you may use:

1
2
3
4
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wattributes\"")
...
_Pragma("GCC diagnostic pop")

-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
2
3
asm(".pushsection .init_array,\"aw\",@init_array\n" \
".reloc ., R_AARCH64_NONE, meta\n" \
".popsection\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 keeps meta 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

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#--- a.s
.globl _start
_start:
leaq __start_meta(%rip), %rdi
leaq __stop_meta(%rip), %rsi
call bar

.section .text.foo,"ax",@progbits
.Lfoo:
.globl foo; foo: ret

.section meta,"ao",@progbits,.Lfoo
# Describes .text.foo
.byte 0

#--- b.s
.section .text,"ax",@progbits
.Lbar:
.globl bar; bar: call foo; ret

.section meta,"ao",@progbits,.Lbar
# Describes .text.bar
.byte 1
  • Each meta section explicitly links to its text section via the sh_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.

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)) ... }
This limitation affected Linux kernel builds (ClangBuiltLinux issue #953).

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 or SHF_LINK_ORDER

Ensure post-inlining SHF_LINK_ORDER creation

Consider this scenario involving allocatable sections referenced by code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.globl _start
_start:
leaq __start_meta(%rip), %rdi
leaq __stop_meta(%rip), %rsi
call bar

.section .text.foo,"ax",@progbits
.globl foo
foo:
leaq .Lmeta.foo(%rip), %rax
ret

.section .text.bar,"ax",@progbits
.globl bar
bar:
call foo
leaq .Lmeta.bar(%rip), %rax
ret

.section meta,"ao",@progbits,foo
.Lmeta.foo:
.byte 0

.section meta,"ao",@progbits,bar
.Lmeta.bar:
.byte 1

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
2
3
4
5
6
7
8
9
10
11
12
13
# After inlining: multiple sections reference the same metadata
.section .text.foo,"ax",@progbits
.globl foo
foo:
leaq .Lmeta.foo(%rip), %rax
ret

.section .text.bar,"ax",@progbits
.globl bar
bar:
leaq .Lmeta.foo(%rip), %rax # Reference from inlined foo
leaq .Lmeta.bar(%rip), %rax # Original reference
ret

The inlining leads to a linker rerror.

  • _start calls bar but not foo.
  • .text.bar (caller) is retained, while .text.foo (callee) is discarded
  • The meta section for foo 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 calls bar and keeps .text.bar (caller) live.
  • .text.bar contains an inline copy of foo and keeps meta section for foo live.
  • The meta section, in turn, references .text.foo. This creates an unintended dependency, causing the code for foo 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
2
3
4
5
6
7
8
9
10
11
12
13
14
header [a.o common]
- DW_TAG_compile_unit [a.o common]
-- DW_TAG_variable [a.o .data.foo]
-- DW_TAG_namespace [common]
--- DW_TAG_subprogram [a.o .text.bar]
--- DW_TAG_variable [a.o .data.baz]
footer [a.o common]
header [b.o common]
- DW_TAG_compile_unit [b.o common]
-- DW_TAG_variable [b.o .data.foo]
-- DW_TAG_namespace [common]
--- DW_TAG_subprogram [b.o .text.bar]
--- DW_TAG_variable [b.o .data.baz]
footer [b.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.