[lld] [lld][MachO] Preserve __eh_frame ordering during BP section sorting (PR #191412)
Karim Alweheshy via llvm-commits
llvm-commits at lists.llvm.org
Thu Apr 16 08:16:41 PDT 2026
https://github.com/karim-alweheshy updated https://github.com/llvm/llvm-project/pull/191412
>From d281929d2ea39da24ba5fab142037d730c15464c Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <karim.alweheshy at reddit.com>
Date: Fri, 10 Apr 2026 15:07:08 +0200
Subject: [PATCH 1/2] [lld][MachO] Preserve __eh_frame ordering during BP
section sorting
The Balanced Partitioning section orderer collects all live
ConcatInputSections 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.
Add a Section::orderSensitive flag to mark sections whose subsection
ordering is semantically meaningful. Set it in splitEhFrames() and
check it in the BP orderer's outer loop, skipping the entire section
before any per-subsection work.
Note: -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.
Made-with: Cursor
---
lld/MachO/BPSectionOrderer.cpp | 2 +
lld/MachO/InputFiles.cpp | 1 +
lld/MachO/InputFiles.h | 4 ++
lld/test/MachO/eh-frame-ordering.s | 102 +++++++++++++++++++++++++++++
4 files changed, 109 insertions(+)
create mode 100644 lld/test/MachO/eh-frame-ordering.s
diff --git a/lld/MachO/BPSectionOrderer.cpp b/lld/MachO/BPSectionOrderer.cpp
index a9b5d07ac55e5..7eea27430461a 100644
--- a/lld/MachO/BPSectionOrderer.cpp
+++ b/lld/MachO/BPSectionOrderer.cpp
@@ -122,6 +122,8 @@ 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->orderSensitive)
+ continue;
for (auto &subsec : sec->subsections) {
auto *isec = subsec.isec;
if (!isec || isec->data.empty() || !isec->data.data())
diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp
index cc7eae51175bc..62823ab4a6380 100644
--- a/lld/MachO/InputFiles.cpp
+++ b/lld/MachO/InputFiles.cpp
@@ -447,6 +447,7 @@ void ObjFile::splitEhFrames(ArrayRef<uint8_t> data, Section &ehFrameSection) {
/*align=*/1)});
}
ehFrameSection.doneSplitting = true;
+ ehFrameSection.orderSensitive = true;
}
template <class T>
diff --git a/lld/MachO/InputFiles.h b/lld/MachO/InputFiles.h
index 8f2cba89abf48..735fb40af24bd 100644
--- a/lld/MachO/InputFiles.h
+++ b/lld/MachO/InputFiles.h
@@ -81,6 +81,10 @@ class Section {
Section(Section &&) = delete;
Section &operator=(Section &&) = delete;
+ // Subsection ordering is semantically meaningful and must not be reordered
+ // by priority-based sorting (e.g. __eh_frame CIE/FDE records).
+ bool orderSensitive = false;
+
private:
// Whether we have already split this section into individual subsections.
// For sections that cannot be split (e.g. literal sections), this is always
diff --git a/lld/test/MachO/eh-frame-ordering.s b/lld/test/MachO/eh-frame-ordering.s
new file mode 100644
index 0000000000000..3b927b1cafc5f
--- /dev/null
+++ b/lld/test/MachO/eh-frame-ordering.s
@@ -0,0 +1,102 @@
+# 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 CIE records precede their FDE records and that
+## FDE records successfully reference their CIEs (no parse errors).
+# CHECK: .eh_frame contents:
+# CHECK: {{[0-9a-f]+}} {{.*}} CIE
+# 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
>From f22f11a91af8b42de6be3b8bddd7a5225066c824 Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <9082720+karim-alweheshy at users.noreply.github.com>
Date: Thu, 16 Apr 2026 17:16:29 +0200
Subject: [PATCH 2/2] Address review feedback for eh-frame-ordering test
- Change REQUIRES to `x86, aarch64` (test needs both backends)
- Add CHECK-NOT: CIE between and after FDE lines to verify no
CIE appears after the first FDE in the output
---
lld/test/MachO/eh-frame-ordering.s | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/lld/test/MachO/eh-frame-ordering.s b/lld/test/MachO/eh-frame-ordering.s
index 3b927b1cafc5f..e366d1f009e5a 100644
--- a/lld/test/MachO/eh-frame-ordering.s
+++ b/lld/test/MachO/eh-frame-ordering.s
@@ -1,4 +1,4 @@
-# REQUIRES: x86 || aarch64
+# 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
@@ -15,12 +15,14 @@
# 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 CIE records precede their FDE records and that
-## FDE records successfully reference their CIEs (no parse errors).
+## Verify that CIE records precede their FDE records, that no CIE
+## appears after the first FDE, and that no parse errors occur.
# CHECK: .eh_frame contents:
# CHECK: {{[0-9a-f]+}} {{.*}} CIE
# CHECK: {{[0-9a-f]+}} {{.*}} FDE
+# CHECK-NOT: CIE
# CHECK: {{[0-9a-f]+}} {{.*}} FDE
+# CHECK-NOT: CIE
.globl _my_personality_a, _my_personality_b, _main
More information about the llvm-commits
mailing list