[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