[Lldb-commits] [lldb] d230798 - [lldb] Detect cycles when following DIE references (#223912)
via lldb-commits
lldb-commits at lists.llvm.org
Thu Sep 17 11:06:20 PDT 2026
Author: Jonas Devlieghere
Date: 2026-09-17T11:05:55-07:00
New Revision: d230798c345ac225591f7bed53204f4ba5206539
URL: https://github.com/llvm/llvm-project/commit/d230798c345ac225591f7bed53204f4ba5206539
DIFF: https://github.com/llvm/llvm-project/commit/d230798c345ac225591f7bed53204f4ba5206539.diff
LOG: [lldb] Detect cycles when following DIE references (#223912)
A DW_TAG_subprogram whose DW_AT_specification points at its own offset
causes both GetDIENamesAndRanges and GetDeclContextDIEContainingDIE to
keep recursing
Track the DIEs already visited, the way the sibling GetAttributes helper
does. GetDIENamesAndRanges also moves to a worklist so a long chain of
distinct specifications no longer costs a stack frame per DIE.
rdar://186891786
Added:
lldb/test/Shell/SymbolFile/DWARF/self-referential-specification.yaml
Modified:
lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.cpp
lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
lldb/unittests/SymbolFile/DWARF/DWARFDIETest.cpp
Removed:
################################################################################
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.cpp
index e5eae7207b7e7..07017b00dd7a4 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.cpp
@@ -98,16 +98,17 @@ static void ExtractAttrAndFormValue(
form_value.SetSigned(attr_spec.getImplicitConstValue());
}
-// GetDIENamesAndRanges
-//
-// Gets the valid address ranges for a given DIE by looking for a
-// DW_AT_low_pc/DW_AT_high_pc pair, DW_AT_entry_pc, or DW_AT_ranges attributes.
-bool DWARFDebugInfoEntry::GetDIENamesAndRanges(
- DWARFUnit *cu, const char *&name, const char *&mangled,
+/// Helper for the public \ref DWARFDebugInfoEntry::GetDIENamesAndRanges API.
+/// Fills in the output parameters that aren't set yet from \c die and appends
+/// the DIEs it elaborates on to \c elaborating_dies.
+static void GetDIENamesAndRanges(
+ const DWARFDIE &die, const char *&name, const char *&mangled,
llvm::DWARFAddressRangesVector &ranges, std::optional<int> &decl_file,
std::optional<int> &decl_line, std::optional<int> &decl_column,
std::optional<int> &call_file, std::optional<int> &call_line,
- std::optional<int> &call_column, DWARFExpressionList *frame_base) const {
+ std::optional<int> &call_column, DWARFExpressionList *frame_base,
+ llvm::SmallVectorImpl<DWARFDIE> &elaborating_dies) {
+ DWARFUnit *cu = die.GetCU();
dw_addr_t lo_pc = LLDB_INVALID_ADDRESS;
dw_addr_t hi_pc = LLDB_INVALID_ADDRESS;
std::vector<DWARFDIE> dies;
@@ -116,12 +117,13 @@ bool DWARFDebugInfoEntry::GetDIENamesAndRanges(
SymbolFileDWARF &dwarf = cu->GetSymbolFileDWARF();
lldb::ModuleSP module = dwarf.GetObjectFile()->GetModule();
- if (const auto *abbrevDecl = GetAbbreviationDeclarationPtr(cu)) {
+ if (const auto *abbrevDecl =
+ die.GetDIE()->GetAbbreviationDeclarationPtr(cu)) {
const DWARFDataExtractor &data = cu->GetData();
- lldb::offset_t offset = GetFirstAttributeOffset();
+ lldb::offset_t offset = die.GetDIE()->GetFirstAttributeOffset();
if (!data.ValidOffset(offset))
- return false;
+ return;
bool do_offset = false;
@@ -168,7 +170,8 @@ bool DWARFDebugInfoEntry::GetDIENamesAndRanges(
"[{0:x16}]: DIE has DW_AT_ranges({1} {2:x16}) attribute, but "
"range extraction failed ({3}), please file a bug "
"and attach the file at the start of this error message",
- GetOffset(), llvm::dwarf::FormEncodingString(form_value.Form()),
+ die.GetOffset(),
+ llvm::dwarf::FormEncodingString(form_value.Form()),
form_value.Unsigned(), fmt_consume(r.takeError()));
}
break;
@@ -270,13 +273,49 @@ bool DWARFDebugInfoEntry::GetDIENamesAndRanges(
if (ranges.empty() || name == nullptr || mangled == nullptr) {
for (const DWARFDIE &die : dies) {
- if (die) {
- die.GetDIE()->GetDIENamesAndRanges(die.GetCU(), name, mangled, ranges,
- decl_file, decl_line, decl_column,
- call_file, call_line, call_column);
- }
+ if (die)
+ elaborating_dies.push_back(die);
+ }
+ }
+}
+
+// GetDIENamesAndRanges
+//
+// Gets the valid address ranges for a given DIE by looking for a
+// DW_AT_low_pc/DW_AT_high_pc pair, DW_AT_entry_pc, or DW_AT_ranges attributes.
+bool DWARFDebugInfoEntry::GetDIENamesAndRanges(
+ DWARFUnit *cu, const char *&name, const char *&mangled,
+ llvm::DWARFAddressRangesVector &ranges, std::optional<int> &decl_file,
+ std::optional<int> &decl_line, std::optional<int> &decl_column,
+ std::optional<int> &call_file, std::optional<int> &call_line,
+ std::optional<int> &call_column, DWARFExpressionList *frame_base) const {
+ llvm::SmallVector<DWARFDIE, 3> worklist;
+ worklist.emplace_back(cu, this);
+
+ // Keep track of the DIEs already seen to catch cycles.
+ llvm::SmallPtrSet<DWARFDebugInfoEntry const *, 3> seen;
+ seen.insert(this);
+
+ // The frame base is only taken from the DIE itself, not from the DIEs it
+ // elaborates on.
+ DWARFExpressionList *die_frame_base = frame_base;
+
+ while (!worklist.empty()) {
+ DWARFDIE current = worklist.pop_back_val();
+
+ llvm::SmallVector<DWARFDIE, 3> elaborating_dies;
+ ::GetDIENamesAndRanges(current, name, mangled, ranges, decl_file, decl_line,
+ decl_column, call_file, call_line, call_column,
+ die_frame_base, elaborating_dies);
+ die_frame_base = nullptr;
+
+ // Push in reverse so that the worklist handles them in the order.
+ for (const DWARFDIE &die : llvm::reverse(elaborating_dies)) {
+ if (seen.insert(die.GetDIE()).second)
+ worklist.push_back(die);
}
}
+
return !ranges.empty();
}
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
index b0007f04a31ab..53e515cc180b5 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
@@ -9,6 +9,7 @@
#include "SymbolFileDWARF.h"
#include "clang/Basic/ABI.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/DebugInfo/DWARF/DWARFAddressRange.h"
@@ -3058,58 +3059,70 @@ TypeSP SymbolFileDWARF::GetTypeForDIE(const DWARFDIE &die,
return type_sp;
}
-DWARFDIE
-SymbolFileDWARF::GetDeclContextDIEContainingDIE(const DWARFDIE &orig_die) {
- if (orig_die) {
- DWARFDIE die = orig_die;
-
- while (die) {
- // If this is the original DIE that we are searching for a declaration
- // for, then don't look in the cache as we don't want our own decl
- // context to be our decl context...
- if (orig_die != die) {
- switch (die.Tag()) {
- case DW_TAG_compile_unit:
- case DW_TAG_partial_unit:
- case DW_TAG_namespace:
- case DW_TAG_structure_type:
- case DW_TAG_union_type:
- case DW_TAG_class_type:
- case DW_TAG_lexical_block:
- case DW_TAG_subprogram:
- return die;
- case DW_TAG_inlined_subroutine: {
- DWARFDIE abs_die = die.GetReferencedDIE(DW_AT_abstract_origin);
- if (abs_die) {
- return abs_die;
- }
- break;
- }
- default:
- break;
+/// Helper for the public \ref SymbolFileDWARF::GetDeclContextDIEContainingDIE
+/// API. Specifications and abstract origins the walk has already descended
+/// into are in \c seen and are not followed again.
+static DWARFDIE GetDeclContextDIEContainingDIE(
+ const DWARFDIE &orig_die,
+ llvm::SmallPtrSetImpl<const DWARFDebugInfoEntry *> &seen) {
+ DWARFDIE die = orig_die;
+
+ while (die) {
+ // If this is the original DIE that we are searching for a declaration
+ // for, then don't look in the cache as we don't want our own decl
+ // context to be our decl context...
+ if (orig_die != die) {
+ switch (die.Tag()) {
+ case DW_TAG_compile_unit:
+ case DW_TAG_partial_unit:
+ case DW_TAG_namespace:
+ case DW_TAG_structure_type:
+ case DW_TAG_union_type:
+ case DW_TAG_class_type:
+ case DW_TAG_lexical_block:
+ case DW_TAG_subprogram:
+ return die;
+ case DW_TAG_inlined_subroutine: {
+ DWARFDIE abs_die = die.GetReferencedDIE(DW_AT_abstract_origin);
+ if (abs_die) {
+ return abs_die;
}
+ break;
}
-
- DWARFDIE spec_die = die.GetReferencedDIE(DW_AT_specification);
- if (spec_die) {
- DWARFDIE decl_ctx_die = GetDeclContextDIEContainingDIE(spec_die);
- if (decl_ctx_die)
- return decl_ctx_die;
+ default:
+ break;
}
+ }
- DWARFDIE abs_die = die.GetReferencedDIE(DW_AT_abstract_origin);
- if (abs_die) {
- DWARFDIE decl_ctx_die = GetDeclContextDIEContainingDIE(abs_die);
- if (decl_ctx_die)
- return decl_ctx_die;
- }
+ DWARFDIE spec_die = die.GetReferencedDIE(DW_AT_specification);
+ if (spec_die && seen.insert(spec_die.GetDIE()).second) {
+ DWARFDIE decl_ctx_die = ::GetDeclContextDIEContainingDIE(spec_die, seen);
+ if (decl_ctx_die)
+ return decl_ctx_die;
+ }
- die = die.GetParent();
+ DWARFDIE abs_die = die.GetReferencedDIE(DW_AT_abstract_origin);
+ if (abs_die && seen.insert(abs_die.GetDIE()).second) {
+ DWARFDIE decl_ctx_die = ::GetDeclContextDIEContainingDIE(abs_die, seen);
+ if (decl_ctx_die)
+ return decl_ctx_die;
}
+
+ die = die.GetParent();
}
+
return DWARFDIE();
}
+DWARFDIE
+SymbolFileDWARF::GetDeclContextDIEContainingDIE(const DWARFDIE &orig_die) {
+ if (!orig_die)
+ return DWARFDIE();
+
+ llvm::SmallPtrSet<const DWARFDebugInfoEntry *, 4> seen{orig_die.GetDIE()};
+ return ::GetDeclContextDIEContainingDIE(orig_die, seen);
+}
+
Symbol *SymbolFileDWARF::GetObjCClassSymbol(ConstString objc_class_name) {
Symbol *objc_class_symbol = nullptr;
if (m_objfile_sp) {
diff --git a/lldb/test/Shell/SymbolFile/DWARF/self-referential-specification.yaml b/lldb/test/Shell/SymbolFile/DWARF/self-referential-specification.yaml
new file mode 100644
index 0000000000000..a0ea973c7e21d
--- /dev/null
+++ b/lldb/test/Shell/SymbolFile/DWARF/self-referential-specification.yaml
@@ -0,0 +1,77 @@
+# This is malformed DWARF where a subprogram is its own specification. Check
+# that parsing the function terminates instead of recursing forever.
+#
+# DW_TAG_compile_unit
+# DW_AT_language (DW_LANG_C99)
+# DW_AT_low_pc (0x1000)
+# DW_AT_high_pc (0x04)
+#
+# DW_TAG_subprogram
+# DW_AT_name ("boom")
+# DW_AT_low_pc (0x1000)
+# DW_AT_high_pc (0x04)
+# DW_AT_specification (0x0000001a "boom")
+#
+# NULL
+
+# RUN: yaml2obj %s > %t
+# RUN: %lldb %t -o "image lookup -v -n boom" -o exit | FileCheck %s
+
+# CHECK: 1 match found
+# CHECK: Function: id = {0x0000001a}, name = "boom"
+
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_EXEC
+ Machine: EM_X86_64
+Sections:
+ - Name: .text
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ Address: 0x1000
+ AddressAlign: 0x10
+ Size: 0x4
+DWARF:
+ debug_abbrev:
+ - ID: 0
+ Table:
+ - Code: 0x1
+ Tag: DW_TAG_compile_unit
+ Children: DW_CHILDREN_yes
+ Attributes:
+ - Attribute: DW_AT_language
+ Form: DW_FORM_data2
+ - Attribute: DW_AT_low_pc
+ Form: DW_FORM_addr
+ - Attribute: DW_AT_high_pc
+ Form: DW_FORM_data4
+ - Code: 0x2
+ Tag: DW_TAG_subprogram
+ Children: DW_CHILDREN_no
+ Attributes:
+ - Attribute: DW_AT_name
+ Form: DW_FORM_string
+ - Attribute: DW_AT_low_pc
+ Form: DW_FORM_addr
+ - Attribute: DW_AT_high_pc
+ Form: DW_FORM_data4
+ - Attribute: DW_AT_specification
+ Form: DW_FORM_ref4
+ debug_info:
+ - Version: 4
+ AddrSize: 8
+ Entries:
+ - AbbrCode: 0x1
+ Values:
+ - Value: 0x0C
+ - Value: 0x1000
+ - Value: 0x04
+ - AbbrCode: 0x2
+ Values:
+ - CStr: boom
+ - Value: 0x1000
+ - Value: 0x04
+ - Value: 0x1A
+ - AbbrCode: 0x0
diff --git a/lldb/unittests/SymbolFile/DWARF/DWARFDIETest.cpp b/lldb/unittests/SymbolFile/DWARF/DWARFDIETest.cpp
index 2611d105e286f..4e36125dbfde5 100644
--- a/lldb/unittests/SymbolFile/DWARF/DWARFDIETest.cpp
+++ b/lldb/unittests/SymbolFile/DWARF/DWARFDIETest.cpp
@@ -1100,6 +1100,178 @@ INSTANTIATE_TEST_SUITE_P(GetAttributeTests, GetAttributesTestFixture,
testing::Values(DW_AT_specification,
DW_AT_abstract_origin));
+struct GetDIENamesAndRangesTestFixture
+ : public testing::TestWithParam<dw_attr_t> {};
+
+TEST_P(GetDIENamesAndRangesTestFixture,
+ TestGetDIENamesAndRanges_SelfReference) {
+ // Tests that GetDIENamesAndRanges terminates on a DIE whose specification or
+ // abstract origin points at itself.
+
+ const char *yamldata = R"(
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_EXEC
+ Machine: EM_AARCH64
+DWARF:
+ debug_abbrev:
+ - ID: 0
+ Table:
+ - Code: 0x1
+ Tag: DW_TAG_compile_unit
+ Children: DW_CHILDREN_yes
+ Attributes:
+ - Attribute: DW_AT_language
+ Form: DW_FORM_data2
+ - Code: 0x2
+ Tag: DW_TAG_subprogram
+ Children: DW_CHILDREN_no
+ Attributes:
+ - Attribute: DW_AT_name
+ Form: DW_FORM_string
+ - Attribute: {0}
+ Form: DW_FORM_ref4
+ debug_info:
+ - Version: 5
+ UnitType: DW_UT_compile
+ AddrSize: 8
+ Entries:
+
+ - AbbrCode: 0x1
+ Values:
+ - Value: 0x04
+
+ - AbbrCode: 0x2
+ Values:
+ - CStr: boom
+ - Value: 0xf
+
+ - AbbrCode: 0x0
+...
+)";
+ YAMLModuleTester t(llvm::formatv(yamldata, GetParam()).str());
+
+ DWARFUnit *unit = t.GetDwarfUnit();
+ ASSERT_NE(unit, nullptr);
+ const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE();
+ ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit);
+ DWARFDIE cu_die(unit, cu_entry);
+
+ DWARFDIE func = cu_die.GetFirstChild();
+ ASSERT_TRUE(func.IsValid());
+ ASSERT_EQ(func.Tag(), DW_TAG_subprogram);
+
+ const char *name = nullptr;
+ const char *mangled = nullptr;
+ llvm::DWARFAddressRangesVector ranges;
+ std::optional<int> decl_file, decl_line, decl_column;
+ std::optional<int> call_file, call_line, call_column;
+
+ EXPECT_FALSE(func.GetDIENamesAndRanges(
+ name, mangled, ranges, decl_file, decl_line, decl_column, call_file,
+ call_line, call_column, /*frame_base=*/nullptr));
+ EXPECT_STREQ(name, "boom");
+ EXPECT_EQ(mangled, nullptr);
+}
+
+TEST_P(GetDIENamesAndRangesTestFixture, TestGetDIENamesAndRanges_Cycle) {
+ // Tests that GetDIENamesAndRanges terminates on a cycle of specifications or
+ // abstract origins, after visiting every DIE in it.
+ //
+ // func1 -> func2 -> func3
+ // ^ |
+ // +-----------------+
+
+ const char *yamldata = R"(
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_EXEC
+ Machine: EM_AARCH64
+DWARF:
+ debug_abbrev:
+ - ID: 0
+ Table:
+ - Code: 0x1
+ Tag: DW_TAG_compile_unit
+ Children: DW_CHILDREN_yes
+ Attributes:
+ - Attribute: DW_AT_language
+ Form: DW_FORM_data2
+ - Code: 0x2
+ Tag: DW_TAG_subprogram
+ Children: DW_CHILDREN_no
+ Attributes:
+ - Attribute: {0}
+ Form: DW_FORM_ref4
+ - Code: 0x3
+ Tag: DW_TAG_subprogram
+ Children: DW_CHILDREN_no
+ Attributes:
+ - Attribute: DW_AT_name
+ Form: DW_FORM_string
+ - Attribute: {0}
+ Form: DW_FORM_ref4
+ debug_info:
+ - Version: 5
+ UnitType: DW_UT_compile
+ AddrSize: 8
+ Entries:
+
+ - AbbrCode: 0x1
+ Values:
+ - Value: 0x04
+
+ - AbbrCode: 0x2
+ Values:
+ - Value: 0x14
+
+ - AbbrCode: 0x2
+ Values:
+ - Value: 0x19
+
+ - AbbrCode: 0x3
+ Values:
+ - CStr: boom
+ - Value: 0xf
+
+ - AbbrCode: 0x0
+...
+)";
+ YAMLModuleTester t(llvm::formatv(yamldata, GetParam()).str());
+
+ DWARFUnit *unit = t.GetDwarfUnit();
+ ASSERT_NE(unit, nullptr);
+ const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE();
+ ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit);
+ DWARFDIE cu_die(unit, cu_entry);
+
+ DWARFDIE func1 = cu_die.GetFirstChild();
+ ASSERT_TRUE(func1.IsValid());
+ ASSERT_EQ(func1.Tag(), DW_TAG_subprogram);
+
+ const char *name = nullptr;
+ const char *mangled = nullptr;
+ llvm::DWARFAddressRangesVector ranges;
+ std::optional<int> decl_file, decl_line, decl_column;
+ std::optional<int> call_file, call_line, call_column;
+
+ EXPECT_FALSE(func1.GetDIENamesAndRanges(
+ name, mangled, ranges, decl_file, decl_line, decl_column, call_file,
+ call_line, call_column, /*frame_base=*/nullptr));
+ // The name comes from the last DIE in the cycle.
+ EXPECT_STREQ(name, "boom");
+ EXPECT_EQ(mangled, nullptr);
+}
+
+INSTANTIATE_TEST_SUITE_P(GetDIENamesAndRangesTests,
+ GetDIENamesAndRangesTestFixture,
+ testing::Values(DW_AT_specification,
+ DW_AT_abstract_origin));
+
// Exercises fallback in SymbolFileDWARF::ParseVariableDIE (added for
// Swift) that walks the type chain for DW_AT_byte_size when the
// TypeSystem cannot determine the size of a variable's storage. The
More information about the lldb-commits
mailing list