[Lldb-commits] [lldb] [llvm] [llvm][lldb] Support GNU-compressed DWARF in Mach-O (PR #212597)

via lldb-commits lldb-commits at lists.llvm.org
Tue Jul 28 12:53:21 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: Li Jie (cpunion)

<details>
<summary>Changes</summary>

## Summary

Go's Darwin linker emits DWARF in Mach-O `__zdebug_*` sections by default. These sections use the GNU compression header (`ZLIB` followed by a big-endian 64-bit uncompressed size), while LLVM currently only handles the ELF compression header.

This change:

- recognizes GNU-style compressed Mach-O DWARF sections and normalizes their names for `DWARFContext`;
- restores GNU compression header decoding in the shared object decompressor without changing ELF section detection;
- makes LLDB decompress both full and partial reads of `__zdebug_*` sections.

## Testing

- `ObjectFileMachOTests`: 30/30 on macOS, 28/28 on Linux arm64
- `llvm-lit llvm/test/DebugInfo/MachO/gnu-compressed-sections.yaml`: passed on macOS and Linux arm64, including the corrupt-header case
- Real Go 1.26.5 Darwin executable with the default six `__zdebug_*` sections: `llvm-dwarfdump` reads `DW_LANG_Go` and the producer, and LLDB resolves a source breakpoint and reads parameters and locals


---
Full diff: https://github.com/llvm/llvm-project/pull/212597.diff


8 Files Affected:

- (modified) lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp (+56-1) 
- (modified) lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h (+7) 
- (modified) lldb/unittests/ObjectFile/MachO/TestObjectFileMachO.cpp (+67) 
- (modified) llvm/include/llvm/Object/Decompressor.h (+1) 
- (modified) llvm/lib/DebugInfo/DWARF/DWARFContext.cpp (+5-1) 
- (modified) llvm/lib/Object/Decompressor.cpp (+18-1) 
- (modified) llvm/lib/Object/MachOObjectFile.cpp (+6-1) 
- (added) llvm/test/DebugInfo/MachO/gnu-compressed-sections.yaml (+67) 


``````````diff
diff --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
index 9eab45f57f422..fb551248b27dc 100644
--- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
+++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
@@ -32,6 +32,7 @@
 #include "lldb/Target/ThreadList.h"
 #include "lldb/Utility/ArchSpec.h"
 #include "lldb/Utility/DataBuffer.h"
+#include "lldb/Utility/DataBufferHeap.h"
 #include "lldb/Utility/FileSpec.h"
 #include "lldb/Utility/FileSpecList.h"
 #include "lldb/Utility/LLDBLog.h"
@@ -46,6 +47,7 @@
 #include "lldb/Host/SafeMachO.h"
 
 #include "llvm/ADT/DenseSet.h"
+#include "llvm/Object/Decompressor.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/MemoryBuffer.h"
 
@@ -1011,6 +1013,58 @@ bool ObjectFileMachO::ParseHeader(DataExtractorSP &extractor_sp,
   return false;
 }
 
+size_t ObjectFileMachO::ReadSectionData(Section *section,
+                                        lldb::offset_t section_offset,
+                                        void *dst, size_t dst_len) {
+  if (section->GetObjectFile() != this)
+    return section->GetObjectFile()->ReadSectionData(section, section_offset,
+                                                     dst, dst_len);
+
+  if (!section->GetName().starts_with("__zdebug_"))
+    return ObjectFile::ReadSectionData(section, section_offset, dst, dst_len);
+
+  DataExtractor data;
+  ReadSectionData(section, data);
+  return data.CopyData(section_offset, dst_len, dst);
+}
+
+size_t ObjectFileMachO::ReadSectionData(Section *section,
+                                        DataExtractor &section_data) {
+  if (section->GetObjectFile() != this)
+    return section->GetObjectFile()->ReadSectionData(section, section_data);
+
+  size_t result = ObjectFile::ReadSectionData(section, section_data);
+  if (result == 0 || !section->GetName().starts_with("__zdebug_"))
+    return result;
+
+  auto decompressor = llvm::object::Decompressor::create(
+      section->GetName(),
+      {reinterpret_cast<const char *>(section_data.GetDataStart()),
+       size_t(section_data.GetByteSize())},
+      GetByteOrder() == eByteOrderLittle, GetAddressByteSize() == 8);
+  if (!decompressor) {
+    GetModule()->ReportWarning(
+        "unable to initialize decompressor for section '{0}': {1}",
+        section->GetName(), llvm::toString(decompressor.takeError()).c_str());
+    section_data.Clear();
+    return 0;
+  }
+
+  auto buffer_sp =
+      std::make_shared<DataBufferHeap>(decompressor->getDecompressedSize(), 0);
+  if (auto error = decompressor->decompress(
+          {buffer_sp->GetBytes(), size_t(buffer_sp->GetByteSize())})) {
+    GetModule()->ReportWarning("decompression of section '{0}' failed: {1}",
+                               section->GetName(),
+                               llvm::toString(std::move(error)).c_str());
+    section_data.Clear();
+    return 0;
+  }
+
+  section_data.SetData(buffer_sp);
+  return buffer_sp->GetByteSize();
+}
+
 bool ObjectFileMachO::ParseHeader() {
   ModuleSP module_sp(GetModule());
   if (!module_sp)
@@ -1464,7 +1518,8 @@ static lldb::SectionType GetSectionType(uint32_t flags,
     return eSectionTypeDWARFDebugStrOffsetsDwo;
 
   llvm::StringRef stripped_name = section_name.GetStringRef();
-  if (stripped_name.consume_front("__debug_"))
+  if (stripped_name.consume_front("__debug_") ||
+      stripped_name.consume_front("__zdebug_"))
     return ObjectFile::GetDWARFSectionTypeFromName(stripped_name);
 
   if (section_name == g_sect_name_dwarf_apple_names)
diff --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h
index 4a0b9041b2f87..84605972e5d6a 100644
--- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h
+++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h
@@ -102,6 +102,13 @@ class ObjectFileMachO : public lldb_private::ObjectFile {
 
   void CreateSections(lldb_private::SectionList &unified_section_list) override;
 
+  size_t ReadSectionData(lldb_private::Section *section,
+                         lldb::offset_t section_offset, void *dst,
+                         size_t dst_len) override;
+
+  size_t ReadSectionData(lldb_private::Section *section,
+                         lldb_private::DataExtractor &section_data) override;
+
   void Dump(lldb_private::Stream *s) override;
 
   lldb_private::ArchSpec GetArchitecture() override;
diff --git a/lldb/unittests/ObjectFile/MachO/TestObjectFileMachO.cpp b/lldb/unittests/ObjectFile/MachO/TestObjectFileMachO.cpp
index b3a238022aa57..d90b342bf146c 100644
--- a/lldb/unittests/ObjectFile/MachO/TestObjectFileMachO.cpp
+++ b/lldb/unittests/ObjectFile/MachO/TestObjectFileMachO.cpp
@@ -19,6 +19,7 @@
 #include "lldb/Symbol/Symtab.h"
 #include "lldb/Utility/FileSpec.h"
 #include "lldb/lldb-defines.h"
+#include "llvm/Config/llvm-config.h"
 #include "llvm/Testing/Support/Error.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
@@ -37,6 +38,72 @@ class ObjectFileMachOTest : public ::testing::Test {
 };
 } // namespace
 
+#if LLVM_ENABLE_ZLIB
+TEST_F(ObjectFileMachOTest, GNUCompressedSection) {
+  const char *yamldata = R"(
+--- !mach-o
+FileHeader:
+  magic:           0xFEEDFACF
+  cputype:         0x0100000C
+  cpusubtype:      0x00000000
+  filetype:        0x00000001
+  ncmds:           1
+  sizeofcmds:      152
+  flags:           0x00000000
+  reserved:        0x00000000
+LoadCommands:
+  - cmd:             LC_SEGMENT_64
+    cmdsize:         152
+    segname:         __DWARF
+    vmaddr:          0
+    vmsize:          23
+    fileoff:         184
+    filesize:        23
+    maxprot:         7
+    initprot:        3
+    nsects:          1
+    flags:           0
+    Sections:
+      - sectname:        __zdebug_line
+        segname:         __DWARF
+        addr:            0
+        size:            23
+        offset:          184
+        align:           0
+        reloff:          0
+        nreloc:          0
+        flags:           0x02000000
+        reserved1:       0
+        reserved2:       0
+        reserved3:       0
+        content:         5A4C49420000000000000003789C4B4C4A0600024D0127
+...
+)";
+
+  llvm::Expected<TestFile> file = TestFile::fromYaml(yamldata);
+  ASSERT_THAT_EXPECTED(file, llvm::Succeeded());
+  lldb::ModuleSP module = std::make_shared<Module>(file->moduleSpec());
+  ObjectFile *object = module->GetObjectFile();
+  ASSERT_TRUE(llvm::isa<ObjectFileMachO>(object));
+
+  SectionSP line = object->GetSectionList()->FindSectionByType(
+      eSectionTypeDWARFDebugLine, true);
+  ASSERT_TRUE(line);
+  EXPECT_EQ(line->GetName(), ConstString("__zdebug_line"));
+
+  lldb_private::DataExtractor data;
+  EXPECT_EQ(line->GetSectionData(data), 3u);
+  ASSERT_EQ(data.GetByteSize(), 3u);
+  EXPECT_EQ(
+      llvm::StringRef(reinterpret_cast<const char *>(data.GetDataStart()), 3),
+      "abc");
+
+  char tail[2];
+  EXPECT_EQ(object->ReadSectionData(line.get(), 1, tail, sizeof(tail)), 2u);
+  EXPECT_EQ(llvm::StringRef(tail, sizeof(tail)), "bc");
+}
+#endif
+
 #if defined(__APPLE__)
 TEST_F(ObjectFileMachOTest, ModuleFromSharedCacheInfo) {
   ArchSpec arch("arm64-apple-macosx-");
diff --git a/llvm/include/llvm/Object/Decompressor.h b/llvm/include/llvm/Object/Decompressor.h
index 852b71ac86a55..362f66d540314 100644
--- a/llvm/include/llvm/Object/Decompressor.h
+++ b/llvm/include/llvm/Object/Decompressor.h
@@ -45,6 +45,7 @@ class Decompressor {
 private:
   Decompressor(StringRef Data);
 
+  Error consumeCompressedGNUHeader();
   Error consumeCompressedHeader(bool Is64Bit, bool IsLittleEndian);
 
   StringRef SectionData;
diff --git a/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp b/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp
index 70ce257a5804a..536dc75ba2836 100644
--- a/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp
+++ b/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp
@@ -2100,7 +2100,7 @@ class DWARFObjInMemory final : public DWARFObject {
 
   /// If Sec is compressed section, decompresses and updates its contents
   /// provided by Data. Otherwise leaves it unchanged.
-  Error maybeDecompress(const object::SectionRef &Sec, StringRef Name,
+  Error maybeDecompress(const object::SectionRef &Sec, StringRef &Name,
                         StringRef &Data) {
     if (!Sec.isCompressed())
       return Error::success();
@@ -2117,6 +2117,10 @@ class DWARFObjInMemory final : public DWARFObject {
     UncompressedSections.push_back(std::move(Out));
     Data = UncompressedSections.back();
 
+    StringRef NormalizedName = Name.ltrim("._");
+    if (NormalizedName.starts_with("zdebug_"))
+      Name = NormalizedName.drop_front();
+
     return Error::success();
   }
 
diff --git a/llvm/lib/Object/Decompressor.cpp b/llvm/lib/Object/Decompressor.cpp
index 9022566a97f35..826b9b6e0ec55 100644
--- a/llvm/lib/Object/Decompressor.cpp
+++ b/llvm/lib/Object/Decompressor.cpp
@@ -21,7 +21,9 @@ using namespace object;
 Expected<Decompressor> Decompressor::create(StringRef Name, StringRef Data,
                                             bool IsLE, bool Is64Bit) {
   Decompressor D(Data);
-  if (Error Err = D.consumeCompressedHeader(Is64Bit, IsLE))
+  const bool IsGNUStyle = Name.ltrim("._").starts_with("zdebug_");
+  if (Error Err = IsGNUStyle ? D.consumeCompressedGNUHeader()
+                             : D.consumeCompressedHeader(Is64Bit, IsLE))
     return std::move(Err);
   return D;
 }
@@ -29,6 +31,21 @@ Expected<Decompressor> Decompressor::create(StringRef Name, StringRef Data,
 Decompressor::Decompressor(StringRef Data)
     : SectionData(Data), DecompressedSize(0) {}
 
+Error Decompressor::consumeCompressedGNUHeader() {
+  if (!SectionData.starts_with("ZLIB"))
+    return createError("corrupted compressed section header");
+  if (const char *Reason =
+          llvm::compression::getReasonIfUnsupported(compression::Format::Zlib))
+    return createError(Reason);
+  if (SectionData.size() < 12)
+    return createError("corrupted uncompressed section size");
+
+  CompressionType = DebugCompressionType::Zlib;
+  DecompressedSize = read64be(SectionData.data() + 4);
+  SectionData = SectionData.substr(12);
+  return Error::success();
+}
+
 Error Decompressor::consumeCompressedHeader(bool Is64Bit, bool IsLittleEndian) {
   using namespace ELF;
   uint64_t HdrSize = Is64Bit ? sizeof(Elf64_Chdr) : sizeof(Elf32_Chdr);
diff --git a/llvm/lib/Object/MachOObjectFile.cpp b/llvm/lib/Object/MachOObjectFile.cpp
index 3342c99e245ca..f3c372b88caea 100644
--- a/llvm/lib/Object/MachOObjectFile.cpp
+++ b/llvm/lib/Object/MachOObjectFile.cpp
@@ -2057,7 +2057,12 @@ Expected<SectionRef> MachOObjectFile::getSection(StringRef SectionName) const {
 }
 
 bool MachOObjectFile::isSectionCompressed(DataRefImpl Sec) const {
-  return false;
+  Expected<StringRef> NameOrErr = getSectionName(Sec);
+  if (!NameOrErr) {
+    consumeError(NameOrErr.takeError());
+    return false;
+  }
+  return NameOrErr->ltrim("._").starts_with("zdebug_");
 }
 
 bool MachOObjectFile::isSectionText(DataRefImpl Sec) const {
diff --git a/llvm/test/DebugInfo/MachO/gnu-compressed-sections.yaml b/llvm/test/DebugInfo/MachO/gnu-compressed-sections.yaml
new file mode 100644
index 0000000000000..0ddcc50ace1cd
--- /dev/null
+++ b/llvm/test/DebugInfo/MachO/gnu-compressed-sections.yaml
@@ -0,0 +1,67 @@
+# REQUIRES: zlib
+# RUN: yaml2obj %s -o %t \
+# RUN:   -DINFO=5A4C4942000000000000000C789CE3606060606100010E0600009C0015
+# RUN: llvm-dwarfdump --debug-info %t | FileCheck %s
+# RUN: yaml2obj %s -o %t.invalid \
+# RUN:   -DINFO=4241442100000000000000000000000000000000000000000000000000
+# RUN: not llvm-dwarfdump --debug-info %t.invalid 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=ERR
+
+# CHECK: .debug_info contents:
+# CHECK: Compile Unit: length = 0x00000008
+# CHECK-SAME: version = 0x0004
+# CHECK-SAME: addr_size = 0x08
+
+# ERR: error: failed to decompress
+# ERR-SAME: corrupted compressed section header
+
+--- !mach-o
+FileHeader:
+  magic:           0xFEEDFACF
+  cputype:         0x0100000C
+  cpusubtype:      0x00000000
+  filetype:        0x00000001
+  ncmds:           1
+  sizeofcmds:      232
+  flags:           0x00002000
+  reserved:        0x00000000
+LoadCommands:
+  - cmd:             LC_SEGMENT_64
+    cmdsize:         232
+    segname:         __DWARF
+    vmaddr:          0
+    vmsize:          50
+    fileoff:         264
+    filesize:        50
+    maxprot:         7
+    initprot:        3
+    nsects:          2
+    flags:           0
+    Sections:
+      - sectname:        __zdebug_info
+        segname:         __DWARF
+        addr:            0
+        size:            29
+        offset:          264
+        align:           0
+        reloff:          0
+        nreloc:          0
+        flags:           0x02000000
+        reserved1:       0
+        reserved2:       0
+        reserved3:       0
+        content:         '[[INFO]]'
+      - sectname:        __zdebug_abbrev
+        segname:         __DWARF
+        addr:            29
+        size:            21
+        offset:          293
+        align:           0
+        reloff:          0
+        nreloc:          0
+        flags:           0x02000000
+        reserved1:       0
+        reserved2:       0
+        reserved3:       0
+        content:         5A4C49420000000000000001789C63000000010001
+...

``````````

</details>


https://github.com/llvm/llvm-project/pull/212597


More information about the lldb-commits mailing list