Skip to content

[llvm-debuginfo-analyzer] Fix crash with WebAssembly dead code #141616

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 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions llvm/include/llvm/DebugInfo/LogicalView/Core/LVObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ using LVTypes = SmallVector<LVType *, 8>;

using LVOffsets = SmallVector<LVOffset, 8>;

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

enum class LVBinaryType { NONE, ELF, COFF };
Expand Down
9 changes: 9 additions & 0 deletions llvm/include/llvm/DebugInfo/LogicalView/Core/LVReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ class LLVM_ABI LVReader {
LVAddress LowerAddress, LVAddress UpperAddress);
LVRange *getSectionRanges(LVSectionIndex SectionIndex);

// The value is updated for each Compile Unit that is processed.
std::optional<LVAddress> TombstoneAddress;

// Record Compilation Unit entry.
void addCompileUnitOffset(LVOffset Offset, LVScopeCompileUnit *CompileUnit) {
CompileUnits.emplace(Offset, CompileUnit);
Expand Down Expand Up @@ -282,6 +285,12 @@ class LLVM_ABI LVReader {
return CompileUnit->getCPUType();
}

void setTombstoneAddress(LVAddress Address) { TombstoneAddress = Address; }
LVAddress getTombstoneAddress() const {
assert(TombstoneAddress && "Unset tombstone value");
return TombstoneAddress.value();
}

// Access to the scopes root.
LVScopeRoot *getScopesRoot() const { return Root; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,17 @@ class LVCodeViewReader final : public LVBinaryReader {
llvm::object::COFFObjectFile &Obj, ScopedPrinter &W,
StringRef ExePath)
: LVBinaryReader(Filename, FileFormatName, W, LVBinaryType::COFF),
Input(&Obj), ExePath(ExePath), LogicalVisitor(this, W, Input) {}
Input(&Obj), ExePath(ExePath), LogicalVisitor(this, W, Input) {
// CodeView does not have the concept of 'tombstone' address.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment could be a bit more descriptive about why we then set the tombstone to maxaddress - even though CV doesn't have a tombstone?

But my suspicion/theory is that perhaps we can leave this unset? Or further - why is the tombstone a member of the LVReader - could it be only a member of the LVDWARFReader? It's the only one that sets or gets the value?

setTombstoneAddress(MaxAddress);
}
LVCodeViewReader(StringRef Filename, StringRef FileFormatName,
llvm::pdb::PDBFile &Pdb, ScopedPrinter &W, StringRef ExePath)
: LVBinaryReader(Filename, FileFormatName, W, LVBinaryType::COFF),
Input(&Pdb), ExePath(ExePath), LogicalVisitor(this, W, Input) {}
Input(&Pdb), ExePath(ExePath), LogicalVisitor(this, W, Input) {
// CodeView does not have the concept of 'tombstone' address.
setTombstoneAddress(MaxAddress);
}
LVCodeViewReader(const LVCodeViewReader &) = delete;
LVCodeViewReader &operator=(const LVCodeViewReader &) = delete;
~LVCodeViewReader() = default;
Expand Down
15 changes: 11 additions & 4 deletions llvm/lib/DebugInfo/LogicalView/Readers/LVDWARFReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,11 @@ void LVDWARFReader::processOneAttribute(const DWARFDie &Die,
}
}
if (FoundLowPC) {
if (CurrentLowPC == MaxAddress)
if (CurrentLowPC == getTombstoneAddress())
CurrentElement->setIsDiscarded();
// Consider the case of WebAssembly.
CurrentLowPC += WasmCodeSectionOffset;
else
// Consider the case of WebAssembly.
CurrentLowPC += WasmCodeSectionOffset;
if (CurrentElement->isCompileUnit())
setCUBaseAddress(CurrentLowPC);
}
Expand Down Expand Up @@ -271,7 +272,8 @@ void LVDWARFReader::processOneAttribute(const DWARFDie &Die,
DWARFAddressRangesVector Ranges = RangesOrError.get();
for (DWARFAddressRange &Range : Ranges) {
// This seems to be a tombstone for empty ranges.
if (Range.LowPC == Range.HighPC)
if ((Range.LowPC == Range.HighPC) ||
(Range.LowPC = getTombstoneAddress()))
continue;
// Store the real upper limit for the address range.
if (UpdateHighAddress && Range.HighPC > 0)
Expand Down Expand Up @@ -629,6 +631,11 @@ Error LVDWARFReader::createScopes() {
: DwarfContext->dwo_compile_units();
for (const std::unique_ptr<DWARFUnit> &CU : CompileUnits) {

// Take into account the address byte size for a correct 'tombstone'
// value identification.
setTombstoneAddress(
dwarf::computeTombstoneAddress(CU->getAddressByteSize()));

// Deduction of index used for the line records.
//
// For the following test case: test.cpp
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# REQUIRES: x86-registered-target

# llvm-debuginfo-analyzer crashes on dead code
# https://github.com/llvm/llvm-project/issues/136772

# For the attached reproducer:
# llvm-dwarfdump out/lzma-lzmadec.wasm --all
#
# shows:
#
# 0x000002b3: DW_TAG_subprogram
# DW_AT_low_pc (dead code)
# DW_AT_high_pc (0x00000362)
# DW_AT_frame_base (DW_OP_WASM_location 0x0 0x6, DW_OP_stack_value)

# llvm-debuginfo-analyzer out/lzma-lzmadec.wasm --print=instructions
#
# crashes and shows a stack dump:
#
# PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/
# and include the crash backtrace.
# Stack dump:
# 0. Program arguments: llvm-debuginfo-analyzer out/lzma-lzmadec.wasm --print=instructions

Comment on lines +6 to +24
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commentary, and the name of the test file are probably not suitable/may be confusing - they describe the state of the world before this patch, and in somewhat vague terms "this complex test causes this thing to crash" rather than the specific issue it's been debugged down to.

Something more like "wasm-32bit-tombstone" might be a more suitable title, and a sentence describing "test that DWARF tombstones are correctly detected/respected in wasm 32 bit object files"? (& then proceed to describe the test case as done below - creating a single function, tombstoning it in the assembly, etc)

# The test case was produced by the following steps:
#
# // test-clang.cpp
# void foo() {
# }
#
# 1) clang --target=wasm32 -S -g test-clang.cpp
# -o Inputs/wasm-crash-tombstone.s
# 2) Manually changing the DW_AT_low_pc for the DW_TAG_subprogram:
# .Lfunc_begin0 to 0xffffffff to mark the function as dead code:
#
# .int8 2 # Abbrev [2] 0x26:0x1b DW_TAG_subprogram
# .int32 .Lfunc_begin0 # DW_AT_low_pc <---------
# .int32 .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc

# .int8 2 # Abbrev [2] 0x26:0x1b DW_TAG_subprogram
# .int32 0xffffffff # DW_AT_low_pc <---------
# .int32 .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc

# RUN: llvm-mc -arch=wasm32 -filetype=obj \
# RUN: %p/wasm-crash-tombstone.s \
# RUN: -o %t.wasm-crash-tombstone.wasm

# RUN: llvm-debuginfo-analyzer --select-elements=Discarded \
# RUN: --print=elements \
# RUN: %t.wasm-crash-tombstone.wasm 2>&1 | \
# RUN: FileCheck --strict-whitespace -check-prefix=ONE %s

# ONE: Logical View:
# ONE-NEXT: {File} '{{.*}}wasm-crash-tombstone.wasm'
# ONE-EMPTY:
# ONE-NEXT: {CompileUnit} 'test-clang.cpp'
# ONE-NEXT: {Function} not_inlined 'foo' -> 'void'

# RUN: llvm-dwarfdump --debug-info %t.wasm-crash-tombstone.wasm | \
# RUN: FileCheck %s --check-prefix=TWO

# TWO: DW_TAG_subprogram
# TWO-NEXT: DW_AT_low_pc (dead code)
# TWO-NEXT: DW_AT_high_pc
# TWO-NEXT: DW_AT_name ("foo")

.text
.file "test-clang.cpp"
.functype _Z3foov () -> ()
.section .text._Z3foov,"",@
_Z3foov: # @_Z3foov
.Lfunc_begin0:
.functype _Z3foov () -> ()
return
end_function
.Lfunc_end0:
# -- End function
.section .debug_abbrev,"",@
.int8 1 # Abbreviation Code
.int8 17 # DW_TAG_compile_unit
.int8 1 # DW_CHILDREN_yes
.int8 3 # DW_AT_name
.int8 14 # DW_FORM_strp
.int8 17 # DW_AT_low_pc
.int8 1 # DW_FORM_addr
.int8 18 # DW_AT_high_pc
.int8 6 # DW_FORM_data4
.int8 0 # EOM(1)
.int8 0 # EOM(2)
.int8 2 # Abbreviation Code
.int8 46 # DW_TAG_subprogram
.int8 0 # DW_CHILDREN_no
.int8 17 # DW_AT_low_pc
.int8 1 # DW_FORM_addr
.int8 18 # DW_AT_high_pc
.int8 6 # DW_FORM_data4
.int8 3 # DW_AT_name
.int8 14 # DW_FORM_strp
.int8 0 # EOM(1)
.int8 0 # EOM(2)
.int8 0 # EOM(3)
.section .debug_info,"",@
.Lcu_begin0:
.int32 .Ldebug_info_end0-.Ldebug_info_start0 # Length of Unit
.Ldebug_info_start0:
.int16 4 # DWARF version number
.int32 .debug_abbrev0 # Offset Into Abbrev. Section
.int8 4 # Address Size (in bytes)
.int8 1 # Abbrev [1] 0xb:0x37 DW_TAG_compile_unit
.int32 .Linfo_string1 # DW_AT_name
.int32 .Lfunc_begin0 # DW_AT_low_pc
.int32 .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc
.int8 2 # Abbrev [2] 0x26:0x1b DW_TAG_subprogram
.int32 0xffffffff # DW_AT_low_pc
.int32 .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc
.int32 .Linfo_string4 # DW_AT_name
.int8 0 # End Of Children Mark
.Ldebug_info_end0:
.section .debug_str,"S",@
.Linfo_string1:
.asciz "test-clang.cpp" # string offset=176
.Linfo_string4:
.asciz "foo" # string offset=241
.ident "clang version 19.0.0"
Loading