Skip to content

[DebugInfo] Fix issue with debug line table offsets for empty functions #142253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

alx32
Copy link
Contributor

@alx32 alx32 commented May 31, 2025

This patch addresses an issue where an anonymous DWARF line table symbol could be inadvertently defined multiple times, leading to an "symbol '' is already defined" error during assembly or object file emission. This issue happens for empty functions when -emit-func-debug-line-table-offsets is enabled.

The root cause is the creation of the "end sequence" entry for a DWARF line table. This entry was sometimes created by copying the last existing line table entry. If this last entry was a special one (created for the purpose of marking the position in the line table stream and having an anonymous symbol attached), the copied end-sequence entry would also incorrectly reference this same anonymous symbol. Consequently, when the line table was finally emitted, the DWARF emission logic would attempt to emit a label for this anonymous symbol twice, triggering the redefinition error.

The fix ensures that when an end-sequence line table entry is created, it does not inherit any special stream label from the entry it might have been based on, thereby preventing the duplicate label emission.

@llvmbot llvmbot added debuginfo mc Machine (object) code labels May 31, 2025
@llvmbot
Copy link
Member

llvmbot commented May 31, 2025

@llvm/pr-subscribers-mc

@llvm/pr-subscribers-debuginfo

Author: None (alx32)

Changes

This patch addresses an issue where an anonymous DWARF line table symbol could be inadvertently defined multiple times, leading to an "symbol '' is already defined" error during assembly or object file emission. This issue happens for empty functions when -emit-func-debug-line-table-offsets is enabled.

The root cause is the creation of the "end sequence" entry for a DWARF line table. This entry was sometimes created by copying the last existing line table entry. If this last entry was a special one (created for the purpose of marking the position in the line table stream and having an anonymous symbol attached), the copied end-sequence entry would also incorrectly reference this same anonymous symbol. Consequently, when the line table was finally emitted, the DWARF emission logic would attempt to emit a label for this anonymous symbol twice, triggering the redefinition error.

The fix ensures that when an end-sequence line table entry is created, it does not inherit any special stream label from the entry it might have been based on, thereby preventing the duplicate label emission.


Full diff: https://github.com/llvm/llvm-project/pull/142253.diff

2 Files Affected:

  • (modified) llvm/lib/MC/MCDwarf.cpp (+22-9)
  • (added) llvm/test/DebugInfo/X86/DW_AT_LLVM_stmt_seq_sec_offset_empty_func.ll (+37)
diff --git a/llvm/lib/MC/MCDwarf.cpp b/llvm/lib/MC/MCDwarf.cpp
index 2525ff02878e4..8f6f37e5b610a 100644
--- a/llvm/lib/MC/MCDwarf.cpp
+++ b/llvm/lib/MC/MCDwarf.cpp
@@ -144,19 +144,32 @@ makeStartPlusIntExpr(MCContext &Ctx, const MCSymbol &Start, int IntVal) {
 void MCLineSection::addEndEntry(MCSymbol *EndLabel) {
   auto *Sec = &EndLabel->getSection();
   // The line table may be empty, which we should skip adding an end entry.
-  // There are two cases:
+  // There are three cases:
   // (1) MCAsmStreamer - emitDwarfLocDirective emits a location directive in
   //     place instead of adding a line entry if the target has
   //     usesDwarfFileAndLocDirectives.
   // (2) MCObjectStreamer - if a function has incomplete debug info where
   //     instructions don't have DILocations, the line entries are missing.
+  // (3) It's also possible that there are no prior line entries if the section
+  //     itself is empty before this end label.
   auto I = MCLineDivisions.find(Sec);
-  if (I != MCLineDivisions.end()) {
-    auto &Entries = I->second;
-    auto EndEntry = Entries.back();
-    EndEntry.setEndLabel(EndLabel);
-    Entries.push_back(EndEntry);
-  }
+  if (I == MCLineDivisions.end()) // If section not found, do nothing.
+    return;
+
+  auto &Entries = I->second;
+  // If no entries in this section's list, nothing to base the end entry on.
+  if (Entries.empty())
+    return;
+
+  // Create the end entry based on the last existing entry.
+  MCDwarfLineEntry EndEntry = Entries.back();
+  EndEntry.setEndLabel(EndLabel);
+  // An end entry is just for marking the end of a sequence of code locations.
+  // It should not carry forward a LineStreamLabel from a previous special entry
+  // if Entries.back() happened to be such an entry. So here we clear
+  // LineStreamLabel.
+  EndEntry.LineStreamLabel = nullptr;
+  Entries.push_back(EndEntry);
 }
 
 //
@@ -1227,7 +1240,7 @@ void MCGenDwarfInfo::Emit(MCStreamer *MCOS) {
 // a dwarf label.
 //
 void MCGenDwarfLabelEntry::Make(MCSymbol *Symbol, MCStreamer *MCOS,
-                                     SourceMgr &SrcMgr, SMLoc &Loc) {
+                                SourceMgr &SrcMgr, SMLoc &Loc) {
   // We won't create dwarf labels for temporary symbols.
   if (Symbol->isTemporary())
     return;
@@ -1295,7 +1308,7 @@ static unsigned getSizeForEncoding(MCStreamer &streamer,
 }
 
 static void emitFDESymbol(MCObjectStreamer &streamer, const MCSymbol &symbol,
-                       unsigned symbolEncoding, bool isEH) {
+                          unsigned symbolEncoding, bool isEH) {
   MCContext &context = streamer.getContext();
   const MCAsmInfo *asmInfo = context.getAsmInfo();
   const MCExpr *v = asmInfo->getExprForFDESymbol(&symbol,
diff --git a/llvm/test/DebugInfo/X86/DW_AT_LLVM_stmt_seq_sec_offset_empty_func.ll b/llvm/test/DebugInfo/X86/DW_AT_LLVM_stmt_seq_sec_offset_empty_func.ll
new file mode 100644
index 0000000000000..553a05f12082e
--- /dev/null
+++ b/llvm/test/DebugInfo/X86/DW_AT_LLVM_stmt_seq_sec_offset_empty_func.ll
@@ -0,0 +1,37 @@
+; RUN: llc -filetype=obj -emit-func-debug-line-table-offsets < %s -o %t_yes_off.o
+; RUN: llc -filetype=obj < %s -o %t_no_off.o
+; RUN: llvm-dwarfdump -v -all %t_no_off.o | FileCheck --check-prefix=CHECK-NO-OFF %s
+; RUN: llvm-dwarfdump -v -all %t_yes_off.o | FileCheck --check-prefix=CHECK-YES-OFF %s
+
+; We don't need a line table for an empty function
+; CHECK-NO-OFF-NOT: DW_LNE_set_address
+
+; CHECK-YES-OFF:                 DW_AT_LLVM_stmt_sequence [DW_FORM_sec_offset] ({{.*}})
+; CHECK-YES-OFF:                 Address
+; CHECK-YES-OFF-NEXT:            ------------------
+; CHECK-YES-OFF-NEXT:            DW_LNE_set_address ({{.*}})
+; CHECK-YES-OFF-NEXT:            DW_LNE_end_sequence
+; CHECK-YES-OFF-NEXT:            0x0000000000000000 {{.*}} is_stmt end_sequence
+
+; IR generated by llvm-reduce from LTO repro
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+define ptr @_my_test_function() !dbg !4 {
+entry:
+  unreachable
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 21.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, retainedTypes: !2, globals: !2, imports: !2, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "repro_bug.cpp", directory: "/tmp/repro", checksumkind: CSK_MD5, checksum: "74e33e88b3108a4f94403da9fdb362f5")
+!2 = !{}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = distinct !DISubprogram(name: "east", linkageName: "_my_test_function", scope: !6, file: !5, line: 114, type: !8, scopeLine: 114, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, declaration: !9, retainedNodes: !2)
+!5 = !DIFile(filename: "repro_bug.cpp", directory: "/tmp/repro", checksumkind: CSK_MD5, checksum: "74e33e88b3108a4f94403da9fdb362f5")
+!6 = distinct !DICompositeType(tag: DW_TAG_class_type, name: "rectangle", file: !5, line: 103, size: 256, flags: DIFlagTypePassByReference | DIFlagNonTrivial, elements: !2, vtableHolder: !7)
+!7 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "shape", file: !5, line: 58, size: 128, flags: DIFlagTypePassByReference | DIFlagNonTrivial, elements: !2, vtableHolder: !7, identifier: "_ZTS5shape")
+!8 = distinct !DISubroutineType(types: !2)
+!9 = !DISubprogram(name: "east", linkageName: "_my_test_function", scope: !6, file: !5, line: 114, type: !8, scopeLine: 114, containingType: !6, virtualIndex: 2, flags: DIFlagPublic | DIFlagPrototyped, spFlags: DISPFlagVirtual | DISPFlagOptimized)

@alx32 alx32 changed the title [MCDwarf] Fix issue with debug line offsets for empty functions [DebugInfo] Fix issue with debug line table offsets for empty functions May 31, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
debuginfo mc Machine (object) code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants