[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 §ion_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 §ion_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