[Lldb-commits] [lldb] [llvm] [gsymutil] Fix crash caused by infinite recursion in `unwrapReferencedTypedefType()` (PR #196448)
Roy Shi via lldb-commits
lldb-commits at lists.llvm.org
Fri May 8 12:53:44 PDT 2026
https://github.com/royitaqi updated https://github.com/llvm/llvm-project/pull/196448
>From b0171e9428db3628823a1de6bbb657f99df32075 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Thu, 7 May 2026 16:36:21 -0700
Subject: [PATCH 1/6] Add a test to repro the problem
---
llvm/unittests/DebugInfo/GSYM/GSYMTest.cpp | 156 +++++++++++++++++++++
1 file changed, 156 insertions(+)
diff --git a/llvm/unittests/DebugInfo/GSYM/GSYMTest.cpp b/llvm/unittests/DebugInfo/GSYM/GSYMTest.cpp
index 299e19e3c896e..b7abdd94ce645 100644
--- a/llvm/unittests/DebugInfo/GSYM/GSYMTest.cpp
+++ b/llvm/unittests/DebugInfo/GSYM/GSYMTest.cpp
@@ -9,6 +9,7 @@
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
+#include "llvm/DebugInfo/DWARF/DWARFTypePrinter.h"
#include "llvm/DebugInfo/GSYM/DwarfTransformer.h"
#include "llvm/DebugInfo/GSYM/ExtractRanges.h"
#include "llvm/DebugInfo/GSYM/FileEntry.h"
@@ -5687,3 +5688,158 @@ TEST(GSYMTest, TestMergedFunctionsInfoLargeOffsets) {
EXPECT_EQ(DecResult->MergedFunctions[0].Name, LargeName1);
EXPECT_EQ(DecResult->MergedFunctions[1].Name, LargeName2);
}
+
+TEST(GSYMTest, TestDWARFTypedefCycleDoesNotCrash) {
+ // Test that a self-referencing typedef cycle in DWARF does not cause
+ // infinite recursion in DWARFTypePrinter::unwrapReferencedTypedefType().
+ // This can happen when dsymutil's classic linker incorrectly deduplicates
+ // typedefs with the same name but different underlying types (e.g. from
+ // preferred_name), creating a typedef that points to itself.
+ //
+ // The crash path: DWARFTypePrinter::appendUnqualifiedNameBefore sees a
+ // DW_AT_name with the _STN| prefix (simplified template name), calls
+ // appendTemplateParameters, which for each DW_TAG_template_type_parameter
+ // calls unwrapReferencedTypedefType. With a cyclic typedef, this recurses
+ // infinitely.
+ //
+ // debug_info layout (DWARF32, AddrSize=8):
+ // 0x00: unit_length (4 bytes)
+ // 0x04: version=4 (2), abbrev_offset (4), addr_size=8 (1) = 7 bytes
+ // 0x0B: CU DIE (abbrev 1): strp(4) + addr(8) + addr(8) + data2(2) = 23
+ // 0x22: Subprogram DIE (abbrev 2): strp(4) + addr(8) + addr(8) = 21
+ // 0x37: Template param DIE (abbrev 3): strp(4) + ref4(4) = 9
+ // 0x40: null terminator (1 byte, end of subprogram children)
+ // 0x41: Typedef DIE (abbrev 4): strp(4) + ref4(4) = 9
+ // 0x4A: null terminator (1 byte, end of CU children)
+ //
+ // Template param's DW_AT_type -> 0x41 (typedef)
+ // Typedef's DW_AT_type -> 0x41 (self-referencing cycle)
+
+ // String table: "" (0x00), "/tmp/main.cpp" (0x01), "_STN|foo|<MyType>"
+ // (0x0F), "T" (0x21), "MyType" (0x23)
+ StringRef yamldata = R"(
+ debug_str:
+ - ''
+ - /tmp/main.cpp
+ - '_STN|foo|<MyType>'
+ - T
+ - MyType
+ debug_abbrev:
+ - Table:
+ - Code: 0x00000001
+ Tag: DW_TAG_compile_unit
+ Children: DW_CHILDREN_yes
+ Attributes:
+ - Attribute: DW_AT_name
+ Form: DW_FORM_strp
+ - Attribute: DW_AT_low_pc
+ Form: DW_FORM_addr
+ - Attribute: DW_AT_high_pc
+ Form: DW_FORM_addr
+ - Attribute: DW_AT_language
+ Form: DW_FORM_data2
+ - Code: 0x00000002
+ Tag: DW_TAG_subprogram
+ Children: DW_CHILDREN_yes
+ Attributes:
+ - Attribute: DW_AT_name
+ Form: DW_FORM_strp
+ - Attribute: DW_AT_low_pc
+ Form: DW_FORM_addr
+ - Attribute: DW_AT_high_pc
+ Form: DW_FORM_addr
+ - Code: 0x00000003
+ Tag: DW_TAG_template_type_parameter
+ Children: DW_CHILDREN_no
+ Attributes:
+ - Attribute: DW_AT_name
+ Form: DW_FORM_strp
+ - Attribute: DW_AT_type
+ Form: DW_FORM_ref4
+ - Code: 0x00000004
+ Tag: DW_TAG_typedef
+ Children: DW_CHILDREN_no
+ Attributes:
+ - Attribute: DW_AT_name
+ Form: DW_FORM_strp
+ - Attribute: DW_AT_type
+ Form: DW_FORM_ref4
+ debug_info:
+ - Version: 4
+ AddrSize: 8
+ Entries:
+ - AbbrCode: 0x00000001
+ Values:
+ - Value: 0x0000000000000001
+ - Value: 0x0000000000001000
+ - Value: 0x0000000000002000
+ - Value: 0x0000000000000004
+ - AbbrCode: 0x00000002
+ Values:
+ - Value: 0x000000000000000F
+ - Value: 0x0000000000001000
+ - Value: 0x0000000000002000
+ - AbbrCode: 0x00000003
+ Values:
+ - Value: 0x0000000000000021
+ - Value: 0x0000000000000041
+ - AbbrCode: 0x00000000
+ - AbbrCode: 0x00000004
+ Values:
+ - Value: 0x0000000000000023
+ - Value: 0x0000000000000041
+ - AbbrCode: 0x00000000
+ )";
+ auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
+ ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
+ std::unique_ptr<DWARFContext> DwarfContext =
+ DWARFContext::create(*ErrOrSections, 8);
+ ASSERT_TRUE(DwarfContext.get() != nullptr);
+
+ // Verify the typedef DIE is at offset 0x41 and self-references.
+ auto &CUDie = *DwarfContext->compile_units().begin();
+ DWARFDie CURoot = CUDie->getUnitDIE(false);
+ ASSERT_TRUE(CURoot.isValid());
+ // Walk children to find the typedef and verify the cycle.
+ bool FoundTypedef = false;
+ for (DWARFDie Child : CURoot.children()) {
+ if (Child.getTag() == dwarf::DW_TAG_typedef) {
+ EXPECT_EQ(Child.getOffset(), 0x41u);
+ auto TypeAttr = Child.find(dwarf::DW_AT_type);
+ ASSERT_TRUE(TypeAttr.has_value());
+ auto RefDie = Child.getAttributeValueAsReferencedDie(*TypeAttr);
+ EXPECT_EQ(RefDie.getOffset(), Child.getOffset());
+ FoundTypedef = true;
+ }
+ }
+ ASSERT_TRUE(FoundTypedef);
+
+ // Exercise DWARFTypePrinter on the subprogram with the _STN| name.
+ // appendUnqualifiedName -> appendTemplateParameters ->
+ // unwrapReferencedTypedefType must not infinitely recurse.
+ for (DWARFDie Child : CURoot.children()) {
+ if (Child.getTag() == dwarf::DW_TAG_subprogram) {
+ std::string Result;
+ raw_string_ostream StrOS(Result);
+ DWARFTypePrinter<DWARFDie>(StrOS).appendUnqualifiedName(Child);
+ EXPECT_FALSE(Result.empty());
+ }
+ }
+
+ // Also verify DwarfTransformer::convert() succeeds.
+ auto &OS = llvm::nulls();
+ OutputAggregator OSAgg(&OS);
+ GsymCreatorV1 GC;
+ DwarfTransformer DT(*DwarfContext, GC);
+ ASSERT_THAT_ERROR(DT.convert(1, OSAgg), Succeeded());
+ ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
+ SmallString<512> Str;
+ raw_svector_ostream OutStrm(Str);
+ FileWriter FW(OutStrm, llvm::endianness::native);
+ FW.setStringOffsetSize(GC.getStringOffsetSize());
+ ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
+ auto GROrErr = GsymReader::copyBuffer(OutStrm.str());
+ ASSERT_THAT_EXPECTED(GROrErr, Succeeded());
+ const std::unique_ptr<GsymReader> &GR = *GROrErr;
+ EXPECT_EQ(GR->getNumAddresses(), 1u);
+}
>From 764b1e4f0fd7cc21fa2a17e309cabdaf4439108e Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Thu, 7 May 2026 16:36:33 -0700
Subject: [PATCH 2/6] Fix the problem
---
.../include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h b/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
index 9986aaabf6ed4..ccb823f95b74a 100644
--- a/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
+++ b/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
@@ -147,6 +147,8 @@ void DWARFTypePrinter<DieType>::appendArrayType(const DieType &D) {
EndedWithTemplate = false;
}
+constexpr unsigned kMaxTypedefUnwrapDepth = 256;
+
namespace detail {
template <typename DieType>
DieType resolveReferencedType(DieType D,
@@ -169,8 +171,11 @@ const char *toString(std::optional<DWARFFormValueType> F) {
}
/// Resolve the DW_AT_type of \c D until we reach a DIE that is not a
-/// DW_TAG_typedef.
-template <typename DieType> DieType unwrapReferencedTypedefType(DieType D) {
+/// DW_TAG_typedef. Gives up after 256 typedefs to guard against cycles in
+/// malformed DWARF.
+template <typename DieType>
+DieType unwrapReferencedTypedefType(DieType D,
+ unsigned Depth = kMaxTypedefUnwrapDepth) {
auto TypeAttr = D.find(dwarf::DW_AT_type);
if (!TypeAttr)
return DieType();
@@ -179,8 +184,8 @@ template <typename DieType> DieType unwrapReferencedTypedefType(DieType D) {
if (!Unwrapped)
return DieType();
- if (Unwrapped.getTag() == dwarf::DW_TAG_typedef)
- return unwrapReferencedTypedefType(Unwrapped);
+ if (Unwrapped.getTag() == dwarf::DW_TAG_typedef && Depth > 0)
+ return unwrapReferencedTypedefType(Unwrapped, Depth - 1);
return Unwrapped;
}
>From bae699aba48cad7e46b502c5374b7703177be755 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Thu, 7 May 2026 17:07:34 -0700
Subject: [PATCH 3/6] Inline the magic number
---
llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h b/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
index ccb823f95b74a..0310ffc894e27 100644
--- a/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
+++ b/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
@@ -147,8 +147,6 @@ void DWARFTypePrinter<DieType>::appendArrayType(const DieType &D) {
EndedWithTemplate = false;
}
-constexpr unsigned kMaxTypedefUnwrapDepth = 256;
-
namespace detail {
template <typename DieType>
DieType resolveReferencedType(DieType D,
@@ -174,8 +172,7 @@ const char *toString(std::optional<DWARFFormValueType> F) {
/// DW_TAG_typedef. Gives up after 256 typedefs to guard against cycles in
/// malformed DWARF.
template <typename DieType>
-DieType unwrapReferencedTypedefType(DieType D,
- unsigned Depth = kMaxTypedefUnwrapDepth) {
+DieType unwrapReferencedTypedefType(DieType D, unsigned Depth = 256) {
auto TypeAttr = D.find(dwarf::DW_AT_type);
if (!TypeAttr)
return DieType();
>From 3fce4a2f81426c32b1cf8f8c4de2550b9df216ec Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Fri, 8 May 2026 12:14:55 -0700
Subject: [PATCH 4/6] Convert to loop; use DenseSet
---
.../llvm/DebugInfo/DWARF/DWARFTypePrinter.h | 29 ++++++++++---------
1 file changed, 16 insertions(+), 13 deletions(-)
diff --git a/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h b/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
index 0310ffc894e27..4d97ac93eea1b 100644
--- a/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
+++ b/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
@@ -9,6 +9,7 @@
#ifndef LLVM_DEBUGINFO_DWARF_DWARFTYPEPRINTER_H
#define LLVM_DEBUGINFO_DWARF_DWARFTYPEPRINTER_H
+#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/BinaryFormat/Dwarf.h"
@@ -169,22 +170,24 @@ const char *toString(std::optional<DWARFFormValueType> F) {
}
/// Resolve the DW_AT_type of \c D until we reach a DIE that is not a
-/// DW_TAG_typedef. Gives up after 256 typedefs to guard against cycles in
-/// malformed DWARF.
-template <typename DieType>
-DieType unwrapReferencedTypedefType(DieType D, unsigned Depth = 256) {
- auto TypeAttr = D.find(dwarf::DW_AT_type);
- if (!TypeAttr)
- return DieType();
+/// DW_TAG_typedef. Gives up if a cycle is detected in malformed DWARF.
+/// In this case, return the last typedef DIE before the cycle is formed.
+template <typename DieType> DieType unwrapReferencedTypedefType(DieType D) {
+ SmallSet<uint64_t, 4> Visited;
+ while (true) {
+ auto TypeAttr = D.find(dwarf::DW_AT_type);
+ if (!TypeAttr)
+ return DieType();
- auto Unwrapped = detail::resolveReferencedType(D, *TypeAttr);
- if (!Unwrapped)
- return DieType();
+ auto Unwrapped = detail::resolveReferencedType(D, *TypeAttr);
+ if (!Unwrapped || Unwrapped.getTag() != dwarf::DW_TAG_typedef)
+ return Unwrapped;
- if (Unwrapped.getTag() == dwarf::DW_TAG_typedef && Depth > 0)
- return unwrapReferencedTypedefType(Unwrapped, Depth - 1);
+ if (!Visited.insert(Unwrapped.getOffset()).second)
+ return D;
- return Unwrapped;
+ D = Unwrapped;
+ }
}
} // namespace detail
>From dfd225752832e5b498b7105c8ce6c0a33f3abbc9 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Fri, 8 May 2026 12:21:37 -0700
Subject: [PATCH 5/6] Instead, return the DIE where the cycle is formed
---
llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h b/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
index 4d97ac93eea1b..6c46d7cde77ef 100644
--- a/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
+++ b/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
@@ -171,7 +171,7 @@ const char *toString(std::optional<DWARFFormValueType> F) {
/// Resolve the DW_AT_type of \c D until we reach a DIE that is not a
/// DW_TAG_typedef. Gives up if a cycle is detected in malformed DWARF.
-/// In this case, return the last typedef DIE before the cycle is formed.
+/// In this case, returns the typedef DIE where the cycle is formed.
template <typename DieType> DieType unwrapReferencedTypedefType(DieType D) {
SmallSet<uint64_t, 4> Visited;
while (true) {
@@ -184,7 +184,7 @@ template <typename DieType> DieType unwrapReferencedTypedefType(DieType D) {
return Unwrapped;
if (!Visited.insert(Unwrapped.getOffset()).second)
- return D;
+ return Unwrapped;
D = Unwrapped;
}
>From eaf2082ce64dc0676d6f519acc5c7937187fb168 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Fri, 8 May 2026 12:52:34 -0700
Subject: [PATCH 6/6] Fix build error in lldb
---
lldb/source/Plugins/SymbolFile/DWARF/DWARFBaseDIE.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFBaseDIE.h b/lldb/source/Plugins/SymbolFile/DWARF/DWARFBaseDIE.h
index d92de658a49e8..9736b80ac375f 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFBaseDIE.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFBaseDIE.h
@@ -123,6 +123,8 @@ class DWARFBaseDIE {
// LLVM libraries.
dw_tag_t getTag() const { return Tag(); }
+ dw_offset_t getOffset() const { return GetOffset(); }
+
const char *getShortName() const { return GetName(); }
protected:
More information about the lldb-commits
mailing list