Skip to content

Commit 8b7fc64

Browse files
[llvm-debuginfo-analyzer] Fix crash with WebAssembly dead code (llvm#141616)
llvm#136772 Incorrect handling of 'tombstone' value for WebAssembly. llvm-debuginfo-analyzer already uses the tombstone approach to identify dead code. Currently, the tombstone value is evaluated as std::numeric_limits<uint64_t>::max(). Which is wrong as it does not take into account the 'Address Byte Size' from the Compile Unit header.
1 parent 3ee8dab commit 8b7fc64

File tree

4 files changed

+135
-4
lines changed

4 files changed

+135
-4
lines changed

llvm/include/llvm/DebugInfo/LogicalView/Core/LVObject.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ using LVTypes = SmallVector<LVType *, 8>;
8484

8585
using LVOffsets = SmallVector<LVOffset, 8>;
8686

87+
// The following DWARF documents detail the 'tombstone' concept:
88+
// https://dwarfstd.org/issues/231013.1.html
89+
// https://dwarfstd.org/issues/200609.1.html
90+
//
91+
// The value of the largest representable address offset (for example,
92+
// 0xffffffff when the size of an address is 32 bits).
93+
//
94+
// -1 (0xffffffff) => Valid tombstone
8795
const LVAddress MaxAddress = std::numeric_limits<uint64_t>::max();
8896

8997
enum class LVBinaryType { NONE, ELF, COFF };

llvm/include/llvm/DebugInfo/LogicalView/Readers/LVDWARFReader.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ class LVDWARFReader final : public LVBinaryReader {
5858
bool FoundLowPC = false;
5959
bool FoundHighPC = false;
6060

61+
// The value is updated for each Compile Unit that is processed.
62+
std::optional<LVAddress> TombstoneAddress;
63+
6164
// Cross references (Elements).
6265
using LVElementSet = std::unordered_set<LVElement *>;
6366
struct LVElementEntry {
@@ -127,6 +130,12 @@ class LVDWARFReader final : public LVBinaryReader {
127130
LVAddress getCUHighAddress() const { return CUHighAddress; }
128131
void setCUHighAddress(LVAddress Address) { CUHighAddress = Address; }
129132

133+
void setTombstoneAddress(LVAddress Address) { TombstoneAddress = Address; }
134+
LVAddress getTombstoneAddress() const {
135+
assert(TombstoneAddress && "Unset tombstone value");
136+
return TombstoneAddress.value();
137+
}
138+
130139
const LVSymbols &GetSymbolsWithLocations() const {
131140
return SymbolsWithLocations;
132141
}

llvm/lib/DebugInfo/LogicalView/Readers/LVDWARFReader.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,11 @@ void LVDWARFReader::processOneAttribute(const DWARFDie &Die,
214214
}
215215
}
216216
if (FoundLowPC) {
217-
if (CurrentLowPC == MaxAddress)
217+
if (CurrentLowPC == getTombstoneAddress())
218218
CurrentElement->setIsDiscarded();
219-
// Consider the case of WebAssembly.
220-
CurrentLowPC += WasmCodeSectionOffset;
219+
else
220+
// Consider the case of WebAssembly.
221+
CurrentLowPC += WasmCodeSectionOffset;
221222
if (CurrentElement->isCompileUnit())
222223
setCUBaseAddress(CurrentLowPC);
223224
}
@@ -271,7 +272,8 @@ void LVDWARFReader::processOneAttribute(const DWARFDie &Die,
271272
DWARFAddressRangesVector Ranges = RangesOrError.get();
272273
for (DWARFAddressRange &Range : Ranges) {
273274
// This seems to be a tombstone for empty ranges.
274-
if (Range.LowPC == Range.HighPC)
275+
if ((Range.LowPC == Range.HighPC) ||
276+
(Range.LowPC = getTombstoneAddress()))
275277
continue;
276278
// Store the real upper limit for the address range.
277279
if (UpdateHighAddress && Range.HighPC > 0)
@@ -629,6 +631,11 @@ Error LVDWARFReader::createScopes() {
629631
: DwarfContext->dwo_compile_units();
630632
for (const std::unique_ptr<DWARFUnit> &CU : CompileUnits) {
631633

634+
// Take into account the address byte size for a correct 'tombstone'
635+
// value identification.
636+
setTombstoneAddress(
637+
dwarf::computeTombstoneAddress(CU->getAddressByteSize()));
638+
632639
// Deduction of index used for the line records.
633640
//
634641
// For the following test case: test.cpp
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# REQUIRES: x86-registered-target
2+
3+
# Test that DWARF tombstones are correctly detected/respected in wasm
4+
# 32 bit object files.
5+
6+
# The test case was produced by the following steps:
7+
#
8+
# // test-clang.cpp
9+
# void foo() {
10+
# }
11+
#
12+
# 1) clang --target=wasm32 -S -g test-clang.cpp
13+
# -o Inputs/wasm-32bit-tombstone.s
14+
#
15+
# 2) Creating a single function, tombstoning it in the assembly, by
16+
# manually changing the DW_AT_low_pc for the DW_TAG_subprogram:
17+
# .Lfunc_begin0 to 0xffffffff to mark the function as dead code:
18+
#
19+
# .int8 2 # Abbrev [2] 0x26:0x1b DW_TAG_subprogram
20+
# .int32 .Lfunc_begin0 # DW_AT_low_pc <---------
21+
# .int32 .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc
22+
23+
# .int8 2 # Abbrev [2] 0x26:0x1b DW_TAG_subprogram
24+
# .int32 0xffffffff # DW_AT_low_pc <---------
25+
# .int32 .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc
26+
27+
# RUN: llvm-mc -arch=wasm32 -filetype=obj \
28+
# RUN: %p/wasm-32bit-tombstone.s \
29+
# RUN: -o %t.wasm-32bit-tombstone.wasm
30+
31+
# RUN: llvm-debuginfo-analyzer --select-elements=Discarded \
32+
# RUN: --print=elements \
33+
# RUN: %t.wasm-32bit-tombstone.wasm 2>&1 | \
34+
# RUN: FileCheck --strict-whitespace -check-prefix=ONE %s
35+
36+
# ONE: Logical View:
37+
# ONE-NEXT: {File} '{{.*}}wasm-32bit-tombstone.wasm'
38+
# ONE-EMPTY:
39+
# ONE-NEXT: {CompileUnit} 'test-clang.cpp'
40+
# ONE-NEXT: {Function} not_inlined 'foo' -> 'void'
41+
42+
# RUN: llvm-dwarfdump --debug-info %t.wasm-32bit-tombstone.wasm | \
43+
# RUN: FileCheck %s --check-prefix=TWO
44+
45+
# TWO: DW_TAG_subprogram
46+
# TWO-NEXT: DW_AT_low_pc (dead code)
47+
# TWO-NEXT: DW_AT_high_pc
48+
# TWO-NEXT: DW_AT_name ("foo")
49+
50+
.text
51+
.file "test-clang.cpp"
52+
.functype _Z3foov () -> ()
53+
.section .text._Z3foov,"",@
54+
_Z3foov: # @_Z3foov
55+
.Lfunc_begin0:
56+
.functype _Z3foov () -> ()
57+
return
58+
end_function
59+
.Lfunc_end0:
60+
# -- End function
61+
.section .debug_abbrev,"",@
62+
.int8 1 # Abbreviation Code
63+
.int8 17 # DW_TAG_compile_unit
64+
.int8 1 # DW_CHILDREN_yes
65+
.int8 3 # DW_AT_name
66+
.int8 14 # DW_FORM_strp
67+
.int8 17 # DW_AT_low_pc
68+
.int8 1 # DW_FORM_addr
69+
.int8 18 # DW_AT_high_pc
70+
.int8 6 # DW_FORM_data4
71+
.int8 0 # EOM(1)
72+
.int8 0 # EOM(2)
73+
.int8 2 # Abbreviation Code
74+
.int8 46 # DW_TAG_subprogram
75+
.int8 0 # DW_CHILDREN_no
76+
.int8 17 # DW_AT_low_pc
77+
.int8 1 # DW_FORM_addr
78+
.int8 18 # DW_AT_high_pc
79+
.int8 6 # DW_FORM_data4
80+
.int8 3 # DW_AT_name
81+
.int8 14 # DW_FORM_strp
82+
.int8 0 # EOM(1)
83+
.int8 0 # EOM(2)
84+
.int8 0 # EOM(3)
85+
.section .debug_info,"",@
86+
.Lcu_begin0:
87+
.int32 .Ldebug_info_end0-.Ldebug_info_start0 # Length of Unit
88+
.Ldebug_info_start0:
89+
.int16 4 # DWARF version number
90+
.int32 .debug_abbrev0 # Offset Into Abbrev. Section
91+
.int8 4 # Address Size (in bytes)
92+
.int8 1 # Abbrev [1] 0xb:0x37 DW_TAG_compile_unit
93+
.int32 .Linfo_string1 # DW_AT_name
94+
.int32 .Lfunc_begin0 # DW_AT_low_pc
95+
.int32 .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc
96+
.int8 2 # Abbrev [2] 0x26:0x1b DW_TAG_subprogram
97+
.int32 0xffffffff # DW_AT_low_pc
98+
.int32 .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc
99+
.int32 .Linfo_string4 # DW_AT_name
100+
.int8 0 # End Of Children Mark
101+
.Ldebug_info_end0:
102+
.section .debug_str,"S",@
103+
.Linfo_string1:
104+
.asciz "test-clang.cpp" # string offset=176
105+
.Linfo_string4:
106+
.asciz "foo" # string offset=241
107+
.ident "clang version 19.0.0"

0 commit comments

Comments
 (0)