diff --git a/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugLine.h b/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugLine.h --- a/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugLine.h +++ b/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugLine.h @@ -355,6 +355,7 @@ private: DWARFUnit *prepareToParse(uint64_t Offset); void moveToNextTable(uint64_t OldOffset, const Prologue &P); + bool hasValidVersion(uint64_t Offset); LineToUnitMap LineToUnit; diff --git a/llvm/lib/DebugInfo/DWARF/DWARFDebugLine.cpp b/llvm/lib/DebugInfo/DWARF/DWARFDebugLine.cpp --- a/llvm/lib/DebugInfo/DWARF/DWARFDebugLine.cpp +++ b/llvm/lib/DebugInfo/DWARF/DWARFDebugLine.cpp @@ -1505,6 +1505,21 @@ return U; } +bool DWARFDebugLine::SectionParser::hasValidVersion(uint64_t Offset) { + DataExtractor::Cursor Cursor(Offset); + auto [TotalLength, _] = DebugLineData.getInitialLength(Cursor); + DWARFDataExtractor HeaderData(DebugLineData, Cursor.tell() + TotalLength); + uint16_t Version = HeaderData.getU16(Cursor); + if (!Cursor) { + // Ignore any error here. + // If this is not the end of the section parseNext() will still be + // attempted, where this error will occur again (and can be handled). + consumeError(Cursor.takeError()); + return false; + } + return versionIsSupported(Version); +} + void DWARFDebugLine::SectionParser::moveToNextTable(uint64_t OldOffset, const Prologue &P) { // If the length field is not valid, we don't know where the next table is, so @@ -1518,5 +1533,29 @@ Offset = OldOffset + P.TotalLength + P.sizeofTotalLength(); if (!DebugLineData.isValidOffset(Offset)) { Done = true; + return; + } + + // Heuristic: If the version is valid, then this is probably a line table. + // Otherwise, the offset might need alignment (to a 4 or 8 byte boundary). + if (hasValidVersion(Offset)) + return; + + // ARM C/C++ Compiler aligns each line table to word boundaries and pads out + // the .debug_line section to a word multiple. Note that in the specification + // this does not seem forbidden since each unit has a DW_AT_stmt_list. + for (unsigned Align : {4, 8}) { + uint64_t AlignedOffset = alignTo(Offset, Align); + if (!DebugLineData.isValidOffset(AlignedOffset)) { + // This is almost certainly not another line table but some alignment + // padding. This assumes the alignments tested are ordered, and are + // smaller than the header size (which is true for 4 and 8). + Done = true; + return; + } + if (hasValidVersion(AlignedOffset)) { + Offset = AlignedOffset; + break; + } } } diff --git a/llvm/test/tools/llvm-dwarfdump/ARM/aligned_line_tables.s b/llvm/test/tools/llvm-dwarfdump/ARM/aligned_line_tables.s new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-dwarfdump/ARM/aligned_line_tables.s @@ -0,0 +1,146 @@ +// RUN: llvm-mc %s -defsym ALIGN_8=1 -filetype obj -triple arm-none-eabi -o %t.o +// RUN: llvm-dwarfdump -debug-line %t.o 2>&1 | FileCheck %s --check-prefix=MULT8 +// RUN: llvm-mc %s -defsym ALIGN_4=1 -filetype obj -triple arm-none-eabi -o %t.o +// RUN: llvm-dwarfdump -debug-line %t.o 2>&1 | FileCheck %s --check-prefix=MULT4 +// RUN: llvm-mc %s -defsym UNALIGNED_PADDING=1 -filetype obj -triple arm-none-eabi -o %t.o +// RUN: llvm-dwarfdump -debug-line %t.o 2>&1 | FileCheck %s --check-prefix=UNALIGN + +// This test is based on a real example from ARM C/C++ Compiler. +// It verifies llvm-dwarfdump is able to dump line tables even if they've been +// placed at aligned offsets. + +// MULT8: Address Line Column File ISA Discriminator Flags +// MULT8: ------------------ ------ ------ ------ --- ------------- ------------- +// MULT8: 0x0000000000000000 1 0 1 0 0 is_stmt end_sequence +// MULT8-EMPTY: +// MULT8: debug_line[0x00000028] +// MULT8: Line table prologue: +// MULT8: total_length: 0x0000003a{{$}} +// MULT8: format: DWARF32 +// MULT8: version: 2{{$}} +// MULT8: prologue_length: 0x0000001a +// MULT8: min_inst_length: 2 +// MULT8-NOT: warning: +// MULT8: default_is_stmt: 1 + +// MULT4: Address Line Column File ISA Discriminator Flags +// MULT4: ------------------ ------ ------ ------ --- ------------- ------------- +// MULT4: 0x0000000000000000 1 0 1 0 0 is_stmt end_sequence +// MULT4-EMPTY: +// MULT4: debug_line[0x0000002c] +// MULT4: Line table prologue: +// MULT4: total_length: 0x0000003a{{$}} +// MULT4: format: DWARF32 +// MULT4: version: 2{{$}} +// MULT4: prologue_length: 0x0000001a +// MULT4: min_inst_length: 2 +// MULT4-NOT: warning: +// MULT4: default_is_stmt: 1 + +// This should fail to dump: +// UNALIGN: warning: + +.section .debug_line +// First line table +// Unit total length: +.long .Ltable0_end - .Ltable0_start +.Ltable0_start: +.short 2 // Version +// Header length: +.long .Ltable0_header_end - .Ltable0_header_start +.Ltable0_header_start: +.byte 4 // Min instruction length +.byte 1 // Max operations per instruction +.byte 0 // Default is statement +.byte 6 // Line range +.byte 10 // Opcode base +.byte 0 // standard_opcode_lengths[DW_LNS_copy] = 0 +.byte 1 // standard_opcode_lengths[DW_LNS_advance_pc] = 1 +.byte 1 // standard_opcode_lengths[DW_LNS_advance_line] = 1 +.byte 1 // standard_opcode_lengths[DW_LNS_set_file] = 1 +.byte 1 // standard_opcode_lengths[DW_LNS_set_column] = 1 +.byte 0 // standard_opcode_lengths[DW_LNS_negate_stmt] = 0 +.byte 0 // standard_opcode_lengths[DW_LNS_set_basic_block] = 0 +.byte 0 // standard_opcode_lengths[DW_LNS_const_add_pc] = 0 +.byte 0 // standard_opcode_lengths[DW_LNS_fixed_advance_pc] = 0 +.byte 0 // No include directories +// File name: +.ifdef ALIGN_4 +// Pad out filename so next 4 byte aligned offset is a multiple of 4 and not 8. +.asciz "foobar.cpp" +.else +.asciz "test.c" +.endif +.byte 0 // Dir idx +.byte 0 // Mod time +.byte 0 // Length +.byte 0 // End files +.Ltable0_header_end: +// Line table operations +.byte 0 // Extended opcode +.byte 1 // Length 1 +.byte 1 // DW_LNE_end_sequence +.Ltable0_end: +// End first line table +// Padding: +.ifdef UNALIGNED_PADDING +.short 0 +.else +.byte 0 +.endif +// Second line table +// Unit total length: +.long .Ltable1_end - .Ltable1_start +.Ltable1_start: +.short 2 // Version +// Header length: +.long .Ltable1_header_end - .Ltable1_header_start +.Ltable1_header_start: +.byte 2 // Min instruction length +.byte 1 // Max operations per instruction +.byte 0 // Default is statement +.byte 6 // Line range +.byte 10 // Opcode base +.byte 0 // standard_opcode_lengths[DW_LNS_copy] = 0 +.byte 1 // standard_opcode_lengths[DW_LNS_advance_pc] = 1 +.byte 1 // standard_opcode_lengths[DW_LNS_advance_line] = 1 +.byte 1 // standard_opcode_lengths[DW_LNS_set_file] = 1 +.byte 1 // standard_opcode_lengths[DW_LNS_set_column] = 1 +.byte 0 // standard_opcode_lengths[DW_LNS_negate_stmt] = 0 +.byte 0 // standard_opcode_lengths[DW_LNS_set_basic_block] = 0 +.byte 0 // standard_opcode_lengths[DW_LNS_const_add_pc] = 0 +.byte 0 // standard_opcode_lengths[DW_LNS_fixed_advance_pc] = 0 +.byte 0 // No include directories +.asciz "test.c" // File name +.byte 0 // Dir idx +.byte 0 // Mod time +.byte 0 // Length +.byte 0 // End files +.Ltable1_header_end: +// Line table operations +.byte 4 // DW_LNS_set_file +.byte 1 // File 1 +.byte 5 // DW_LNS_set_column +.byte 1 // Column 1 +.byte 0 // Extended opcode +.byte 5 // Length 5 +.byte 2 // DW_LNE_set_address +.long 32896 // Address = 0x00008080 +.byte 3 // DW_LNS_advance_line +.byte 6 // Line += 6 +.byte 1 // DW_LNS_copy +.byte 5 // DW_LNS_set_column +.byte 2 // Column 2 +.byte 12 // Special opcode (address += 0, line += 2) +.byte 30 // Special opcode (address += 6, line += 2) +.byte 5 // DW_LNS_set_column +.byte 1 // Column 1 +.byte 17 // Special opcode (address += 2, line += 1) +.byte 2 // DW_LNS_advance_pc +.byte 4 // += (4 * min instruction length) +.byte 0 // Extended opcode +.byte 1 // Length 1 +.byte 1 // DW_LNE_end_sequence +.Ltable1_end: +// End second line table +.short 0 // Padding (to make section a word multiple)