[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