[llvm] [llvm-dwp] Fix infinite loop on DWARFv5 DW_FORM_implicit_const (PR #205567)

via llvm-commits llvm-commits at lists.llvm.org
Wed Jun 24 07:01:04 PDT 2026


https://github.com/youngd007 created https://github.com/llvm/llvm-project/pull/205567

 llvm-dwp could spin forever (100% CPU, never terminating) when packaging a
  DWARFv5 .dwo whose abbreviation table uses DW_FORM_implicit_const.

  A DWARF abbreviation declaration is a list of (attribute, form) ULEB128
  pairs terminated by a (0, 0) pair. DW_FORM_implicit_const is the only form
  that stores an additional value -- an SLEB128 constant -- in the
  abbreviation declaration itself (the value is not present in .debug_info).

  getCUAbbrev() and getCUIdentifiers() in llvm/lib/DWP/DWP.cpp walked these
  pairs but never consumed that extra SLEB128, so every implicit_const entry
  shifted the cursor by one ULEB and desynced the rest of the table walk.
  With enough implicit_const attributes preceding the compile unit's
  abbreviation code, the scan in getCUAbbrev() runs off the end of
  .debug_abbrev without ever matching the code. Past end-of-buffer
  DataExtractor::getULEB128 returns 0 without advancing the offset, so the
  loop `while (getULEB128(&Offset) != AbbrCode)` spins on `0 != AbbrCode`
  forever. The input is valid DWARFv5; the bug is entirely in llvm-dwp.

  Consume the implicit_const SLEB128 in both abbreviation-walk loops so the
  (attribute, form) walk stays aligned, matching how
  DWARFAbbreviationDeclaration::extract parses the same data.

  Adds a regression test that hangs before this change and passes after it.

AI assisted with: claude

>From 803f47385f3bcc8380d269992ee84819b9787c4c Mon Sep 17 00:00:00 2001
From: David Young <davidayoung at meta.com>
Date: Wed, 24 Jun 2026 06:58:29 -0700
Subject: [PATCH]   [llvm-dwp] Fix infinite loop on DWARFv5
 DW_FORM_implicit_const

---
 llvm/lib/DWP/DWP.cpp                          | 28 +++++++--
 llvm/test/tools/llvm-dwp/X86/implicit_const.s | 58 +++++++++++++++++++
 2 files changed, 80 insertions(+), 6 deletions(-)
 create mode 100644 llvm/test/tools/llvm-dwp/X86/implicit_const.s

diff --git a/llvm/lib/DWP/DWP.cpp b/llvm/lib/DWP/DWP.cpp
index db0d33cd5a52d..779c9ea7f371c 100644
--- a/llvm/lib/DWP/DWP.cpp
+++ b/llvm/lib/DWP/DWP.cpp
@@ -48,8 +48,18 @@ static uint64_t getCUAbbrev(StringRef Abbrev, uint64_t AbbrCode) {
     // DW_CHILDREN
     AbbrevData.getU8(&Offset);
     // Attributes
-    while (AbbrevData.getULEB128(&Offset) | AbbrevData.getULEB128(&Offset))
-      ;
+    while (true) {
+      uint64_t Name = AbbrevData.getULEB128(&Offset);
+      dwarf::Form Form =
+          static_cast<dwarf::Form>(AbbrevData.getULEB128(&Offset));
+      // DW_FORM_implicit_const carries its value as an SLEB128 in the
+      // abbreviation declaration itself; failing to consume it desyncs the
+      // rest of the abbreviation table walk.
+      if (Form == dwarf::DW_FORM_implicit_const)
+        AbbrevData.getSLEB128(&Offset);
+      if (Name == 0 && Form == 0)
+        break;
+    }
   }
   return Offset;
 }
@@ -115,10 +125,16 @@ getCUIdentifiers(InfoSectionUnitHeader &Header, StringRef Abbrev,
   AbbrevData.getU8(&AbbrevOffset);
   uint32_t Name;
   dwarf::Form Form;
-  while ((Name = AbbrevData.getULEB128(&AbbrevOffset)) |
-             (Form = static_cast<dwarf::Form>(
-                  AbbrevData.getULEB128(&AbbrevOffset))) &&
-         (Name != 0 || Form != 0)) {
+  for (;;) {
+    Name = AbbrevData.getULEB128(&AbbrevOffset);
+    Form = static_cast<dwarf::Form>(AbbrevData.getULEB128(&AbbrevOffset));
+    // DW_FORM_implicit_const stores its value as an SLEB128 in the abbreviation
+    // declaration, not in .debug_info; consume it so the (attribute, form) walk
+    // stays aligned.
+    if (Form == dwarf::DW_FORM_implicit_const)
+      AbbrevData.getSLEB128(&AbbrevOffset);
+    if (Name == 0 && Form == 0)
+      break;
     switch (Name) {
     case dwarf::DW_AT_name: {
       Expected<const char *> EName = getIndexedString(
diff --git a/llvm/test/tools/llvm-dwp/X86/implicit_const.s b/llvm/test/tools/llvm-dwp/X86/implicit_const.s
new file mode 100644
index 0000000000000..15d9569c7c671
--- /dev/null
+++ b/llvm/test/tools/llvm-dwp/X86/implicit_const.s
@@ -0,0 +1,58 @@
+# Check that llvm-dwp correctly handles a DWARFv5 .dwo whose abbreviation table
+# uses DW_FORM_implicit_const. DW_FORM_implicit_const stores its value as an
+# SLEB128 in the abbreviation declaration itself (not in .debug_info). If
+# llvm-dwp fails to consume that value while scanning the abbreviation table, the
+# (attribute, form) walk desyncs; here the desync makes the scan for the
+# compile-unit's abbreviation code run off the end of .debug_abbrev and spin
+# forever. This is a regression test: before the fix llvm-dwp hangs, after the
+# fix it terminates and emits the expected CU identifiers.
+
+# RUN: llvm-mc --triple=x86_64-unknown-linux --filetype=obj --split-dwarf-file=%t.dwo -dwarf-version=5 %s -o %t.o
+# RUN: llvm-dwp %t.dwo -o %t.dwp
+# RUN: llvm-dwarfdump -v %t.dwp | FileCheck %s
+
+# CHECK: .debug_info.dwo contents:
+# CHECK: 0x00000000: Compile Unit: {{.*}} version = 0x0005, unit_type = DW_UT_split_compile, {{.*}} DWO_id = [[DWOID:0x[0-9a-f]+]]
+# CHECK: DW_TAG_compile_unit
+# CHECK: DW_AT_name{{.*}}"test.c"
+# CHECK: DW_AT_dwo_name{{.*}}"test.dwo"
+
+# CHECK: .debug_cu_index contents:
+# CHECK: version = 5, units = 1, slots = 2
+# CHECK: [[DWOID]] [0x0000000000000000
+
+	.section	.debug_info.dwo,"e", at progbits
+	.long	.Ldebug_info_dwo_end0-.Ldebug_info_dwo_start0 # Length of Unit
+.Ldebug_info_dwo_start0:
+	.short	5                       # DWARF version number
+	.byte	5                       # DWARF Unit Type (DW_UT_split_compile)
+	.byte	8                       # Address Size (in bytes)
+	.long	0                       # Offset Into Abbrev. Section
+	.quad	0x1100001122334455      # DWO_id
+	.byte	2                       # Abbrev [2] DW_TAG_compile_unit
+	.asciz	"test.c"                # DW_AT_name
+	.asciz	"test.dwo"              # DW_AT_dwo_name
+.Ldebug_info_dwo_end0:
+
+	.section	.debug_abbrev.dwo,"e", at progbits
+	# Abbrev [1] DW_TAG_variable, declared *before* the compile unit and using
+	# DW_FORM_implicit_const. This is the declaration that desyncs the walk.
+	.byte	1                       # Abbreviation Code
+	.byte	52                      # DW_TAG_variable
+	.byte	0                       # DW_CHILDREN_no
+	.byte	28                      # DW_AT_const_value
+	.byte	33                      # DW_FORM_implicit_const
+	.byte	42                      # implicit const value (SLEB128)
+	.byte	0                       # EOM(1)
+	.byte	0                       # EOM(2)
+	# Abbrev [2] DW_TAG_compile_unit
+	.byte	2                       # Abbreviation Code
+	.byte	17                      # DW_TAG_compile_unit
+	.byte	0                       # DW_CHILDREN_no
+	.byte	3                       # DW_AT_name
+	.byte	8                       # DW_FORM_string
+	.byte	118                     # DW_AT_dwo_name
+	.byte	8                       # DW_FORM_string
+	.byte	0                       # EOM(1)
+	.byte	0                       # EOM(2)
+	.byte	0                       # EOM(3)



More information about the llvm-commits mailing list