[llvm-branch-commits] [lld] bacd876 - [lld][MachO] Preserve __eh_frame ordering during BP section sorting (#191412)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Thu May 28 00:00:57 PDT 2026


Author: Karim Alweheshy
Date: 2026-05-26T10:20:13-07:00
New Revision: bacd876134494c69d02b4509e125c47b5e86d3d2

URL: https://github.com/llvm/llvm-project/commit/bacd876134494c69d02b4509e125c47b5e86d3d2
DIFF: https://github.com/llvm/llvm-project/commit/bacd876134494c69d02b4509e125c47b5e86d3d2.diff

LOG: [lld][MachO] Preserve __eh_frame ordering during BP section sorting (#191412)

The Balanced Partitioning section orderer collects all live
`ConcatInputSection`s as candidates for content-similarity reordering.
This includes `__eh_frame` CIE and FDE records, which have internal
ordering constraints: each FDE contains a backward-relative 32-bit
offset to its parent CIE, requiring CIEs to precede their FDEs.

When the BP orderer assigns priorities to `__eh_frame` subsections and
`Writer.cpp` sorts by those priorities, FDEs can end up before their
parent CIEs. The resulting CIE-pointer offsets resolve correctly with
32-bit wrapping arithmetic but underflow with 64-bit pointer arithmetic,
causing DWARF consumers (crash reporters, debuggers) to silently lose
unwind data.

## Fix

Have the BP orderer skip the Mach-O `__TEXT,__eh_frame` section
explicitly before collecting candidate subsections, preserving the
existing CIE/FDE order without adding state to `Section`.

This is the only MachO section with this constraint:
- `__unwind_info` is a `SyntheticSection` (not a `ConcatOutputSection`),
so it never enters the BP pipeline
- `__gcc_except_tab` LSDA entries are referenced by absolute offset, so
reordering is safe
- `-order_file` is unaffected because it assigns priorities through
symbol definitions (which live in `__text`, not `__eh_frame`). Only the
BP orderer enumerates sections directly.

## Production impact

Verified on a large iOS application (~218 MB binary, ~34,000 FDEs)
linked with `lld` + `--bp-compression-sort=both`.

### Static analysis of `__eh_frame`

Simulating 64-bit CIE pointer resolution on the output binary across
multiple builds:

| Build | `__eh_frame` layout | FDEs resolved (64-bit) | FDEs failed |
|---|---|---|---|
| lld + BP sort | FDEs first | 15 / 34,257 | **34,242 (99.96%)** |
| lld + fix | CIEs first | 30,558 / 30,558 | **0** |

### Runtime verification

Proxied crash report uploads from a device running both the affected and
fixed binaries:

|  | Affected build | Fixed build |
|---|---|---|
| Threads captured | 3 | 24 |
| Total frames | 28 | 135 |
| Background threads | 0-2 | 23 |

The affected build lost ~85% of thread data. The crash reporter could
only unwind the crashed thread (via compact unwind). All background
thread unwind data was silently dropped.

## Reproducer

Minimal test case (ARM64). Requires `--bp-compression-sort=both` to
trigger:

```bash
llvm-mc -filetype=obj -emit-compact-unwind-non-canonical=true \
  -triple=arm64-apple-macos11.0 test.s -o test.o
ld64.lld -arch arm64 -platform_version macos 11.0 11.0 \
  -syslibroot $(xcrun --show-sdk-path) -lSystem -lc++ \
  test.o -o test --bp-compression-sort=both
llvm-objdump --dwarf=frames test
# Without fix: "error: parsing FDE data at 0x0 failed due to missing CIE"
# With fix: CIE records correctly precede their FDEs
```

Standalone reproducer with 64-bit CIE simulation script:
https://gist.github.com/karim-alweheshy/ae28196c4fbb295f81cc793cfbe0c1b7

## Test

The lit test creates multiple functions with `cfi_escape` (forcing DWARF
unwind mode) and different personality functions (producing separate
CIEs), then links with `--bp-compression-sort=both`. Verified on both
x86_64 and arm64.

Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Karim Alweheshy <karim.alweheshy at reddit.com>
Co-authored-by: Ellis Hoag <ellis.sparky.hoag at gmail.com>

Added: 
    lld/test/MachO/eh-frame-ordering.s

Modified: 
    lld/MachO/BPSectionOrderer.cpp

Removed: 
    


################################################################################
diff  --git a/lld/MachO/BPSectionOrderer.cpp b/lld/MachO/BPSectionOrderer.cpp
index a9b5d07ac55e5..11f48278a64bb 100644
--- a/lld/MachO/BPSectionOrderer.cpp
+++ b/lld/MachO/BPSectionOrderer.cpp
@@ -8,6 +8,7 @@
 
 #include "BPSectionOrderer.h"
 #include "InputSection.h"
+#include "OutputSegment.h"
 #include "Relocations.h"
 #include "Symbols.h"
 #include "lld/Common/BPSectionOrdererBase.inc"
@@ -122,6 +123,9 @@ DenseMap<const InputSection *, int> lld::macho::runBalancedPartitioning(
   DenseMap<CachedHashStringRef, std::set<unsigned>> rootSymbolToSectionIdxs;
   for (const auto *file : inputFiles) {
     for (auto *sec : file->sections) {
+      if (sec->name == section_names::ehFrame &&
+          sec->segname == segment_names::text)
+        continue;
       for (auto &subsec : sec->subsections) {
         auto *isec = subsec.isec;
         if (!isec || isec->data.empty() || !isec->data.data())

diff  --git a/lld/test/MachO/eh-frame-ordering.s b/lld/test/MachO/eh-frame-ordering.s
new file mode 100644
index 0000000000000..8c6d9986b5f9f
--- /dev/null
+++ b/lld/test/MachO/eh-frame-ordering.s
@@ -0,0 +1,107 @@
+# REQUIRES: x86, aarch64
+## Test that __eh_frame CIE/FDE ordering is preserved even when
+## priority-based section sorting (from BP compression sort, order files,
+## etc.) would otherwise reorder input sections. CIE records must precede
+## the FDE records that reference them; reordering breaks CIE pointer
+## resolution.
+
+## x86_64
+# RUN: llvm-mc -filetype=obj -emit-compact-unwind-non-canonical=true -triple=x86_64-apple-macos10.15 %s -o %t-x86_64.o
+# RUN: %lld -lSystem -lc++ %t-x86_64.o -o %t-x86_64 --bp-compression-sort=both
+# RUN: llvm-objdump --dwarf=frames %t-x86_64 2>&1 | FileCheck %s --implicit-check-not=error --implicit-check-not=warning
+
+## arm64
+# RUN: llvm-mc -filetype=obj -emit-compact-unwind-non-canonical=true -triple=arm64-apple-macos11.0 %s -o %t-arm64.o
+# RUN: %lld -arch arm64 -lSystem -lc++ %t-arm64.o -o %t-arm64 --bp-compression-sort=both
+# RUN: llvm-objdump --dwarf=frames %t-arm64 2>&1 | FileCheck %s --implicit-check-not=error --implicit-check-not=warning
+
+## Verify that __eh_frame starts with a CIE (not an FDE), contains both
+## CIEs and FDEs, and that no parse errors occur. The test uses two
+## personality functions, producing two CIE groups (CIE_A + FDEs, CIE_B +
+## FDEs). The --implicit-check-not flags above ensure no FDE fails to
+## resolve its CIE pointer.
+# CHECK: .eh_frame contents:
+# CHECK: {{[0-9a-f]+}} {{.*}} CIE
+# CHECK: {{[0-9a-f]+}} {{.*}} FDE
+# CHECK: {{[0-9a-f]+}} {{.*}} FDE
+# CHECK: {{[0-9a-f]+}} {{.*}} FDE
+# CHECK: {{[0-9a-f]+}} {{.*}} FDE
+
+.globl _my_personality_a, _my_personality_b, _main
+
+.text
+## _func_a uses cfi_escape to force DWARF unwind (can't be compact-encoded).
+## Uses personality A -> produces CIE_A + FDE for _func_a.
+.p2align 2
+_func_a:
+  .cfi_startproc
+  .cfi_personality 155, _my_personality_a
+  .cfi_lsda 16, Lexception_a
+  .cfi_def_cfa_offset 16
+  .cfi_escape 0x2e, 0x10
+  ret
+  .cfi_endproc
+
+## _func_b also uses personality A + cfi_escape -> reuses CIE_A, new FDE.
+.p2align 2
+_func_b:
+  .cfi_startproc
+  .cfi_personality 155, _my_personality_a
+  .cfi_lsda 16, Lexception_b
+  .cfi_def_cfa_offset 16
+  .cfi_escape 0x2e, 0x10
+  ret
+  .cfi_endproc
+
+## _func_c uses personality B + cfi_escape -> produces CIE_B + FDE.
+.p2align 2
+_func_c:
+  .cfi_startproc
+  .cfi_personality 155, _my_personality_b
+  .cfi_lsda 16, Lexception_c
+  .cfi_def_cfa_offset 16
+  .cfi_escape 0x2e, 0x10
+  ret
+  .cfi_endproc
+
+## _func_d uses personality B + cfi_escape -> reuses CIE_B, new FDE.
+.p2align 2
+_func_d:
+  .cfi_startproc
+  .cfi_personality 155, _my_personality_b
+  .cfi_lsda 16, Lexception_d
+  .cfi_def_cfa_offset 16
+  .cfi_escape 0x2e, 0x10
+  ret
+  .cfi_endproc
+
+.p2align 2
+_my_personality_a:
+  ret
+
+.p2align 2
+_my_personality_b:
+  ret
+
+.p2align 2
+_main:
+  ret
+
+.section __TEXT,__gcc_except_tab
+GCC_except_table_a:
+Lexception_a:
+  .byte 255
+
+GCC_except_table_b:
+Lexception_b:
+  .byte 255
+
+GCC_except_table_c:
+Lexception_c:
+  .byte 255
+
+GCC_except_table_d:
+Lexception_d:
+  .byte 255
+
+.subsections_via_symbols


        


More information about the llvm-branch-commits mailing list