Index: include/llvm/CodeGen/AsmPrinter.h =================================================================== --- include/llvm/CodeGen/AsmPrinter.h +++ include/llvm/CodeGen/AsmPrinter.h @@ -463,6 +463,9 @@ /// Emit a long directive and value. void emitInt32(int Value) const; + /// Emit a long long directive and value. + void emitInt64(uint64_t Value) const; + /// Emit something like ".long Hi-Lo" where the size in bytes of the directive /// is specified by Size and Hi/Lo specify the labels. This implicitly uses /// .set if it is available. Index: include/llvm/DebugInfo/DWARF/DWARFContext.h =================================================================== --- include/llvm/DebugInfo/DWARF/DWARFContext.h +++ include/llvm/DebugInfo/DWARF/DWARFContext.h @@ -259,7 +259,14 @@ const AppleAcceleratorTable &getAppleObjC(); /// Get a pointer to a parsed line table corresponding to a compile unit. - const DWARFDebugLine::LineTable *getLineTableForUnit(DWARFUnit *cu); + /// Report any parsing issues as warnings on stderr. + const DWARFDebugLine::LineTable *getLineTableForUnit(DWARFUnit *U); + + /// Get a pointer to a parsed line table corresponding to a compile unit. + /// Report any parsing warnings using the callback. + Expected + getLineTableForUnit(DWARFUnit *U, + std::function WarnCallback); DataExtractor getStringExtractor() const { return DataExtractor(DObj->getStringSection(), false, 0); Index: include/llvm/DebugInfo/DWARF/DWARFDebugLine.h =================================================================== --- include/llvm/DebugInfo/DWARF/DWARFDebugLine.h +++ include/llvm/DebugInfo/DWARF/DWARFDebugLine.h @@ -27,6 +27,29 @@ class DWARFUnit; class raw_ostream; +class DebugLineLengthError : public ErrorInfo { +public: + static char ID; + + DebugLineLengthError(const Twine &Msg) : Msg(Msg.str()) {} + + void log(raw_ostream &OS) const override; + std::error_code convertToErrorCode() const override { + return inconvertibleErrorCode(); + } + +private: + std::string Msg; +}; + +/// Report any issues detected during parsing of a debug line table. +/// +/// \param ParseError The Errors returned by a call to LineTable::parse() or +/// Prologue::parse(). +/// +/// \returns true if the prologue length value is a valid length. +bool handleDebugLineParseError(Error ParseError); + class DWARFDebugLine { public: struct FileNameEntry { @@ -120,8 +143,8 @@ void clear(); void dump(raw_ostream &OS, DIDumpOptions DumpOptions) const; - bool parse(const DWARFDataExtractor &DebugLineData, uint32_t *OffsetPtr, - const DWARFContext &Ctx, const DWARFUnit *U = nullptr); + Error parse(const DWARFDataExtractor &DebugLineData, uint32_t *OffsetPtr, + const DWARFContext &Ctx, const DWARFUnit *U = nullptr); }; /// Standard .debug_line state machine structure. @@ -206,6 +229,12 @@ bool containsPC(uint64_t PC) const { return (LowPC <= PC && PC < HighPC); } }; + /// Helper function for DWARFDebugLine parse functions, to report issues that + /// don't prevent parsing the remainder of the table, as warnings. + /// + /// \param Message The message to report. + static void warn(StringRef Message); + struct LineTable { LineTable(); @@ -243,9 +272,10 @@ void clear(); /// Parse prologue and all rows. - bool parse(DWARFDataExtractor &DebugLineData, uint32_t *OffsetPtr, - const DWARFContext &Ctx, const DWARFUnit *U, - raw_ostream *OS = nullptr); + Error parse(DWARFDataExtractor &DebugLineData, uint32_t *OffsetPtr, + const DWARFContext &Ctx, const DWARFUnit *U, + std::function WarnCallback = warn, + raw_ostream *OS = nullptr); using RowVector = std::vector; using RowIter = RowVector::const_iterator; @@ -259,14 +289,16 @@ private: uint32_t findRowInSeq(const DWARFDebugLine::Sequence &Seq, uint64_t Address) const; - Optional getSourceByIndex(uint64_t FileIndex, - DILineInfoSpecifier::FileLineInfoKind Kind) const; + Optional + getSourceByIndex(uint64_t FileIndex, + DILineInfoSpecifier::FileLineInfoKind Kind) const; }; const LineTable *getLineTable(uint32_t Offset) const; - const LineTable *getOrParseLineTable(DWARFDataExtractor &DebugLineData, - uint32_t Offset, const DWARFContext &C, - const DWARFUnit *U); + Expected + getOrParseLineTable(DWARFDataExtractor &DebugLineData, uint32_t Offset, + const DWARFContext &Ctx, const DWARFUnit *U, + std::function WarnCallback = warn); private: struct ParsingState { Index: lib/CodeGen/AsmPrinter/AsmPrinter.cpp =================================================================== --- lib/CodeGen/AsmPrinter/AsmPrinter.cpp +++ lib/CodeGen/AsmPrinter/AsmPrinter.cpp @@ -1938,6 +1938,11 @@ OutStreamer->EmitIntValue(Value, 4); } +/// Emit a long long directive and value. +void AsmPrinter::emitInt64(uint64_t Value) const { + OutStreamer->EmitIntValue(Value, 8); +} + /// Emit something like ".long Hi-Lo" where the size in bytes of the directive /// is specified by Size and Hi/Lo specify the labels. This implicitly uses /// .set if it avoids relocations. Index: lib/DebugInfo/DWARF/DWARFContext.cpp =================================================================== --- lib/DebugInfo/DWARF/DWARFContext.cpp +++ lib/DebugInfo/DWARF/DWARFContext.cpp @@ -389,7 +389,9 @@ if (DumpOffset && Offset != *DumpOffset) { // Find the size of this part of the line table section and skip it. unsigned OldOffset = Offset; - LineTable.Prologue.parse(LineData, &Offset, *this, U); + Error Err = LineTable.Prologue.parse(LineData, &Offset, *this, U); + if (!handleDebugLineParseError(std::move(Err))) + break; Offset = OldOffset + LineTable.Prologue.TotalLength + LineTable.Prologue.sizeofTotalLength(); continue; @@ -399,14 +401,22 @@ OS << "debug_line[" << format("0x%8.8x", Offset) << "]\n"; unsigned OldOffset = Offset; if (DumpOpts.Verbose) { - LineTable.parse(LineData, &Offset, *this, U, &OS); + Error Err = LineTable.parse(LineData, &Offset, *this, U, + DWARFDebugLine::warn, &OS); + if (!handleDebugLineParseError(std::move(Err))) + break; } else { - LineTable.parse(LineData, &Offset, *this, U); - LineTable.dump(OS, DIDumpOptions()); + if (Error Err = LineTable.parse(LineData, &Offset, *this, U)) { + if (!handleDebugLineParseError(std::move(Err))) + break; + } else + LineTable.dump(OS, DIDumpOptions()); } - // Check for unparseable prologue, to avoid infinite loops. - if (OldOffset == Offset) - break; + // If we get here, we can assume that the unit length field is valid, so + // use that to move Offset to past the end of the table, in case it wasn't + // adjusted automatically by the parser. + Offset = OldOffset + LineTable.Prologue.TotalLength + + LineTable.Prologue.sizeofTotalLength(); } } @@ -424,10 +434,13 @@ U = It->second; DWARFDebugLine::LineTable LineTable; unsigned OldOffset = Offset; - if (!LineTable.Prologue.parse(LineData, &Offset, *this, U)) - break; - if (!DumpOffset || OldOffset == *DumpOffset) + if (Error Err = LineTable.Prologue.parse(LineData, &Offset, *this, U)) { + if (!handleDebugLineParseError(std::move(Err))) + break; + } else if (!DumpOffset || OldOffset == *DumpOffset) LineTable.dump(OS, DumpOpts); + Offset = OldOffset + LineTable.Prologue.TotalLength + + LineTable.Prologue.sizeofTotalLength(); } } @@ -786,8 +799,20 @@ DObj->getStringSection(), isLittleEndian()); } -const DWARFLineTable * +const DWARFDebugLine::LineTable * DWARFContext::getLineTableForUnit(DWARFUnit *U) { + Expected ExpectedLineTable = + getLineTableForUnit(U, DWARFDebugLine::warn); + if (!ExpectedLineTable) { + handleDebugLineParseError(ExpectedLineTable.takeError()); + return nullptr; + } + return *ExpectedLineTable; +} + +Expected +DWARFContext::getLineTableForUnit(DWARFUnit *U, + std::function WarnCallback) { if (!Line) Line.reset(new DWARFDebugLine); @@ -811,7 +836,9 @@ // We have to parse it first. DWARFDataExtractor lineData(*DObj, U->getLineSection(), isLittleEndian(), U->getAddressByteSize()); - return Line->getOrParseLineTable(lineData, stmtOffset, *this, U); + + return Line->getOrParseLineTable(lineData, stmtOffset, *this, U, + WarnCallback); } void DWARFContext::parseCompileUnits() { Index: lib/DebugInfo/DWARF/DWARFDebugLine.cpp =================================================================== --- lib/DebugInfo/DWARF/DWARFDebugLine.cpp +++ lib/DebugInfo/DWARF/DWARFDebugLine.cpp @@ -31,6 +31,27 @@ using FileLineInfoKind = DILineInfoSpecifier::FileLineInfoKind; +char DebugLineLengthError::ID = 0; + +void DebugLineLengthError::log(raw_ostream &OS) const { OS << Msg; } + +bool llvm::handleDebugLineParseError(Error ParseError) { + bool CanContinue = true; + auto LogError = [](ErrorInfoBase &Info) { + raw_ostream &Stream = WithColor::warning(); + Info.log(Stream); + Stream << "\n"; + }; + + handleAllErrors(std::move(ParseError), + [&](DebugLineLengthError &Info) { + LogError(Info); + CanContinue = false; + }, + [&](ErrorInfoBase &Info) { LogError(Info); }); + + return CanContinue; +} namespace { struct ContentDescriptor { @@ -273,10 +294,29 @@ return true; } -bool DWARFDebugLine::Prologue::parse(const DWARFDataExtractor &DebugLineData, - uint32_t *OffsetPtr, - const DWARFContext &Ctx, - const DWARFUnit *U) { +template +static std::string formatErrorString(char const *Fmt, const Ts &... Vals) { + std::string Buffer; + raw_string_ostream Stream(Buffer); + Stream << format(Fmt, Vals...); + return Stream.str(); +} + +template +static Error createError(char const *Fmt, const Ts &... Vals) { + return make_error(formatErrorString(Fmt, Vals...), + inconvertibleErrorCode()); +} + +template +static Error createLengthError(char const *Fmt, const Ts &... Vals) { + return make_error(formatErrorString(Fmt, Vals...)); +} + +Error DWARFDebugLine::Prologue::parse(const DWARFDataExtractor &DebugLineData, + uint32_t *OffsetPtr, + const DWARFContext &Ctx, + const DWARFUnit *U) { const uint64_t PrologueOffset = *OffsetPtr; clear(); @@ -285,11 +325,16 @@ FormParams.Format = dwarf::DWARF64; TotalLength = DebugLineData.getU64(OffsetPtr); } else if (TotalLength >= 0xffffff00) { - return false; + return createLengthError( + "parsing line table prologue at offset 0x%8.8" PRIx64 + " unsupported reserved unit length found of value 0x%8.8" PRIx64, + PrologueOffset, TotalLength); } FormParams.Version = DebugLineData.getU16(OffsetPtr); if (getVersion() < 2) - return false; + return createError("parsing line table prologue at offset 0x%8.8" PRIx64 + " found unsupported version 0x%2.2" PRIx16, + PrologueOffset, getVersion()); if (getVersion() >= 5) { FormParams.AddrSize = DebugLineData.getU8(OffsetPtr); @@ -319,26 +364,22 @@ if (!parseV5DirFileTables(DebugLineData, OffsetPtr, EndPrologueOffset, FormParams, Ctx, U, ContentTypes, IncludeDirectories, FileNames)) { - WithColor::warning() << format( + return createError( "parsing line table prologue at 0x%8.8" PRIx64 " found an invalid directory or file table description at" - " 0x%8.8" PRIx64 "\n", + " 0x%8.8" PRIx64, PrologueOffset, (uint64_t)*OffsetPtr); - return false; } } else parseV2DirFileTables(DebugLineData, OffsetPtr, EndPrologueOffset, ContentTypes, IncludeDirectories, FileNames); - if (*OffsetPtr != EndPrologueOffset) { - WithColor::warning() << format( - "parsing line table prologue at 0x%8.8" PRIx64 - " should have ended at 0x%8.8" PRIx64 " but it ended at 0x%8.8" PRIx64 - "\n", - PrologueOffset, EndPrologueOffset, (uint64_t)*OffsetPtr); - return false; - } - return true; + if (*OffsetPtr != EndPrologueOffset) + return createError("parsing line table prologue at 0x%8.8" PRIx64 + " should have ended at 0x%8.8" PRIx64 + " but it ended at 0x%8.8" PRIx64, + PrologueOffset, EndPrologueOffset, (uint64_t)*OffsetPtr); + return Error::success(); } DWARFDebugLine::Row::Row(bool DefaultIsStmt) { reset(DefaultIsStmt); } @@ -447,36 +488,39 @@ return nullptr; } -const DWARFDebugLine::LineTable * -DWARFDebugLine::getOrParseLineTable(DWARFDataExtractor &DebugLineData, - uint32_t Offset, const DWARFContext &Ctx, - const DWARFUnit *U) { +Expected DWARFDebugLine::getOrParseLineTable( + DWARFDataExtractor &DebugLineData, uint32_t Offset, const DWARFContext &Ctx, + const DWARFUnit *U, std::function WarnCallback) { if (!DebugLineData.isValidOffset(Offset)) - return nullptr; + return createLengthError("offset 0x%8.8" PRIx64 + " is not a valid debug line section offset", + Offset); std::pair Pos = LineTableMap.insert(LineTableMapTy::value_type(Offset, LineTable())); LineTable *LT = &Pos.first->second; if (Pos.second) { - if (!LT->parse(DebugLineData, &Offset, Ctx, U)) - return nullptr; + if (Error Err = LT->parse(DebugLineData, &Offset, Ctx, U, WarnCallback)) + return std::move(Err); + return LT; } return LT; } -bool DWARFDebugLine::LineTable::parse(DWARFDataExtractor &DebugLineData, - uint32_t *OffsetPtr, - const DWARFContext &Ctx, - const DWARFUnit *U, raw_ostream *OS) { +void DWARFDebugLine::warn(StringRef Message) { + WithColor::warning() << Message; +} + +Error DWARFDebugLine::LineTable::parse( + DWARFDataExtractor &DebugLineData, uint32_t *OffsetPtr, + const DWARFContext &Ctx, const DWARFUnit *U, + std::function WarnCallback, raw_ostream *OS) { const uint32_t DebugLineOffset = *OffsetPtr; clear(); - if (!Prologue.parse(DebugLineData, OffsetPtr, Ctx, U)) { - // Restore our offset and return false to indicate failure! - *OffsetPtr = DebugLineOffset; - return false; - } + if (Error PrologueErr = Prologue.parse(DebugLineData, OffsetPtr, Ctx, U)) + return PrologueErr; if (OS) { // The presence of OS signals verbose dumping. @@ -554,13 +598,10 @@ if (DebugLineData.getAddressSize() == 0) DebugLineData.setAddressSize(Len - 1); else if (DebugLineData.getAddressSize() != Len - 1) { - WithColor::warning() - << format("mismatching address size at offset 0x%8.8" PRIx32 - " expected 0x%2.2" PRIx8 " found 0x%2.2" PRIx64 "\n", - ExtOffset, DebugLineData.getAddressSize(), Len - 1); - // Skip the rest of the line-number program. - *OffsetPtr = EndOffset; - return false; + return createError("mismatching address size at offset 0x%8.8" PRIx32 + " expected 0x%2.2" PRIx8 " found 0x%2.2" PRIx64, + ExtOffset, DebugLineData.getAddressSize(), + Len - 1); } State.Row.Address = DebugLineData.getRelocatedAddress(OffsetPtr); if (OS) @@ -621,15 +662,10 @@ } // Make sure the stated and parsed lengths are the same. // Otherwise we have an unparseable line-number program. - if (*OffsetPtr - ExtOffset != Len) { - WithColor::warning() - << format("unexpected line op length at offset 0x%8.8" PRIx32 - " expected 0x%2.2" PRIx64 " found 0x%2.2" PRIx32 "\n", - ExtOffset, Len, *OffsetPtr - ExtOffset); - // Skip the rest of the line-number program. - *OffsetPtr = EndOffset; - return false; - } + if (*OffsetPtr - ExtOffset != Len) + return createError("unexpected line op length at offset 0x%8.8" PRIx32 + " expected 0x%2.2" PRIx64 " found 0x%2.2" PRIx32, + ExtOffset, Len, *OffsetPtr - ExtOffset); } else if (Opcode < Prologue.OpcodeBase) { if (OS) *OS << LNStandardString(Opcode); @@ -833,8 +869,7 @@ } if (!State.Sequence.Empty) - WithColor::warning() << "last sequence in debug line table is not" - "terminated!\n"; + WarnCallback("last sequence in debug line table is not terminated!"); // Sort all sequences so that address lookup will work faster. if (!Sequences.empty()) { @@ -847,7 +882,7 @@ // rudimentary sequences for address ranges [0x0, 0xsomething). } - return EndOffset; + return Error::success(); } uint32_t Index: test/DebugInfo/X86/dwarfdump-bogus-LNE.s =================================================================== --- test/DebugInfo/X86/dwarfdump-bogus-LNE.s +++ test/DebugInfo/X86/dwarfdump-bogus-LNE.s @@ -150,100 +150,3 @@ # ERR: warning: unexpected line op length at offset 0x0000005e # ERR-SAME: expected 0x02 found 0x01 - -# The above parsing errors still let us move to the next unit. -# If the prologue is bogus, we need to bail out because we can't -# even find the next unit. - -# DWARF v4 line-table header #3. -LT3_start: - .long LT3_end-LT3_version # Length of Unit (DWARF-32 format) -LT3_version: - .short 4 # DWARF version number - .long LT3_header_end-LT3_params # Length of Prologue -LT3_params: - .byte 1 # Minimum Instruction Length - .byte 1 # Maximum Operations per Instruction - .byte 1 # Default is_stmt - .byte -5 # Line Base - .byte 14 # Line Range - .byte 13 # Opcode Base - .byte 0 # Standard Opcode Lengths - .byte 1 - .byte 1 - .byte 1 - .byte 1 - .byte 0 - .byte 0 - .byte 0 - .byte 1 - .byte 0 - .byte 0 - .byte 1 - # No directories. - .byte 0 - # No files. - .byte 0 - # Extra junk at the end of the prologue, so the length isn't right. - .long 0 -LT3_header_end: - # Real opcode and operand. - .byte 0 - .byte 9 - .byte 2 # DW_LNE_set_address - .quad .text - # Real opcode with incorrect length. - .byte 0 - .byte 2 # Wrong length, should be 1. - .byte 1 # DW_LNE_end_sequence -LT3_end: - -# We should have bailed out above, so never see this in the dump. -# DWARF v4 line-table header #4. -LT4_start: - .long LT4_end-LT4_version # Length of Unit (DWARF-32 format) -LT4_version: - .short 4 # DWARF version number - .long LT4_header_end-LT4_params # Length of Prologue -LT4_params: - .byte 1 # Minimum Instruction Length - .byte 1 # Maximum Operations per Instruction - .byte 1 # Default is_stmt - .byte -5 # Line Base - .byte 14 # Line Range - .byte 13 # Opcode Base - .byte 0 # Standard Opcode Lengths - .byte 1 - .byte 1 - .byte 1 - .byte 1 - .byte 0 - .byte 0 - .byte 0 - .byte 1 - .byte 0 - .byte 0 - .byte 1 - # No directories. - .byte 0 - # No files. - .byte 0 -LT4_header_end: - # Real opcode and operand. - .byte 0 - .byte 9 - .byte 2 # DW_LNE_set_address - .quad .text - # Real opcode with correct length. - .byte 0 - .byte 1 - .byte 1 # DW_LNE_end_sequence -LT4_end: - -# Look for the dump of unit 3, and don't want unit 4. -# CHECK: Line table prologue: -# CHECK-NOT: Line table prologue: - -# And look for the error message. -# ERR: warning: parsing line table prologue at 0x0000005f should have -# ERR-SAME: ended at 0x00000081 but it ended at 0x0000007d Index: test/tools/llvm-dwarfdump/X86/Inputs/debug_line_malformed.s =================================================================== --- test/tools/llvm-dwarfdump/X86/Inputs/debug_line_malformed.s +++ test/tools/llvm-dwarfdump/X86/Inputs/debug_line_malformed.s @@ -0,0 +1,190 @@ +.section .debug_line,"",@progbits +# Leading good section +.long .Lunit1_end - .Lunit1_start # Length of Unit (DWARF-32 format) +.Lunit1_start: +.short 4 # DWARF version number +.long .Lprologue1_end-.Lprologue1_start # Length of Prologue +.Lprologue1_start: +.byte 1 # Minimum Instruction Length +.byte 1 # Maximum Operations per Instruction +.byte 1 # Default is_stmt +.byte -5 # Line Base +.byte 14 # Line Range +.byte 13 # Opcode Base +.byte 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 # Standard Opcode Lengths +.asciz "dir1" # Include table +.asciz "dir2" +.byte 0 +.asciz "file1" # File table +.byte 0, 0, 0 +.asciz "file2" +.byte 1, 0, 0 +.byte 0 +.Lprologue1_end: +.byte 0, 9, 2 # DW_LNE_set_address +.quad 0x0badbeef +.byte 0, 1, 1 # DW_LNE_end_sequence +.Lunit1_end: + +# version 0 +.long .Lunit_v0_end - .Lunit_v0_start # unit length +.Lunit_v0_start: +.short 0 # version +.Lunit_v0_end: + +# version 1 +.long .Lunit_v1_end - .Lunit_v1_start # unit length +.Lunit_v1_start: +.short 1 # version +.Lunit_v1_end: + +# version 5 malformed line/include table +.long .Lunit_v5_end - .Lunit_v5_start # unit length +.Lunit_v5_start: +.short 5 # version +.byte 8 # address size +.byte 8 # segment selector +.long .Lprologue_v5_end-.Lprologue_v5_start # Length of Prologue +.Lprologue_v5_start: +.byte 1 # Minimum Instruction Length +.byte 1 # Maximum Operations per Instruction +.byte 1 # Default is_stmt +.byte -5 # Line Base +.byte 14 # Line Range +.byte 13 # Opcode Base +.byte 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 # Standard Opcode Lengths +.byte 0 # directory table (invalid) +.Lprologue_v5_end: +.Lunit_v5_end: + +# Short prologue +.long .Lunit_short_prologue_end - .Lunit_short_prologue_start # unit length +.Lunit_short_prologue_start: +.short 4 # version +.long .Lprologue_short_prologue_end-.Lprologue_short_prologue_start - 2 # Length of Prologue +.Lprologue_short_prologue_start: +.byte 1 # Minimum Instruction Length +.byte 1 # Maximum Operations per Instruction +.byte 1 # Default is_stmt +.byte -5 # Line Base +.byte 14 # Line Range +.byte 13 # Opcode Base +.byte 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 # Standard Opcode Lengths +.asciz "dir1" # Include table +.asciz "dir2" +.byte 0 +.asciz "file1" # File table +.byte 0, 0, 0 +.asciz "file2" +.byte 1, 0, 0 +.byte 0 +.Lprologue_short_prologue_end: +.Lunit_short_prologue_end: + +# Over-long prologue +.long .Lunit_long_prologue_end - .Lunit_long_prologue_start # unit length +.Lunit_long_prologue_start: +.short 4 # version +.long .Lprologue_long_prologue_end-.Lprologue_long_prologue_start + 1 # Length of Prologue +.Lprologue_long_prologue_start: +.byte 1 # Minimum Instruction Length +.byte 1 # Maximum Operations per Instruction +.byte 1 # Default is_stmt +.byte -5 # Line Base +.byte 14 # Line Range +.byte 13 # Opcode Base +.byte 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 # Standard Opcode Lengths +.asciz "dir1" # Include table +.asciz "dir2" +.byte 0 +.asciz "file1" # File table +.byte 0, 0, 0 +.asciz "file2" +.byte 1, 0, 0 +.byte 0 +.Lprologue_long_prologue_end: +.Lunit_long_prologue_end: + +# Over-long extended opcode +.long .Lunit_long_opcode_end - .Lunit_long_opcode_start # unit length +.Lunit_long_opcode_start: +.short 4 # version +.long .Lprologue_long_opcode_end-.Lprologue_long_opcode_start # Length of Prologue +.Lprologue_long_opcode_start: +.byte 1 # Minimum Instruction Length +.byte 1 # Maximum Operations per Instruction +.byte 1 # Default is_stmt +.byte -5 # Line Base +.byte 14 # Line Range +.byte 13 # Opcode Base +.byte 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 # Standard Opcode Lengths +.asciz "dir1" # Include table +.asciz "dir2" +.byte 0 +.asciz "file1" # File table +.byte 0, 0, 0 +.asciz "file2" +.byte 1, 0, 0 +.byte 0 +.Lprologue_long_opcode_end: +.byte 0, 9, 2 # DW_LNE_set_address +.quad 0xabbadaba +.byte 0, 2, 1 # DW_LNE_end_sequence (wrong length) +.byte 0, 9, 2 # DW_LNE_set_address +.quad 0xbabb1e45 +.byte 0, 1, 1 # DW_LNE_end_sequence (wrong length) +.Lunit_long_opcode_end: + +# No end of sequence +.long .Lunit_no_eos_end - .Lunit_no_eos_start # unit length +.Lunit_no_eos_start: +.short 4 # version +.long .Lprologue_no_eos_end-.Lprologue_no_eos_start # Length of Prologue +.Lprologue_no_eos_start: +.byte 1 # Minimum Instruction Length +.byte 1 # Maximum Operations per Instruction +.byte 1 # Default is_stmt +.byte -5 # Line Base +.byte 14 # Line Range +.byte 13 # Opcode Base +.byte 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 # Standard Opcode Lengths +.asciz "dir1" # Include table +.asciz "dir2" +.byte 0 +.asciz "file1" # File table +.byte 0, 0, 0 +.asciz "file2" +.byte 1, 0, 0 +.byte 0 +.Lprologue_no_eos_end: +.byte 0, 9, 2 # DW_LNE_set_address +.quad 0xdeadfade +.byte 1 # DW_LNS_copy +.Lunit_no_eos_end: + +# Trailing good section +.long .Lunit_good_end - .Lunit_good_start # Length of Unit (DWARF-32 format) +.Lunit_good_start: +.short 4 # DWARF version number +.long .Lprologue_good_end-.Lprologue_good_start # Length of Prologue +.Lprologue_good_start: +.byte 1 # Minimum Instruction Length +.byte 1 # Maximum Operations per Instruction +.byte 1 # Default is_stmt +.byte -5 # Line Base +.byte 14 # Line Range +.byte 13 # Opcode Base +.byte 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 # Standard Opcode Lengths +.asciz "dir1" # Include table +.asciz "dir2" +.byte 0 +.asciz "file1" # File table +.byte 0, 0, 0 +.asciz "file2" +.byte 1, 0, 0 +.byte 0 +.Lprologue_good_end: +.byte 0, 9, 2 # DW_LNE_set_address +.quad 0xcafebabe +.byte 0, 1, 1 # DW_LNE_end_sequence +.Lunit_good_end: Index: test/tools/llvm-dwarfdump/X86/Inputs/debug_line_reserved_length.s =================================================================== --- test/tools/llvm-dwarfdump/X86/Inputs/debug_line_reserved_length.s +++ test/tools/llvm-dwarfdump/X86/Inputs/debug_line_reserved_length.s @@ -0,0 +1,57 @@ +.section .debug_line,"",@progbits +# Leading good section +.long .Lunit1_end - .Lunit1_start # Length of Unit (DWARF-32 format) +.Lunit1_start: +.short 4 # DWARF version number +.long .Lprologue1_end-.Lprologue1_start # Length of Prologue +.Lprologue1_start: +.byte 1 # Minimum Instruction Length +.byte 1 # Maximum Operations per Instruction +.byte 1 # Default is_stmt +.byte -5 # Line Base +.byte 14 # Line Range +.byte 13 # Opcode Base +.byte 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 # Standard Opcode Lengths +.asciz "dir1" # Include table +.asciz "dir2" +.byte 0 +.asciz "file1" # File table +.byte 0, 0, 0 +.asciz "file2" +.byte 1, 0, 0 +.byte 0 +.Lprologue1_end: +.byte 0, 9, 2 # DW_LNE_set_address +.quad 0x0badbeef +.byte 0, 1, 1 # DW_LNE_end_sequence +.Lunit1_end: + +# Malformed section +.long 0xfffffffe # reserved unit length + +# Trailing good section +.long .Lunit3_end - .Lunit3_start # Length of Unit (DWARF-32 format) +.Lunit3_start: +.short 4 # DWARF version number +.long .Lprologue3_end-.Lprologue3_start # Length of Prologue +.Lprologue3_start: +.byte 1 # Minimum Instruction Length +.byte 1 # Maximum Operations per Instruction +.byte 1 # Default is_stmt +.byte -5 # Line Base +.byte 14 # Line Range +.byte 13 # Opcode Base +.byte 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 # Standard Opcode Lengths +.asciz "dir1" # Include table +.asciz "dir2" +.byte 0 +.asciz "file1" # File table +.byte 0, 0, 0 +.asciz "file2" +.byte 1, 0, 0 +.byte 0 +.Lprologue3_end: +.byte 0, 9, 2 # DW_LNE_set_address +.quad 0xcafebabe +.byte 0, 1, 1 # DW_LNE_end_sequence +.Lunit3_end: Index: test/tools/llvm-dwarfdump/X86/debug_line_invalid.test =================================================================== --- test/tools/llvm-dwarfdump/X86/debug_line_invalid.test +++ test/tools/llvm-dwarfdump/X86/debug_line_invalid.test @@ -0,0 +1,87 @@ +# Test the different error cases in the debug line parsing and how they prevent +# or don't prevent further dumping of section contents. + +# RUN: llvm-mc -triple x86_64-pc-linux %S/Inputs/debug_line_reserved_length.s -filetype=obj -o %t-reserved.o +# RUN: llvm-dwarfdump -debug-line %t-reserved.o 2> %t-reserved.err | FileCheck %s --check-prefixes=FIRST,FATAL +# RUN: FileCheck %s --input-file=%t-reserved.err --check-prefix=RESERVED +# RUN: llvm-dwarfdump -debug-line %t-reserved.o -verbose 2> %t-reserved-verbose.err | FileCheck %s --check-prefixes=FIRST,FATAL +# RUN: FileCheck %s --input-file=%t-reserved-verbose.err --check-prefix=RESERVED + +# We should still produce warnings for malformed tables after the specified unit. +# RUN: llvm-dwarfdump -debug-line=0 %t-reserved.o 2> %t-reserved-off-first.err | FileCheck %s --check-prefixes=FIRST,NOLATER +# RUN: FileCheck %s --input-file=%t-reserved-off-first.err --check-prefix=RESERVED + +# Stop looking for the specified unit, if a fatally-bad prologue is detected. +# RUN: llvm-dwarfdump -debug-line=0x4b %t-reserved.o 2> %t-reserved-off-last.err | FileCheck %s --check-prefixes=NOFIRST,NOLATER +# RUN: FileCheck %s --input-file=%t-reserved-off-last.err --check-prefix=RESERVED + +# RUN: llvm-mc -triple x86_64-pc-linux %S/Inputs/debug_line_malformed.s -filetype=obj -o %t-malformed.o +# RUN: llvm-dwarfdump -debug-line %t-malformed.o 2> %t-malformed.err | FileCheck %s --check-prefixes=FIRST,NONFATAL,TERSE +# RUN: FileCheck %s --input-file=%t-malformed.err --check-prefixes=ALL,OTHER +# RUN: llvm-dwarfdump -debug-line %t-malformed.o -verbose 2> %t-malformed-verbose.err | FileCheck %s --check-prefixes=FIRST,NONFATAL,VERBOSE +# RUN: FileCheck %s --input-file=%t-malformed-verbose.err --check-prefixes=ALL,OTHER + +# RUN: llvm-dwarfdump -debug-line=0 %t-malformed.o 2> %t-malformed-off-first.err | FileCheck %s --check-prefixes=FIRST,NOLATER +# RUN: FileCheck %s --input-file=%t-malformed-off-first.err --check-prefix=ALL + +# Don't stop looking for the later unit if non-fatal issues are found. +# RUN: llvm-dwarfdump -debug-line=0x183 %t-malformed.o 2> %t-malformed-off-last.err | FileCheck %s --check-prefixes=LASTONLY +# RUN: FileCheck %s --input-file=%t-malformed-off-last.err --check-prefix=ALL + +# FIRST: debug_line[0x00000000] +# FIRST: 0x000000000badbeef {{.*}} end_sequence +# NOFIRST-NOT: debug_line[0x00000000] +# NOFIRST-NOT: 0x000000000badbeef {{.*}} end_sequence +# NOLATER-NOT: debug_line[{{.*}}] +# NOLATER-NOT: end_sequence + +# For fatal issues, the malformed table should not be dumped, nor should the following table. +# FATAL: debug_line[0x00000048] +# FATAL-NOT: Line table prologue +# FATAL-NOT: debug_line + +# For non-fatal issues, the malformed table should not be dumped, but any subsequent ones should be. +# NONFATAL: debug_line[0x00000048] +# NONFATAL-NOT: Line table prologue +# NONFATAL: debug_line[0x0000004e] +# NONFATAL-NOT: Line table prologue +# NONFATAL: debug_line[0x00000054] +# NONFATAL-NOT: Line table prologue +# NONFATAL: debug_line[0x00000073] +# NONFATAL-NOT: Line table prologue +# NONFATAL: debug_line[0x000000ad] +# NONFATAL-NOT: Line table prologue +# NONFATAL: debug_line[0x000000e7] +# TERSE-NOT: Line table prologue +# Verbose dumping prints the line table prologue and any valid operations up to the point causing +# the problem. +# VERBOSE-NEXT: Line table prologue +# VERBOSE: 0x00000000abbadaba {{.*}} end_sequence +# VERBOSE-NOT: DW_LNE_set_address + +# For minor issues, we can dump the table. +# NONFATAL: debug_line[0x0000013d] +# NONFATAL-NEXT: Line table prologue +# NONFATAL-NOT: debug_line[{{.*}}] +# NONFATAL: 0x00000000deadfade {{.*}} +# NONFATAL: debug_line[0x00000183] +# NONFATAL-NOT: debug_line[{{.*}}] +# NONFATAL: 0x00000000cafebabe {{.*}} end_sequence +# NONFATAL-NOT: debug_line[{{.*}}] + +# LASTONLY-NOT: debug_line[{{.*}}] +# LASTONLY: debug_line[0x00000183] +# LASTONLY: 0x00000000cafebabe {{.*}} end_sequence + +# RESERVED: warning: parsing line table prologue at offset 0x00000048 unsupported reserved unit length found of value 0xfffffffe + +# ALL-NOT: warning: +# ALL: warning: parsing line table prologue at offset 0x00000048 found unsupported version 0x00 +# ALL-NEXT: warning: parsing line table prologue at offset 0x0000004e found unsupported version 0x01 +# ALL-NEXT: warning: parsing line table prologue at 0x00000054 found an invalid directory or file table description at 0x00000073 +# FIXME - The latter offset in the next line should be 0xad. The filename parsing code does not notice a missing terminating byte. +# ALL-NEXT: warning: parsing line table prologue at 0x00000073 should have ended at 0x000000ab but it ended at 0x000000ac +# ALL-NEXT: warning: parsing line table prologue at 0x000000ad should have ended at 0x000000e8 but it ended at 0x000000e7 +# OTHER-NEXT: warning: unexpected line op length at offset 0x0000012e expected 0x02 found 0x01 +# OTHER-NEXT: warning: last sequence in debug line table is not terminated! +# ALL-NOT: warning: Index: tools/dsymutil/DwarfLinker.cpp =================================================================== --- tools/dsymutil/DwarfLinker.cpp +++ tools/dsymutil/DwarfLinker.cpp @@ -3603,7 +3603,10 @@ DWARFDataExtractor LineExtractor( OrigDwarf.getDWARFObj(), OrigDwarf.getDWARFObj().getLineSection(), OrigDwarf.isLittleEndian(), Unit.getOrigUnit().getAddressByteSize()); - LineTable.parse(LineExtractor, &StmtOffset, OrigDwarf, &Unit.getOrigUnit()); + + Error Err = LineTable.parse(LineExtractor, &StmtOffset, OrigDwarf, + &Unit.getOrigUnit()); + handleDebugLineParseError(std::move(Err)); // This vector is the output line table. std::vector NewRows; Index: unittests/DebugInfo/DWARF/DWARFDebugLineTest.cpp =================================================================== --- unittests/DebugInfo/DWARF/DWARFDebugLineTest.cpp +++ unittests/DebugInfo/DWARF/DWARFDebugLineTest.cpp @@ -17,22 +17,32 @@ using namespace llvm; using namespace dwarf; +using namespace dwarfgen; using namespace object; using namespace utils; +using namespace testing; namespace { +struct CommonFixture { + CommonFixture() : LineData("", true, 0){}; -struct DebugLineGenerator { - bool init() { + bool setupGenerator(uint16_t Version = 4) { Triple T = getHostTripleForAddrSize(8); if (!isConfigurationSupported(T)) return false; - auto ExpectedGenerator = dwarfgen::Generator::create(T, 4); + auto ExpectedGenerator = Generator::create(T, Version); if (ExpectedGenerator) Generator.reset(ExpectedGenerator->release()); return true; } + void generate() { + Context = createContext(); + assert(Context != nullptr && "test state is not valid"); + const DWARFObject &Obj = Context->getDWARFObj(); + LineData = DWARFDataExtractor(Obj, Obj.getLineSection(), true, 8); + } + std::unique_ptr createContext() { if (!Generator) return nullptr; @@ -44,21 +54,375 @@ return nullptr; } - std::unique_ptr Generator; + void recordIssue(StringRef Message) { IssueMessage = Message; } + + template + void checkGetOrParseLineTableEmitsError(StringRef ExpectedMsg, + uint64_t Offset = 0) { + auto ExpectedLineTable = Line.getOrParseLineTable( + LineData, Offset, *Context, nullptr, + std::bind(&CommonFixture::recordIssue, this, std::placeholders::_1)); + EXPECT_FALSE(ExpectedLineTable); + EXPECT_TRUE(IssueMessage.empty()); + + Error Err = ExpectedLineTable.takeError(); + ASSERT_TRUE(Err.operator bool()); + Error Remaining = + handleErrors(std::move(Err), [&](const ErrorType &Actual) { + // Use .str(), because googletest doesn't visualise a StringRef + // properly. + EXPECT_EQ(Actual.message(), ExpectedMsg.str()); + }); + EXPECT_FALSE(Remaining); + } + + std::unique_ptr Generator; + std::unique_ptr Context; + DWARFDataExtractor LineData; + DWARFDebugLine Line; + std::string IssueMessage; +}; + +// Fixtures must derive from "Test", but parameterised fixtures from +// "TestWithParam". It does not seem possible to inherit from both, so we share +// the common state in a separate class, inherited by the two fixture classes. +struct DebugLineBasicFixture : public Test, public CommonFixture {}; + +struct DebugLineParameterisedFixture + : public TestWithParam>, + public CommonFixture { + void SetUp() { std::tie(Version, Format) = GetParam(); } + + uint16_t Version; + DwarfFormat Format; }; -TEST(DWARFDebugLine, GetLineTableAtInvalidOffset) { - DebugLineGenerator LineGen; - if (!LineGen.init()) +void checkDefaultPrologue(uint16_t Version, DwarfFormat Format, + DWARFDebugLine::Prologue Prologue, + uint64_t BodyLength) { + // Check version specific fields and values. + uint64_t UnitLength; + uint64_t PrologueLength; + switch (Version) { + case 4: + PrologueLength = 36; + UnitLength = PrologueLength + 2; + EXPECT_EQ(Prologue.MaxOpsPerInst, 1); + break; + case 2: + case 3: + PrologueLength = 35; + UnitLength = PrologueLength + 2; + break; + case 5: + PrologueLength = 39; + UnitLength = PrologueLength + 4; + EXPECT_EQ(Prologue.getAddressSize(), 8); + EXPECT_EQ(Prologue.SegSelectorSize, 0); + break; + default: + llvm_unreachable("unsupported DWARF version"); + } + UnitLength += BodyLength + (Format == DWARF32 ? 4 : 8); + + EXPECT_EQ(Prologue.TotalLength, UnitLength); + EXPECT_EQ(Prologue.PrologueLength, PrologueLength); + EXPECT_EQ(Prologue.MinInstLength, 1); + EXPECT_EQ(Prologue.DefaultIsStmt, 1); + EXPECT_EQ(Prologue.LineBase, -5); + EXPECT_EQ(Prologue.LineRange, 14); + EXPECT_EQ(Prologue.OpcodeBase, 13); + std::vector ExpectedLengths = {0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1}; + EXPECT_EQ(Prologue.StandardOpcodeLengths, ExpectedLengths); + ASSERT_EQ(Prologue.IncludeDirectories.size(), 1); + ASSERT_EQ(Prologue.IncludeDirectories[0].getForm(), DW_FORM_string); + EXPECT_STREQ(*Prologue.IncludeDirectories[0].getAsCString(), "a dir"); + ASSERT_EQ(Prologue.FileNames.size(), 1); + ASSERT_EQ(Prologue.FileNames[0].Name.getForm(), DW_FORM_string); + EXPECT_STREQ(*Prologue.FileNames[0].Name.getAsCString(), "a file"); +} + +TEST_F(DebugLineBasicFixture, GetOrParseLineTableAtInvalidOffset) { + if (!setupGenerator()) return; + generate(); - DWARFDebugLine Line; - std::unique_ptr Context = LineGen.createContext(); - ASSERT_TRUE(Context != nullptr); - const DWARFObject &Obj = Context->getDWARFObj(); - DWARFDataExtractor LineData(Obj, Obj.getLineSection(), true, 8); + checkGetOrParseLineTableEmitsError( + "offset 0x00000000 is not a valid debug line section offset", 0); + // Repeat to show that an error is reported each time. + checkGetOrParseLineTableEmitsError( + "offset 0x00000000 is not a valid debug line section offset", 0); + // Show that an error is reported for later offsets too. + checkGetOrParseLineTableEmitsError( + "offset 0x00000001 is not a valid debug line section offset", 1); +} + +TEST_F(DebugLineBasicFixture, GetOrParseLineTableAtInvalidOffsetAfterData) { + if (!setupGenerator()) + return; + + LineTable < = Generator->addLineTable(); + LT.setCustomPrologue({{0, LineTable::Byte}}); + + generate(); + + checkGetOrParseLineTableEmitsError( + "offset 0x00000001 is not a valid debug line section offset", 1); +} + +TEST_P(DebugLineParameterisedFixture, GetOrParseLineTableValidTable) { + if (!setupGenerator(Version)) + return; + + SCOPED_TRACE("Checking Version " + std::to_string(Version) + ", Format " + + (Format == DWARF64 ? "DWARF64" : "DWARF32")); + + LineTable < = Generator->addLineTable(Format); + LT.addExtendedOpcode(9, DW_LNE_set_address, {{0xadd4e55, LineTable::Quad}}); + LT.addStandardOpcode(DW_LNS_copy, {}); + LT.addByte(0xaa); + LT.addExtendedOpcode(1, DW_LNE_end_sequence, {}); + + LineTable <2 = Generator->addLineTable(Format); + LT2.addExtendedOpcode(9, DW_LNE_set_address, {{0x11223344, LineTable::Quad}}); + LT2.addStandardOpcode(DW_LNS_copy, {}); + LT2.addByte(0xbb); + LT2.addExtendedOpcode(1, DW_LNE_end_sequence, {}); + LT2.addExtendedOpcode(9, DW_LNE_set_address, {{0x55667788, LineTable::Quad}}); + LT2.addStandardOpcode(DW_LNS_copy, {}); + LT2.addByte(0xcc); + LT2.addExtendedOpcode(1, DW_LNE_end_sequence, {}); + + generate(); + + auto RecordIssue = + std::bind(&CommonFixture::recordIssue, this, std::placeholders::_1); + + auto ExpectedLineTable = + Line.getOrParseLineTable(LineData, 0, *Context, nullptr, RecordIssue); + ASSERT_TRUE(ExpectedLineTable.operator bool()); + EXPECT_TRUE(IssueMessage.empty()); + const DWARFDebugLine::LineTable *Expected = *ExpectedLineTable; + checkDefaultPrologue(Version, Format, Expected->Prologue, 16); + EXPECT_EQ(Expected->Sequences.size(), 1); + + uint64_t SecondOffset = + Expected->Prologue.sizeofTotalLength() + Expected->Prologue.TotalLength; + IssueMessage.clear(); + auto ExpectedLineTable2 = Line.getOrParseLineTable( + LineData, SecondOffset, *Context, nullptr, RecordIssue); + ASSERT_TRUE(ExpectedLineTable2.operator bool()); + EXPECT_TRUE(IssueMessage.empty()); + const DWARFDebugLine::LineTable *Expected2 = *ExpectedLineTable2; + checkDefaultPrologue(Version, Format, Expected2->Prologue, 32); + EXPECT_EQ(Expected2->Sequences.size(), 2); + + EXPECT_NE(Expected, Expected2); + + // Check that if the same offset is requested, the exact same pointer is + // returned. + IssueMessage.clear(); + auto ExpectedLineTable3 = + Line.getOrParseLineTable(LineData, 0, *Context, nullptr, RecordIssue); + ASSERT_TRUE(ExpectedLineTable3.operator bool()); + EXPECT_TRUE(IssueMessage.empty()); + EXPECT_EQ(Expected, *ExpectedLineTable3); + + IssueMessage.clear(); + auto ExpectedLineTable4 = Line.getOrParseLineTable( + LineData, SecondOffset, *Context, nullptr, RecordIssue); + ASSERT_TRUE(ExpectedLineTable4.operator bool()); + EXPECT_TRUE(IssueMessage.empty()); + EXPECT_EQ(Expected2, *ExpectedLineTable4); + + // TODO: Add tests that show that the body of the programs have been read + // correctly. +} + +TEST_F(DebugLineBasicFixture, LengthErrorForReservedLength) { + if (!setupGenerator()) + return; + + LineTable < = Generator->addLineTable(); + LT.setCustomPrologue({{0xffffff00, LineTable::Long}}); + + generate(); + + checkGetOrParseLineTableEmitsError( + "parsing line table prologue at offset 0x00000000 unsupported reserved " + "unit length found of value 0xffffff00"); +} + +TEST_F(DebugLineBasicFixture, ErrorForLowVersion) { + if (!setupGenerator()) + return; + + LineTable < = Generator->addLineTable(); + LT.setCustomPrologue( + {{LineTable::Half, LineTable::Long}, {1, LineTable::Half}}); + + generate(); + + checkGetOrParseLineTableEmitsError("parsing line table prologue " + "at offset 0x00000000 found " + "unsupported version 0x01"); +} + +TEST_F(DebugLineBasicFixture, ErrorForInvalidV5IncludeDirTable) { + if (!setupGenerator(5)) + return; + + LineTable < = Generator->addLineTable(); + LT.setCustomPrologue({ + {19, LineTable::Long}, // unit length + {5, LineTable::Half}, // version + {8, LineTable::Byte}, // addr size + {0, LineTable::Byte}, // segment selector size + {11, LineTable::Long}, // prologue length + {1, LineTable::Byte}, // min instruction length + {1, LineTable::Byte}, // max ops per instruction + {1, LineTable::Byte}, // default is_stmt + {0, LineTable::Byte}, // line base + {14, LineTable::Byte}, // line range + {2, LineTable::Byte}, // opcode base (small to reduce the amount of + // setup required). + {0, LineTable::Byte}, // standard opcode lengths + {0, LineTable::Byte}, // directory entry format count (should not be + // zero). + {0, LineTable::ULEB}, // directories count + {0, LineTable::Byte}, // file name entry format count + {0, LineTable::ULEB} // file name entry count + }); + + generate(); + + checkGetOrParseLineTableEmitsError( + "parsing line table prologue at 0x00000000 found an invalid directory or " + "file table description at 0x00000014"); +} + +TEST_P(DebugLineParameterisedFixture, ErrorForTooLargePrologueLength) { + if (!setupGenerator(Version)) + return; + + SCOPED_TRACE("Checking Version " + std::to_string(Version) + ", Format " + + (Format == DWARF64 ? "DWARF64" : "DWARF32")); + + LineTable < = Generator->addLineTable(Format); + DWARFDebugLine::Prologue Prologue = LT.createBasicPrologue(); + ++Prologue.PrologueLength; + LT.setPrologue(Prologue); + + generate(); + + uint64_t ExpectedEnd = + Prologue.TotalLength + 1 + Prologue.sizeofTotalLength(); + checkGetOrParseLineTableEmitsError( + (Twine("parsing line table prologue at 0x00000000 should have ended at " + "0x000000") + + Twine::utohexstr(ExpectedEnd) + " but it ended at 0x000000" + + Twine::utohexstr(ExpectedEnd - 1)) + .str()); +} + +TEST_P(DebugLineParameterisedFixture, ErrorForTooShortPrologueLength) { + if (!setupGenerator(Version)) + return; + + SCOPED_TRACE("Checking Version " + std::to_string(Version) + ", Format " + + (Format == DWARF64 ? "DWARF64" : "DWARF32")); + + LineTable < = Generator->addLineTable(Format); + DWARFDebugLine::Prologue Prologue = LT.createBasicPrologue(); + // FIXME: Ideally, we'd test for 1 less than expected, but the code does not + // currently fail if missing only the terminator of a v2-4 file table. + if (Version < 5) + Prologue.PrologueLength -= 2; + else + Prologue.PrologueLength -= 1; + LT.setPrologue(Prologue); + + generate(); + + uint64_t ExpectedEnd = + Prologue.TotalLength - 1 + Prologue.sizeofTotalLength(); + if (Version < 5) + --ExpectedEnd; + checkGetOrParseLineTableEmitsError( + (Twine("parsing line table prologue at 0x00000000 should have ended at " + "0x000000") + + Twine::utohexstr(ExpectedEnd) + " but it ended at 0x000000" + + Twine::utohexstr(ExpectedEnd + 1)) + .str()); +} + +INSTANTIATE_TEST_CASE_P( + LineTableTestParams, DebugLineParameterisedFixture, + Values(std::make_pair( + 2, DWARF32), // Test lower-bound of v2-3 fields and DWARF32. + std::make_pair(3, DWARF32), // Test upper-bound of v2-3 fields. + std::make_pair(4, DWARF64), // Test v4 fields and DWARF64. + std::make_pair(5, DWARF32), std::make_pair(5, DWARF64))); + +TEST_F(DebugLineBasicFixture, ErrorForInvalidExtendedOpcodeLength) { + if (!setupGenerator()) + return; + + LineTable < = Generator->addLineTable(); + // The Length should be 1 for an end sequence opcode. + LT.addExtendedOpcode(2, DW_LNE_end_sequence, {}); + + generate(); + + checkGetOrParseLineTableEmitsError("unexpected line op length " + "at offset 0x00000030 " + "expected 0x02 found 0x01"); +} + +TEST_F(DebugLineBasicFixture, ErrorForMismatchedAddressSize) { + if (!setupGenerator()) + return; + + LineTable < = Generator->addLineTable(); + // The line data extractor expects size 8 (Quad) addresses. + LT.addExtendedOpcode(5, DW_LNE_set_address, {{0x11223344, LineTable::Long}}); + LT.addStandardOpcode(DW_LNS_copy, {}); + LT.addByte(0xaa); + LT.addExtendedOpcode(1, DW_LNE_end_sequence, {}); + + generate(); + + checkGetOrParseLineTableEmitsError( + "mismatching address size at offset 0x00000030 expected 0x08 found 0x04"); +} + +TEST_F(DebugLineBasicFixture, CallbackUsedForUnterminatedSequence) { + if (!setupGenerator()) + return; + + LineTable < = Generator->addLineTable(); + LT.addExtendedOpcode(9, DW_LNE_set_address, + {{0x1122334455667788, LineTable::Quad}}); + LT.addStandardOpcode(DW_LNS_copy, {}); + LT.addByte(0xaa); + LT.addExtendedOpcode(1, DW_LNE_end_sequence, {}); + LT.addExtendedOpcode(9, DW_LNE_set_address, + {{0x99aabbccddeeff00, LineTable::Quad}}); + LT.addStandardOpcode(DW_LNS_copy, {}); + LT.addByte(0xbb); + LT.addByte(0xcc); + + generate(); - EXPECT_EQ(Line.getOrParseLineTable(LineData, 0, *Context, nullptr), nullptr); + auto ExpectedLineTable = Line.getOrParseLineTable( + LineData, 0, *Context, nullptr, + std::bind(&CommonFixture::recordIssue, this, std::placeholders::_1)); + EXPECT_EQ(IssueMessage, + "last sequence in debug line table is not terminated!"); + ASSERT_TRUE(ExpectedLineTable.operator bool()); + EXPECT_EQ((*ExpectedLineTable)->Rows.size(), 6); + // The unterminated sequence is not added to the sequence list. + EXPECT_EQ((*ExpectedLineTable)->Sequences.size(), 1); } } // end anonymous namespace Index: unittests/DebugInfo/DWARF/DwarfGenerator.h =================================================================== --- unittests/DebugInfo/DWARF/DwarfGenerator.h +++ unittests/DebugInfo/DWARF/DwarfGenerator.h @@ -153,6 +153,74 @@ void setLength(uint64_t Length) { DU.setLength(Length); } }; +/// A DWARF line unit-like class used to generate DWARF line units. +/// +/// Instances of this class are created by instances of the Generator class. +class LineTable { +public: + enum ValueLength { Byte = 1, Half = 2, Long = 4, Quad = 8, ULEB, SLEB }; + + struct ValueAndLength { + uint64_t Value; + ValueLength Length; + }; + + LineTable(Generator &DG, uint16_t Version, dwarf::DwarfFormat Format, + uint8_t AddrSize, uint8_t SegSize = 0) + : DG(DG), Version(Version), Format(Format), AddrSize(AddrSize), + SegSize(SegSize) { + assert(Version >= 2 && Version <= 5 && "unsupported version"); + } + + // Create a Prologue suitable to pass to setPrologue, with a single file and + // include directory entry. + DWARFDebugLine::Prologue createBasicPrologue() const; + + // Set or replace the current prologue with the specified prologue. If no + // prologue is set, a default one will be used when generating. + void setPrologue(DWARFDebugLine::Prologue NewPrologue); + // Used to write an arbitrary payload instead of the standard prologue. This + // is useful if you wish to test handling of corrupt .debug_line sections. + void setCustomPrologue(ArrayRef NewPrologue); + + // Add a byte to the program, with the given value. This can be used to + // specify a special opcode, or to add arbitrary contents to the section. + void addByte(uint8_t Value); + // Add a standard opcode to the program. The opcode and operands do not have + // to be valid. + void addStandardOpcode(uint8_t Opcode, ArrayRef Operands); + // Add an extended opcode to the program with the specified length, opcode, + // and operands. These values do not have to be valid. + void addExtendedOpcode(uint64_t Length, uint8_t Opcode, + ArrayRef Operands); + + // Write the contents of the LineUnit to the current section in the generator. + void generate(MCContext &MC, AsmPrinter &Asm) const; + +private: + void writeData(ArrayRef Data, AsmPrinter &Asm) const; + MCSymbol *writeDefaultPrologue(AsmPrinter &Asm) const; + void writePrologue(AsmPrinter &Asm) const; + + void writeProloguePayload(const DWARFDebugLine::Prologue &Prologue, + AsmPrinter &Asm) const; + + Generator &DG; + llvm::Optional Prologue; + std::vector CustomPrologue; + std::vector Contents; + + // The Version field is used for determining how to write the Prologue, if a + // non-custom prologue is used. The version value actually written, will be + // that specified in the Prologue, if a custom prologue has been passed in. + // Otherwise, it will be this value. + uint16_t Version; + + uint8_t AddrSize; + uint8_t SegSize; + dwarf::DwarfFormat Format; +}; + /// A DWARF generator. /// /// Generate DWARF for unit tests by creating any instance of this class and @@ -173,6 +241,7 @@ BumpPtrAllocator Allocator; std::unique_ptr StringPool; // Entries owned by Allocator. std::vector> CompileUnits; + std::vector> LineTables; DIEAbbrevSet Abbreviations; SmallString<4096> FileBytes; @@ -210,9 +279,17 @@ /// /// \returns a dwarfgen::CompileUnit that can be used to retrieve the compile /// unit dwarfgen::DIE that can be used to add attributes and add child DIE - /// objedts to. + /// objects to. dwarfgen::CompileUnit &addCompileUnit(); + /// Add a line table unit to be generated. + /// \param Format the DWARF format to use (DWARF32 or DWARF64). + /// + /// \returns a dwarfgen::LineTable that can be used to customise the contents + /// of the line table. + LineTable & + addLineTable(dwarf::DwarfFormat DwarfFormat = dwarf::DwarfFormat::DWARF32); + BumpPtrAllocator &getAllocator() { return Allocator; } AsmPrinter *getAsmPrinter() const { return Asm.get(); } MCContext *getMCContext() const { return MC.get(); } Index: unittests/DebugInfo/DWARF/DwarfGenerator.cpp =================================================================== --- unittests/DebugInfo/DWARF/DwarfGenerator.cpp +++ unittests/DebugInfo/DWARF/DwarfGenerator.cpp @@ -106,6 +106,230 @@ } //===----------------------------------------------------------------------===// +/// dwarfgen::LineTable implementation. +//===----------------------------------------------------------------------===// +DWARFDebugLine::Prologue dwarfgen::LineTable::createBasicPrologue() const { + DWARFDebugLine::Prologue P; + switch (Version) { + case 2: + case 3: + P.TotalLength = 41; + P.PrologueLength = 35; + break; + case 4: + P.TotalLength = 42; + P.PrologueLength = 36; + break; + case 5: + P.TotalLength = 47; + P.PrologueLength = 39; + P.FormParams.AddrSize = AddrSize; + break; + default: + llvm_unreachable("unsupported version"); + } + if (Format == DWARF64) { + P.TotalLength += 4; + P.FormParams.Format = DWARF64; + } + P.FormParams.Version = Version; + P.MinInstLength = 1; + P.MaxOpsPerInst = 1; + P.DefaultIsStmt = 1; + P.LineBase = -5; + P.LineRange = 14; + P.OpcodeBase = 13; + P.StandardOpcodeLengths = {0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1}; + P.IncludeDirectories.push_back(DWARFFormValue(DW_FORM_string)); + P.IncludeDirectories.back().setPValue("a dir"); + P.FileNames.push_back(DWARFDebugLine::FileNameEntry()); + P.FileNames.back().Name.setPValue("a file"); + P.FileNames.back().Name.setForm(DW_FORM_string); + return P; +} + +void dwarfgen::LineTable::setPrologue(DWARFDebugLine::Prologue NewPrologue) { + Prologue = NewPrologue; + CustomPrologue.clear(); +} + +void dwarfgen::LineTable::setCustomPrologue( + ArrayRef NewPrologue) { + Prologue.reset(); + CustomPrologue = NewPrologue; +} + +void dwarfgen::LineTable::addByte(uint8_t Value) { + Contents.push_back({Value, Byte}); +} + +void dwarfgen::LineTable::addStandardOpcode(uint8_t Opcode, + ArrayRef Operands) { + Contents.push_back({Opcode, Byte}); + Contents.insert(Contents.end(), Operands.begin(), Operands.end()); +} + +void dwarfgen::LineTable::addExtendedOpcode(uint64_t Length, uint8_t Opcode, + ArrayRef Operands) { + Contents.push_back({0, Byte}); + Contents.push_back({Length, ULEB}); + Contents.push_back({Opcode, Byte}); + Contents.insert(Contents.end(), Operands.begin(), Operands.end()); +} + +void dwarfgen::LineTable::generate(MCContext &MC, AsmPrinter &Asm) const { + MC.setDwarfVersion(Version); + + MCSymbol *EndSymbol = nullptr; + if (!CustomPrologue.empty()) { + writeData(CustomPrologue, Asm); + } else if (!Prologue) { + EndSymbol = writeDefaultPrologue(Asm); + } else { + writePrologue(Asm); + } + + writeData(Contents, Asm); + if (EndSymbol != nullptr) + Asm.OutStreamer->EmitLabel(EndSymbol); +} + +void dwarfgen::LineTable::writeData(ArrayRef Data, + AsmPrinter &Asm) const { + for (auto Entry : Data) { + switch (Entry.Length) { + case Byte: + case Half: + case Long: + case Quad: + Asm.OutStreamer->EmitIntValue(Entry.Value, Entry.Length); + break; + case ULEB: + Asm.EmitULEB128(Entry.Value); + break; + case SLEB: + Asm.EmitSLEB128(Entry.Value); + break; + default: + llvm_unreachable("unsupported ValueAndLength Length value"); + } + } +} + +MCSymbol *dwarfgen::LineTable::writeDefaultPrologue(AsmPrinter &Asm) const { + MCSymbol *UnitStart = Asm.createTempSymbol("line_unit_start"); + MCSymbol *UnitEnd = Asm.createTempSymbol("line_unit_end"); + if (Format == DwarfFormat::DWARF64) { + Asm.emitInt32(0xffffffff); + Asm.EmitLabelDifference(UnitEnd, UnitStart, 8); + } else { + Asm.EmitLabelDifference(UnitEnd, UnitStart, 4); + } + Asm.OutStreamer->EmitLabel(UnitStart); + Asm.emitInt16(Version); + if (Version == 5) { + Asm.emitInt8(AddrSize); + Asm.emitInt8(SegSize); + } + + MCSymbol *PrologueStart = Asm.createTempSymbol("line_prologue_start"); + MCSymbol *PrologueEnd = Asm.createTempSymbol("line_prologue_end"); + Asm.EmitLabelDifference(PrologueEnd, PrologueStart, + Format == DwarfFormat::DWARF64 ? 8 : 4); + Asm.OutStreamer->EmitLabel(PrologueStart); + + DWARFDebugLine::Prologue DefaultPrologue = createBasicPrologue(); + writeProloguePayload(DefaultPrologue, Asm); + Asm.OutStreamer->EmitLabel(PrologueEnd); + return UnitEnd; +} + +void dwarfgen::LineTable::writePrologue(AsmPrinter &Asm) const { + if (Format == DwarfFormat::DWARF64) { + Asm.emitInt32(0xffffffff); + Asm.emitInt64(Prologue->TotalLength); + } else { + Asm.emitInt32(Prologue->TotalLength); + } + Asm.emitInt16(Prologue->getVersion()); + if (Version == 5) { + Asm.emitInt8(Prologue->getAddressSize()); + Asm.emitInt8(Prologue->SegSelectorSize); + } + if (Format == DwarfFormat::DWARF64) + Asm.emitInt64(Prologue->PrologueLength); + else + Asm.emitInt32(Prologue->PrologueLength); + + writeProloguePayload(*Prologue, Asm); +} + +static void writeCString(StringRef Str, AsmPrinter &Asm) { + Asm.OutStreamer->EmitBytes(Str); + Asm.emitInt8(0); +} + +static void writeV2IncludeAndFileTable(const DWARFDebugLine::Prologue &Prologue, + AsmPrinter &Asm) { + for (auto Include : Prologue.IncludeDirectories) { + assert(Include.getAsCString() && "expected a string form for include dir"); + writeCString(*Include.getAsCString(), Asm); + } + Asm.emitInt8(0); + + for (auto File : Prologue.FileNames) { + assert(File.Name.getAsCString() && "expected a string form for file name"); + writeCString(*File.Name.getAsCString(), Asm); + Asm.EmitULEB128(File.DirIdx); + Asm.EmitULEB128(File.ModTime); + Asm.EmitULEB128(File.Length); + } + Asm.emitInt8(0); +} + +static void writeV5IncludeAndFileTable(const DWARFDebugLine::Prologue &Prologue, + AsmPrinter &Asm) { + Asm.emitInt8(1); // directory_entry_format_count. + // TODO: Add support for other content descriptions - we currently only + // support a single DW_LNCT_path/DW_FORM_string. + Asm.EmitULEB128(DW_LNCT_path); + Asm.EmitULEB128(DW_FORM_string); + Asm.EmitULEB128(Prologue.IncludeDirectories.size()); + for (auto Include : Prologue.IncludeDirectories) { + assert(Include.getAsCString() && "expected a string form for include dir"); + writeCString(*Include.getAsCString(), Asm); + } + + Asm.emitInt8(1); // file_name_entry_format_count. + Asm.EmitULEB128(DW_LNCT_path); + Asm.EmitULEB128(DW_FORM_string); + Asm.EmitULEB128(Prologue.FileNames.size()); + for (auto File : Prologue.FileNames) { + assert(File.Name.getAsCString() && "expected a string form for file name"); + writeCString(*File.Name.getAsCString(), Asm); + } +} + +void dwarfgen::LineTable::writeProloguePayload( + const DWARFDebugLine::Prologue &Prologue, AsmPrinter &Asm) const { + Asm.emitInt8(Prologue.MinInstLength); + if (Version >= 4) + Asm.emitInt8(Prologue.MaxOpsPerInst); + Asm.emitInt8(Prologue.DefaultIsStmt); + Asm.emitInt8(Prologue.LineBase); + Asm.emitInt8(Prologue.LineRange); + Asm.emitInt8(Prologue.OpcodeBase); + for (auto Length : Prologue.StandardOpcodeLengths) { + Asm.emitInt8(Length); + } + + if (Version < 5) + writeV2IncludeAndFileTable(Prologue, Asm); + else + writeV5IncludeAndFileTable(Prologue, Asm); +} + +//===----------------------------------------------------------------------===// /// dwarfgen::Generator implementation. //===----------------------------------------------------------------------===// @@ -244,6 +468,11 @@ Asm->emitDwarfDIE(*CU->getUnitDIE().Die); } + MS->SwitchSection(MOFI->getDwarfLineSection()); + for (auto < : LineTables) { + LT->generate(*MC, *Asm); + } + MS->Finish(); if (FileBytes.empty()) return StringRef(); @@ -267,3 +496,9 @@ new CompileUnit(*this, Version, Asm->getPointerSize()))); return *CompileUnits.back(); } + +dwarfgen::LineTable &dwarfgen::Generator::addLineTable(DwarfFormat Format) { + LineTables.push_back(std::unique_ptr( + new LineTable(*this, Version, Format, Asm->getPointerSize()))); + return *LineTables.back(); +}