[lld] [lld-macho] Fix crash with DWARF section-relative relocations (PR #168075)
Joel Reymont via llvm-commits
llvm-commits at lists.llvm.org
Fri Nov 28 06:37:10 PST 2025
https://github.com/joelreymont updated https://github.com/llvm/llvm-project/pull/168075
>From 36050dfacd09de48dc3f51fd92ab3cd3c6cc7644 Mon Sep 17 00:00:00 2001
From: Joel Reymont <18791+joelreymont at users.noreply.github.com>
Date: Fri, 14 Nov 2025 17:29:05 +0200
Subject: [PATCH 1/2] [lld-macho] Fix crash with DWARF section-relative
relocations
DWARF sections with section-relative relocations (e.g., DW_FORM_strp) caused
the linker to crash. Fixed by adding DWARF sections to subsections for
relocation processing while marking them live=false to preserve MachO's
traditional STABS-only debug output behavior.
---
lld/MachO/InputFiles.cpp | 10 ++--
lld/test/MachO/dwarf-strp-relocations.s | 63 +++++++++++++++++++++++++
2 files changed, 70 insertions(+), 3 deletions(-)
create mode 100644 lld/test/MachO/dwarf-strp-relocations.s
diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp
index d0128d03a9eab..2324a83868eeb 100644
--- a/lld/MachO/InputFiles.cpp
+++ b/lld/MachO/InputFiles.cpp
@@ -411,10 +411,14 @@ void ObjFile::parseSections(ArrayRef<SectionHeader> sectionHeaders) {
auto *isec = make<ConcatInputSection>(section, data, align);
if (isDebugSection(isec->getFlags()) &&
isec->getSegName() == segment_names::dwarf) {
- // Instead of emitting DWARF sections, we emit STABS symbols to the
- // object files that contain them. We filter them out early to avoid
- // parsing their relocations unnecessarily.
+ // Keep debug sections in debugSections for diagnostic purposes
debugSections.push_back(isec);
+ // Add DWARF sections to subsections so their relocations are processed,
+ // but mark them dead so they aren't emitted (MachO uses STABS, not DWARF).
+ // This fixes crashes with section-relative relocations (e.g., DW_FORM_strp)
+ // while maintaining MachO's traditional STABS-only debug output.
+ isec->live = false;
+ section.subsections.push_back({0, isec});
} else {
section.subsections.push_back({0, isec});
}
diff --git a/lld/test/MachO/dwarf-strp-relocations.s b/lld/test/MachO/dwarf-strp-relocations.s
new file mode 100644
index 0000000000000..bc873d6c50a0c
--- /dev/null
+++ b/lld/test/MachO/dwarf-strp-relocations.s
@@ -0,0 +1,63 @@
+# REQUIRES: x86
+# RUN: rm -rf %t; split-file %s %t
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/test.s -o %t/test.o
+# RUN: %lld -dylib %t/test.o -o %t/test.dylib
+# RUN: llvm-objdump --section-headers %t/test.dylib | FileCheck %s
+
+## Test that lld can handle section-relative relocations in DWARF sections,
+## specifically DW_FORM_strp which creates X86_64_RELOC_UNSIGNED relocations
+## to the __debug_str section. This previously caused linker crashes with
+## "malformed relocation" errors on macOS.
+##
+## The test verifies that:
+## 1. The link completes successfully without crashing (key requirement)
+## 2. DWARF sections are processed for relocations but not emitted to output
+## (MachO traditionally uses STABS, not DWARF, for debug info)
+##
+## Negative checks ensure DWARF sections are NOT in the final binary, preventing
+## regression where they might be accidentally emitted.
+
+# CHECK-NOT: __debug_info
+# CHECK-NOT: __debug_abbrev
+# CHECK-NOT: __debug_str
+
+#--- test.s
+.section __TEXT,__text,regular,pure_instructions
+.globl _main
+_main:
+ movl $42, %eax
+ retq
+
+.section __DWARF,__debug_abbrev,regular,debug
+Labbrev_begin:
+ .byte 1 ## Abbrev code
+ .byte 17 ## DW_TAG_compile_unit
+ .byte 1 ## DW_CHILDREN_yes
+ .byte 37 ## DW_AT_producer
+ .byte 14 ## DW_FORM_strp (string table pointer!)
+ .byte 3 ## DW_AT_name
+ .byte 14 ## DW_FORM_strp
+ .byte 0 ## End attributes
+ .byte 0
+ .byte 0 ## End abbrev table
+
+.section __DWARF,__debug_info,regular,debug
+Linfo_begin:
+ .long Linfo_end - Linfo_begin - 4 ## Length
+ .short 4 ## DWARF version 4
+ .long 0 ## Abbrev offset
+ .byte 8 ## Address size
+ .byte 1 ## Abbrev code
+ ## These .long directives create section-relative relocations (X86_64_RELOC_UNSIGNED)
+ ## to the __debug_str section. This is the critical test case.
+ .long Lproducer - Ldebug_str ## DW_AT_producer (section-relative!)
+ .long Lfilename - Ldebug_str ## DW_AT_name (section-relative!)
+Linfo_end:
+
+.section __DWARF,__debug_str,regular,debug
+Ldebug_str:
+Lproducer:
+ .asciz "Test Producer 1.0"
+Lfilename:
+ .asciz "test.c"
>From 0e4a63dcd190b318ba4e7a947a27e59d31ef11f3 Mon Sep 17 00:00:00 2001
From: Joel Reymont <18791+joelreymont at users.noreply.github.com>
Date: Fri, 28 Nov 2025 15:31:47 +0200
Subject: [PATCH 2/2] [lld-macho] Preserve DWARF sections for reloc rewriting
---
lld/MachO/Dwarf.cpp | 29 ++++++++++++++++-------------
lld/MachO/InputFiles.cpp | 17 ++++++-----------
lld/MachO/InputFiles.h | 1 -
3 files changed, 22 insertions(+), 25 deletions(-)
diff --git a/lld/MachO/Dwarf.cpp b/lld/MachO/Dwarf.cpp
index 47dc51e6196d1..bcabff3d51f3e 100644
--- a/lld/MachO/Dwarf.cpp
+++ b/lld/MachO/Dwarf.cpp
@@ -25,19 +25,22 @@ std::unique_ptr<DwarfObject> DwarfObject::create(ObjFile *obj) {
// to get that path. The debugger will locate the debug info via the object
// file paths that we emit in our STABS symbols, so we don't need to process &
// emit them ourselves.
- for (const InputSection *isec : obj->debugSections) {
- if (StringRef *s =
- StringSwitch<StringRef *>(isec->getName())
- .Case(section_names::debugInfo, &dObj->infoSection.Data)
- .Case(section_names::debugLine, &dObj->lineSection.Data)
- .Case(section_names::debugStrOffs, &dObj->strOffsSection.Data)
- .Case(section_names::debugAbbrev, &dObj->abbrevSection)
- .Case(section_names::debugStr, &dObj->strSection)
- .Default(nullptr)) {
- *s = toStringRef(isec->data);
- hasDwarfInfo = true;
- }
- }
+ for (const Section *sec : obj->sections)
+ if (sec->segname == segment_names::dwarf)
+ for (const Subsection &subsec : sec->subsections)
+ if (const InputSection *isec = subsec.isec)
+ if (StringRef *s =
+ StringSwitch<StringRef *>(isec->getName())
+ .Case(section_names::debugInfo, &dObj->infoSection.Data)
+ .Case(section_names::debugLine, &dObj->lineSection.Data)
+ .Case(section_names::debugStrOffs,
+ &dObj->strOffsSection.Data)
+ .Case(section_names::debugAbbrev, &dObj->abbrevSection)
+ .Case(section_names::debugStr, &dObj->strSection)
+ .Default(nullptr)) {
+ *s = toStringRef(isec->data);
+ hasDwarfInfo = true;
+ }
if (hasDwarfInfo)
return dObj;
diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp
index 2324a83868eeb..f9fc7aa97f343 100644
--- a/lld/MachO/InputFiles.cpp
+++ b/lld/MachO/InputFiles.cpp
@@ -409,19 +409,14 @@ void ObjFile::parseSections(ArrayRef<SectionHeader> sectionHeaders) {
addrSigSection = sections.back();
auto *isec = make<ConcatInputSection>(section, data, align);
+ // Keep DWARF sections long enough for relocation rewriting; dropping them
+ // here leaves section-relative relocations (e.g. DW_FORM_strp into
+ // __debug_str) dangling and crashes the linker. Mark them dead so they
+ // are not emitted; Mach-O keeps STABS-only debug output.
if (isDebugSection(isec->getFlags()) &&
- isec->getSegName() == segment_names::dwarf) {
- // Keep debug sections in debugSections for diagnostic purposes
- debugSections.push_back(isec);
- // Add DWARF sections to subsections so their relocations are processed,
- // but mark them dead so they aren't emitted (MachO uses STABS, not DWARF).
- // This fixes crashes with section-relative relocations (e.g., DW_FORM_strp)
- // while maintaining MachO's traditional STABS-only debug output.
+ isec->getSegName() == segment_names::dwarf)
isec->live = false;
- section.subsections.push_back({0, isec});
- } else {
- section.subsections.push_back({0, isec});
- }
+ section.subsections.push_back({0, isec});
}
}
}
diff --git a/lld/MachO/InputFiles.h b/lld/MachO/InputFiles.h
index 2d5bceb160445..92da714027e8b 100644
--- a/lld/MachO/InputFiles.h
+++ b/lld/MachO/InputFiles.h
@@ -181,7 +181,6 @@ class ObjFile final : public InputFile {
const uint32_t modTime;
bool forceHidden;
bool builtFromBitcode;
- std::vector<ConcatInputSection *> debugSections;
std::vector<CallGraphEntry> callGraph;
llvm::DenseMap<ConcatInputSection *, FDE> fdes;
std::vector<AliasSymbol *> aliases;
More information about the llvm-commits
mailing list