Skip to content

Commit f407dff

Browse files
authored
[DebugInfo][DWARF] Emit Per-Function Line Table Offsets and End Sequences (#110192)
**Summary** This patch introduces a new compiler option `-mllvm -emit-func-debug-line-table-offsets` that enables the emission of per-function line table offsets and end sequences in DWARF debug information. This enhancement allows tools and debuggers to accurately attribute line number information to their corresponding functions, even in scenarios where functions are merged or share the same address space due to optimizations like Identical Code Folding (ICF) in the linker. **Background** RFC: [New DWARF Attribute for Symbolication of Merged Functions](https://discourse.llvm.org/t/rfc-new-dwarf-attribute-for-symbolication-of-merged-functions/79434) Previous similar PR: [#93137](#93137) – This PR was very similar to the current one but at the time, the assembler had no support for emitting labels within the line table. That support was added in PR [#99710](#99710) - and in this PR we use some of the support added in the assembler PR. In the current implementation, Clang generates line information in the `debug_line` section without directly associating line entries with their originating `DW_TAG_subprogram` DIEs. This can lead to issues when post-compilation optimizations merge functions, resulting in overlapping address ranges and ambiguous line information. For example, when functions are merged by ICF in LLD, multiple functions may end up sharing the same address range. Without explicit linkage between functions and their line entries, tools cannot accurately attribute line information to the correct function, adversely affecting debugging and call stack resolution. **Implementation Details** To address the above issue, the patch makes the following key changes: **`DW_AT_LLVM_stmt_sequence` Attribute**: Introduces a new LLVM-specific attribute `DW_AT_LLVM_stmt_sequence` to each `DW_TAG_subprogram` DIE. This attribute holds a label pointing to the offset in the line table where the function's line entries begin. **End-of-Sequence Markers**: Emits an explicit DW_LNE_end_sequence after each function's line entries in the line table. This marks the end of the line information for that function, ensuring that line entries are correctly delimited. **Assembler and Streamer Modifications**: Modifies the MCStreamer and related classes to support emitting the necessary labels and tracking the current function's line entries. A new flag GenerateFuncLineTableOffsets is added to control this behavior. **Compiler Option**: Introduces the `-mllvm -emit-func-debug-line-table-offsets` option to enable this functionality, allowing users to opt-in as needed.
1 parent e9aee4f commit f407dff

File tree

8 files changed

+180
-11
lines changed

8 files changed

+180
-11
lines changed

llvm/include/llvm/BinaryFormat/Dwarf.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,7 @@ HANDLE_DW_AT(0x3e08, LLVM_ptrauth_isa_pointer, 0, LLVM)
618618
HANDLE_DW_AT(0x3e09, LLVM_ptrauth_authenticates_null_values, 0, LLVM)
619619
HANDLE_DW_AT(0x3e0a, LLVM_ptrauth_authentication_mode, 0, LLVM)
620620
HANDLE_DW_AT(0x3e0b, LLVM_num_extra_inhabitants, 0, LLVM)
621+
HANDLE_DW_AT(0x3e0c, LLVM_stmt_sequence, 0, LLVM)
621622

622623
// Apple extensions.
623624

llvm/include/llvm/MC/MCStreamer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ class MCStreamer {
313313
void setAllowAutoPadding(bool v) { AllowAutoPadding = v; }
314314
bool getAllowAutoPadding() const { return AllowAutoPadding; }
315315

316+
MCSymbol *emitLineTableLabel();
317+
316318
/// When emitting an object file, create and emit a real label. When emitting
317319
/// textual assembly, this should do nothing to avoid polluting our output.
318320
virtual MCSymbol *emitCFILabel();

llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ cl::opt<cl::boolOrDefault> AddLinkageNamesToDeclCallOrigins(
4949
"referenced by DW_AT_call_origin attributes. Enabled by default "
5050
"for -gsce debugger tuning."));
5151

52+
static cl::opt<bool> EmitFuncLineTableOffsetsOption(
53+
"emit-func-debug-line-table-offsets", cl::Hidden,
54+
cl::desc("Include line table offset in function's debug info and emit end "
55+
"sequence after each function's line data."),
56+
cl::init(false));
57+
5258
static bool AddLinkageNamesToDeclCallOriginsForTuning(const DwarfDebug *DD) {
5359
bool EnabledByDefault = DD->tuneForSCE();
5460
if (EnabledByDefault)
@@ -511,7 +517,8 @@ void DwarfCompileUnit::addWasmRelocBaseGlobal(DIELoc *Loc, StringRef GlobalName,
511517
// Find DIE for the given subprogram and attach appropriate DW_AT_low_pc
512518
// and DW_AT_high_pc attributes. If there are global variables in this
513519
// scope then create and insert DIEs for these variables.
514-
DIE &DwarfCompileUnit::updateSubprogramScopeDIE(const DISubprogram *SP) {
520+
DIE &DwarfCompileUnit::updateSubprogramScopeDIE(const DISubprogram *SP,
521+
MCSymbol *LineTableSym) {
515522
DIE *SPDie = getOrCreateSubprogramDIE(SP, includeMinimalInlineScopes());
516523
SmallVector<RangeSpan, 2> BB_List;
517524
// If basic block sections are on, ranges for each basic block section has
@@ -526,6 +533,12 @@ DIE &DwarfCompileUnit::updateSubprogramScopeDIE(const DISubprogram *SP) {
526533
*DD->getCurrentFunction()))
527534
addFlag(*SPDie, dwarf::DW_AT_APPLE_omit_frame_ptr);
528535

536+
if (emitFuncLineTableOffsets() && LineTableSym) {
537+
addSectionLabel(
538+
*SPDie, dwarf::DW_AT_LLVM_stmt_sequence, LineTableSym,
539+
Asm->getObjFileLowering().getDwarfLineSection()->getBeginSymbol());
540+
}
541+
529542
// Only include DW_AT_frame_base in full debug info
530543
if (!includeMinimalInlineScopes()) {
531544
const TargetFrameLowering *TFI = Asm->MF->getSubtarget().getFrameLowering();
@@ -1096,8 +1109,9 @@ sortLocalVars(SmallVectorImpl<DbgVariable *> &Input) {
10961109
}
10971110

10981111
DIE &DwarfCompileUnit::constructSubprogramScopeDIE(const DISubprogram *Sub,
1099-
LexicalScope *Scope) {
1100-
DIE &ScopeDIE = updateSubprogramScopeDIE(Sub);
1112+
LexicalScope *Scope,
1113+
MCSymbol *LineTableSym) {
1114+
DIE &ScopeDIE = updateSubprogramScopeDIE(Sub, LineTableSym);
11011115

11021116
if (Scope) {
11031117
assert(!Scope->getInlinedAt());
@@ -1691,6 +1705,10 @@ bool DwarfCompileUnit::includeMinimalInlineScopes() const {
16911705
(DD->useSplitDwarf() && !Skeleton);
16921706
}
16931707

1708+
bool DwarfCompileUnit::emitFuncLineTableOffsets() const {
1709+
return EmitFuncLineTableOffsetsOption;
1710+
}
1711+
16941712
void DwarfCompileUnit::addAddrTableBase() {
16951713
const TargetLoweringObjectFile &TLOF = Asm->getObjFileLowering();
16961714
MCSymbol *Label = DD->getAddressPool().getLabel();

llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ class DwarfCompileUnit final : public DwarfUnit {
152152

153153
bool includeMinimalInlineScopes() const;
154154

155+
bool emitFuncLineTableOffsets() const;
156+
155157
void initStmtList();
156158

157159
/// Apply the DW_AT_stmt_list from this compile unit to the specified DIE.
@@ -207,10 +209,10 @@ class DwarfCompileUnit final : public DwarfUnit {
207209
void attachLowHighPC(DIE &D, const MCSymbol *Begin, const MCSymbol *End);
208210

209211
/// Find DIE for the given subprogram and attach appropriate
210-
/// DW_AT_low_pc and DW_AT_high_pc attributes. If there are global
211-
/// variables in this scope then create and insert DIEs for these
212-
/// variables.
213-
DIE &updateSubprogramScopeDIE(const DISubprogram *SP);
212+
/// DW_AT_low_pc, DW_AT_high_pc and DW_AT_LLVM_stmt_sequence attributes.
213+
/// If there are global variables in this scope then create and insert DIEs
214+
/// for these variables.
215+
DIE &updateSubprogramScopeDIE(const DISubprogram *SP, MCSymbol *LineTableSym);
214216

215217
void constructScopeDIE(LexicalScope *Scope, DIE &ParentScopeDIE);
216218

@@ -254,8 +256,8 @@ class DwarfCompileUnit final : public DwarfUnit {
254256
DIE *getOrCreateContextDIE(const DIScope *Ty) override;
255257

256258
/// Construct a DIE for this subprogram scope.
257-
DIE &constructSubprogramScopeDIE(const DISubprogram *Sub,
258-
LexicalScope *Scope);
259+
DIE &constructSubprogramScopeDIE(const DISubprogram *Sub, LexicalScope *Scope,
260+
MCSymbol *LineTableSym);
259261

260262
DIE *createAndAddScopeChildren(LexicalScope *Scope, DIE &ScopeDIE);
261263

llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2361,6 +2361,9 @@ void DwarfDebug::beginFunctionImpl(const MachineFunction *MF) {
23612361
return;
23622362

23632363
DwarfCompileUnit &CU = getOrCreateDwarfCompileUnit(SP->getUnit());
2364+
FunctionLineTableLabel = CU.emitFuncLineTableOffsets()
2365+
? Asm->OutStreamer->emitLineTableLabel()
2366+
: nullptr;
23642367

23652368
Asm->OutStreamer->getContext().setDwarfCompileUnitID(
23662369
getDwarfCompileUnitIDForLineTable(CU));
@@ -2474,11 +2477,14 @@ void DwarfDebug::endFunctionImpl(const MachineFunction *MF) {
24742477
}
24752478

24762479
ProcessedSPNodes.insert(SP);
2477-
DIE &ScopeDIE = TheCU.constructSubprogramScopeDIE(SP, FnScope);
2480+
DIE &ScopeDIE =
2481+
TheCU.constructSubprogramScopeDIE(SP, FnScope, FunctionLineTableLabel);
24782482
if (auto *SkelCU = TheCU.getSkeleton())
24792483
if (!LScopes.getAbstractScopesList().empty() &&
24802484
TheCU.getCUNode()->getSplitDebugInlining())
2481-
SkelCU->constructSubprogramScopeDIE(SP, FnScope);
2485+
SkelCU->constructSubprogramScopeDIE(SP, FnScope, FunctionLineTableLabel);
2486+
2487+
FunctionLineTableLabel = nullptr;
24822488

24832489
// Construct call site entries.
24842490
constructCallSiteEntryDIEs(*SP, TheCU, ScopeDIE, *MF);

llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,9 @@ class DwarfDebug : public DebugHandlerBase {
410410
std::pair<std::unique_ptr<DwarfTypeUnit>, const DICompositeType *>, 1>
411411
TypeUnitsUnderConstruction;
412412

413+
/// Symbol pointing to the current function's DWARF line table entries.
414+
MCSymbol *FunctionLineTableLabel;
415+
413416
/// Used to set a uniqe ID for a Type Unit.
414417
/// This counter represents number of DwarfTypeUnits created, not necessarily
415418
/// number of type units that will be emitted.

llvm/lib/MC/MCStreamer.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,20 @@ void MCStreamer::emitCFIEndProcImpl(MCDwarfFrameInfo &Frame) {
483483
Frame.End = (MCSymbol *)1;
484484
}
485485

486+
MCSymbol *MCStreamer::emitLineTableLabel() {
487+
// Create a label and insert it into the line table and return this label
488+
const MCDwarfLoc &DwarfLoc = getContext().getCurrentDwarfLoc();
489+
490+
MCSymbol *LineStreamLabel = getContext().createTempSymbol();
491+
MCDwarfLineEntry LabelLineEntry(nullptr, DwarfLoc, LineStreamLabel);
492+
getContext()
493+
.getMCDwarfLineTable(getContext().getDwarfCompileUnitID())
494+
.getMCLineSections()
495+
.addLineEntry(LabelLineEntry, getCurrentSectionOnly() /*Section*/);
496+
497+
return LineStreamLabel;
498+
}
499+
486500
MCSymbol *MCStreamer::emitCFILabel() {
487501
// Return a dummy non-null value so that label fields appear filled in when
488502
// generating textual assembly.
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
; RUN: llc -O3 -mtriple=i686-w64-mingw32 -o %t_no -filetype=obj %s
2+
; RUN: llvm-dwarfdump -v -all %t_no | FileCheck %s -check-prefix=NO_STMT_SEQ
3+
4+
; RUN: llc -O3 -mtriple=i686-w64-mingw32 -o %t_yes -filetype=obj %s -emit-func-debug-line-table-offsets
5+
; RUN: llvm-dwarfdump -v -all %t_yes | FileCheck %s -check-prefix=STMT_SEQ
6+
7+
; NO_STMT_SEQ-NOT: DW_AT_LLVM_stmt_sequence
8+
9+
; STMT_SEQ: [[[ABBREV_CODE1:[0-9]+]]] DW_TAG_subprogram
10+
; STMT_SEQ: DW_AT_LLVM_stmt_sequence DW_FORM_sec_offset
11+
; STMT_SEQ: [[[ABBREV_CODE2:[0-9]+]]] DW_TAG_subprogram
12+
; STMT_SEQ: DW_AT_LLVM_stmt_sequence DW_FORM_sec_offset
13+
; STMT_SEQ: DW_TAG_subprogram [[[ABBREV_CODE1]]]
14+
; STMT_SEQ: DW_AT_LLVM_stmt_sequence [DW_FORM_sec_offset] (0x00000043)
15+
; STMT_SEQ: DW_AT_name {{.*}}func01
16+
; STMT_SEQ: DW_TAG_subprogram [[[ABBREV_CODE2]]]
17+
; STMT_SEQ: DW_AT_LLVM_stmt_sequence [DW_FORM_sec_offset] (0x00000056)
18+
; STMT_SEQ: DW_AT_name {{.*}}main
19+
20+
;; Check the entire line sequence to see that it's correct
21+
; STMT_SEQ: Address Line Column File ISA Discriminator OpIndex Flags
22+
; STMT_SEQ-NEXT: ------------------ ------ ------ ------ --- ------------- ------- -------------
23+
; STMT_SEQ-NEXT: 0x00000043: 04 DW_LNS_set_file (0)
24+
; STMT_SEQ-NEXT: 0x00000045: 05 DW_LNS_set_column (9)
25+
; STMT_SEQ-NEXT: 0x00000047: 0a DW_LNS_set_prologue_end
26+
; STMT_SEQ-NEXT: 0x00000048: 00 DW_LNE_set_address (0x00000000)
27+
; STMT_SEQ-NEXT: 0x0000004f: 16 address += 0, line += 4, op-index += 0
28+
; STMT_SEQ-NEXT: 0x0000000000000000 5 9 0 0 0 0 is_stmt prologue_end
29+
; STMT_SEQ-NEXT: 0x00000050: 05 DW_LNS_set_column (3)
30+
; STMT_SEQ-NEXT: 0x00000052: 67 address += 6, line += 1, op-index += 0
31+
; STMT_SEQ-NEXT: 0x0000000000000006 6 3 0 0 0 0 is_stmt
32+
; STMT_SEQ-NEXT: 0x00000053: 00 DW_LNE_end_sequence
33+
; STMT_SEQ-NEXT: 0x0000000000000006 6 3 0 0 0 0 is_stmt end_sequence
34+
; STMT_SEQ-NEXT: 0x00000056: 04 DW_LNS_set_file (0)
35+
; STMT_SEQ-NEXT: 0x00000058: 00 DW_LNE_set_address (0x00000008)
36+
; STMT_SEQ-NEXT: 0x0000005f: 03 DW_LNS_advance_line (10)
37+
; STMT_SEQ-NEXT: 0x00000061: 01 DW_LNS_copy
38+
; STMT_SEQ-NEXT: 0x0000000000000008 10 0 0 0 0 0 is_stmt
39+
; STMT_SEQ-NEXT: 0x00000062: 05 DW_LNS_set_column (10)
40+
; STMT_SEQ-NEXT: 0x00000064: 0a DW_LNS_set_prologue_end
41+
; STMT_SEQ-NEXT: 0x00000065: 83 address += 8, line += 1, op-index += 0
42+
; STMT_SEQ-NEXT: 0x0000000000000010 11 10 0 0 0 0 is_stmt prologue_end
43+
; STMT_SEQ-NEXT: 0x00000066: 05 DW_LNS_set_column (3)
44+
; STMT_SEQ-NEXT: 0x00000068: 9f address += 10, line += 1, op-index += 0
45+
; STMT_SEQ-NEXT: 0x000000000000001a 12 3 0 0 0 0 is_stmt
46+
; STMT_SEQ-NEXT: 0x00000069: 02 DW_LNS_advance_pc (addr += 5, op-index += 0)
47+
; STMT_SEQ-NEXT: 0x0000006b: 00 DW_LNE_end_sequence
48+
; STMT_SEQ-NEXT: 0x000000000000001f 12 3 0 0 0 0 is_stmt end_sequence
49+
50+
; generated from:
51+
; clang -Oz -g -S -emit-llvm test.c -o test.ll
52+
; ======= test.c ======
53+
; volatile int g_var1 = 1;
54+
; #define ATTR __attribute__((noinline))
55+
; ATTR int func01() {
56+
; g_var1++;
57+
; func01();
58+
; return 1;
59+
; }
60+
; ATTR int main() {
61+
; g_var1 = 100;
62+
; func01();
63+
; g_var1--;
64+
; return g_var1;
65+
; }
66+
; =====================
67+
68+
69+
; ModuleID = 'test.c'
70+
source_filename = "test.c"
71+
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"
72+
target triple = "x86_64-unknown-linux-gnu"
73+
@g_var1 = dso_local global i32 1, align 4, !dbg !0
74+
; Function Attrs: minsize nofree noinline norecurse noreturn nounwind optsize memory(readwrite, argmem: none) uwtable
75+
define dso_local noundef i32 @func01() local_unnamed_addr #0 !dbg !14 {
76+
entry:
77+
br label %tailrecurse
78+
tailrecurse: ; preds = %tailrecurse, %entry
79+
%0 = load volatile i32, ptr @g_var1, align 4, !dbg !17, !tbaa !18
80+
%inc = add nsw i32 %0, 1, !dbg !17
81+
store volatile i32 %inc, ptr @g_var1, align 4, !dbg !17, !tbaa !18
82+
br label %tailrecurse, !dbg !22
83+
}
84+
; Function Attrs: minsize nofree noinline norecurse noreturn nounwind optsize uwtable
85+
define dso_local noundef i32 @main() local_unnamed_addr #1 !dbg !23 {
86+
entry:
87+
store volatile i32 100, ptr @g_var1, align 4, !dbg !24, !tbaa !18
88+
%call = tail call i32 @func01() #2, !dbg !25
89+
unreachable, !dbg !26
90+
}
91+
attributes #0 = { minsize nofree noinline norecurse noreturn nounwind optsize memory(readwrite, argmem: none) uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
92+
attributes #1 = { minsize nofree noinline norecurse noreturn nounwind optsize uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
93+
attributes #2 = { minsize optsize }
94+
!llvm.dbg.cu = !{!2}
95+
!llvm.module.flags = !{!7, !8, !9, !10, !11, !12}
96+
!llvm.ident = !{!13}
97+
!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
98+
!1 = distinct !DIGlobalVariable(name: "g_var1", scope: !2, file: !3, line: 1, type: !5, isLocal: false, isDefinition: true)
99+
!2 = distinct !DICompileUnit(language: DW_LANG_C11, file: !3, producer: "clang version 20.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None)
100+
!3 = !DIFile(filename: "test.c", directory: "/tmp/tst", checksumkind: CSK_MD5, checksum: "eee003eb3c4fd0a1ff078d3148679e06")
101+
!4 = !{!0}
102+
!5 = !DIDerivedType(tag: DW_TAG_volatile_type, baseType: !6)
103+
!6 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
104+
!7 = !{i32 7, !"Dwarf Version", i32 5}
105+
!8 = !{i32 2, !"Debug Info Version", i32 3}
106+
!9 = !{i32 1, !"wchar_size", i32 4}
107+
!10 = !{i32 8, !"PIC Level", i32 2}
108+
!11 = !{i32 7, !"PIE Level", i32 2}
109+
!12 = !{i32 7, !"uwtable", i32 2}
110+
!13 = !{!"clang version 20.0.0"}
111+
!14 = distinct !DISubprogram(name: "func01", scope: !3, file: !3, line: 4, type: !15, scopeLine: 4, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2)
112+
!15 = !DISubroutineType(types: !16)
113+
!16 = !{!6}
114+
!17 = !DILocation(line: 5, column: 9, scope: !14)
115+
!18 = !{!19, !19, i64 0}
116+
!19 = !{!"int", !20, i64 0}
117+
!20 = !{!"omnipotent char", !21, i64 0}
118+
!21 = !{!"Simple C/C++ TBAA"}
119+
!22 = !DILocation(line: 6, column: 3, scope: !14)
120+
!23 = distinct !DISubprogram(name: "main", scope: !3, file: !3, line: 10, type: !15, scopeLine: 10, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2)
121+
!24 = !DILocation(line: 11, column: 10, scope: !23)
122+
!25 = !DILocation(line: 12, column: 3, scope: !23)
123+
!26 = !DILocation(line: 13, column: 9, scope: !23)

0 commit comments

Comments
 (0)