diff options
Diffstat (limited to 'llvm/unittests/DebugInfo')
| -rw-r--r-- | llvm/unittests/DebugInfo/DWARF/DWARFDebugLineTest.cpp | 628 | ||||
| -rw-r--r-- | llvm/unittests/DebugInfo/DWARF/DwarfGenerator.cpp | 238 | ||||
| -rw-r--r-- | llvm/unittests/DebugInfo/DWARF/DwarfGenerator.h | 79 |
3 files changed, 926 insertions, 19 deletions
diff --git a/llvm/unittests/DebugInfo/DWARF/DWARFDebugLineTest.cpp b/llvm/unittests/DebugInfo/DWARF/DWARFDebugLineTest.cpp index 8073b705dc2..8ace067172e 100644 --- a/llvm/unittests/DebugInfo/DWARF/DWARFDebugLineTest.cpp +++ b/llvm/unittests/DebugInfo/DWARF/DWARFDebugLineTest.cpp @@ -17,26 +17,44 @@ 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), + RecordIssue(std::bind(&CommonFixture::recordIssue, this, + std::placeholders::_1)), + FoundError(Error::success()), + RecordError(std::bind(&CommonFixture::recordError, this, + std::placeholders::_1)){}; -struct DebugLineGenerator { - bool init() { + ~CommonFixture() { EXPECT_FALSE(FoundError); } + + 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()); + Gen.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<DWARFContext> createContext() { - if (!Generator) + if (!Gen) return nullptr; - StringRef FileBytes = Generator->generate(); + StringRef FileBytes = Gen->generate(); MemoryBufferRef FileBuffer(FileBytes, "dwarf"); auto Obj = object::ObjectFile::createObjectFile(FileBuffer); if (Obj) @@ -44,21 +62,599 @@ struct DebugLineGenerator { return nullptr; } - std::unique_ptr<dwarfgen::Generator> Generator; + DWARFDebugLine::SectionParser setupParser() { + LineTable < = Gen->addLineTable(DWARF32); + 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 = Gen->addLineTable(DWARF64); + 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, {}); + + generate(); + + return DWARFDebugLine::SectionParser(LineData, *Context, CUs, TUs); + } + + void recordIssue(StringRef Message) { IssueMessage = Message; } + void recordError(Error Err) { + FoundError = joinErrors(std::move(FoundError), std::move(Err)); + } + + void checkError(ArrayRef<StringRef> ExpectedMsgs, Error Err) { + ASSERT_TRUE(Err.operator bool()); + size_t WhichMsg = 0; + Error Remaining = + handleErrors(std::move(Err), [&](const ErrorInfoBase &Actual) { + ASSERT_LT(WhichMsg, ExpectedMsgs.size()); + // Use .str(), because googletest doesn't visualise a StringRef + // properly. + EXPECT_EQ(Actual.message(), ExpectedMsgs[WhichMsg++].str()); + }); + EXPECT_EQ(WhichMsg, ExpectedMsgs.size()); + EXPECT_FALSE(Remaining); + } + + void checkError(StringRef ExpectedMsg, Error Err) { + checkError(ArrayRef<StringRef>{ExpectedMsg}, std::move(Err)); + } + + void checkGetOrParseLineTableEmitsError(StringRef ExpectedMsg, + uint64_t Offset = 0) { + auto ExpectedLineTable = Line.getOrParseLineTable( + LineData, Offset, *Context, nullptr, RecordIssue); + EXPECT_FALSE(ExpectedLineTable); + EXPECT_TRUE(IssueMessage.empty()); + + checkError(ExpectedMsg, ExpectedLineTable.takeError()); + } + + std::unique_ptr<Generator> Gen; + std::unique_ptr<DWARFContext> Context; + DWARFDataExtractor LineData; + DWARFDebugLine Line; + std::string IssueMessage; + std::function<void(StringRef)> RecordIssue; + Error FoundError; + std::function<void(Error)> RecordError; + + SmallVector<std::unique_ptr<DWARFCompileUnit>, 2> CUs; + std::deque<DWARFUnitSection<DWARFTypeUnit>> TUs; +}; + +// 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<std::pair<uint16_t, DwarfFormat>>, + 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<uint8_t> 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<DWARFContext> 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 < = Gen->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 < = Gen->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 = Gen->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 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, ErrorForReservedLength) { + if (!setupGenerator()) + return; + + LineTable < = Gen->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 < = Gen->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 < = Gen->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 < = Gen->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 < = Gen->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 < = Gen->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 < = Gen->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 < = Gen->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(); + + auto ExpectedLineTable = + Line.getOrParseLineTable(LineData, 0, *Context, nullptr, RecordIssue); + 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); +} + +TEST_F(DebugLineBasicFixture, ParserParsesCorrectly) { + if (!setupGenerator()) + return; + + DWARFDebugLine::SectionParser Parser = setupParser(); + + EXPECT_EQ(Parser.getOffset(), 0); + ASSERT_FALSE(Parser.done()); + + DWARFDebugLine::LineTable Parsed = Parser.parseNext(RecordIssue, RecordError); + checkDefaultPrologue(4, DWARF32, Parsed.Prologue, 16); + EXPECT_EQ(Parsed.Sequences.size(), 1); + EXPECT_EQ(Parser.getOffset(), 62); + ASSERT_FALSE(Parser.done()); + + DWARFDebugLine::LineTable Parsed2 = + Parser.parseNext(RecordIssue, RecordError); + checkDefaultPrologue(4, DWARF64, Parsed2.Prologue, 16); + EXPECT_EQ(Parsed2.Sequences.size(), 1); + EXPECT_EQ(Parser.getOffset(), 136); + EXPECT_TRUE(Parser.done()); + + EXPECT_TRUE(IssueMessage.empty()); + EXPECT_FALSE(FoundError); +} + +TEST_F(DebugLineBasicFixture, ParserSkipsCorrectly) { + if (!setupGenerator()) + return; + + DWARFDebugLine::SectionParser Parser = setupParser(); + + EXPECT_EQ(Parser.getOffset(), 0); + ASSERT_FALSE(Parser.done()); + + Parser.skip(RecordError); + EXPECT_EQ(Parser.getOffset(), 62); + ASSERT_FALSE(Parser.done()); + + Parser.skip(RecordError); + EXPECT_EQ(Parser.getOffset(), 136); + EXPECT_TRUE(Parser.done()); + + EXPECT_FALSE(FoundError); +} + +TEST_F(DebugLineBasicFixture, ParserAlwaysDoneForEmptySection) { + if (!setupGenerator()) + return; + + generate(); + DWARFDebugLine::SectionParser Parser(LineData, *Context, CUs, TUs); + + EXPECT_TRUE(Parser.done()); +} + +TEST_F(DebugLineBasicFixture, ParserMovesToEndForBadLengthWhenParsing) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(); + LT.setCustomPrologue({{0xffffff00, LineTable::Long}}); + Gen->addLineTable(); + generate(); + + DWARFDebugLine::SectionParser Parser(LineData, *Context, CUs, TUs); + Parser.parseNext(RecordIssue, RecordError); + + EXPECT_EQ(Parser.getOffset(), 4); + EXPECT_TRUE(Parser.done()); + EXPECT_TRUE(IssueMessage.empty()); + + checkError("parsing line table prologue at offset 0x00000000 unsupported " + "reserved unit length found of value 0xffffff00", + std::move(FoundError)); +} + +TEST_F(DebugLineBasicFixture, ParserMovesToEndForBadLengthWhenSkipping) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(); + LT.setCustomPrologue({{0xffffff00, LineTable::Long}}); + Gen->addLineTable(); + generate(); + + DWARFDebugLine::SectionParser Parser(LineData, *Context, CUs, TUs); + Parser.skip(RecordError); + + EXPECT_EQ(Parser.getOffset(), 4); + EXPECT_TRUE(Parser.done()); + + checkError("parsing line table prologue at offset 0x00000000 unsupported " + "reserved unit length found of value 0xffffff00", + std::move(FoundError)); +} + +TEST_F(DebugLineBasicFixture, ParserReportsFirstErrorInEachTableWhenParsing) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(DWARF32); + LT.setCustomPrologue({{2, LineTable::Long}, {0, LineTable::Half}}); + LineTable <2 = Gen->addLineTable(DWARF32); + LT2.setCustomPrologue({{2, LineTable::Long}, {1, LineTable::Half}}); + generate(); + + DWARFDebugLine::SectionParser Parser(LineData, *Context, CUs, TUs); + Parser.parseNext(RecordIssue, RecordError); + ASSERT_FALSE(Parser.done()); + Parser.parseNext(RecordIssue, RecordError); + + EXPECT_TRUE(Parser.done()); + EXPECT_TRUE(IssueMessage.empty()); + + checkError({"parsing line table prologue at offset 0x00000000 found " + "unsupported version 0x00", + "parsing line table prologue at offset 0x00000006 found " + "unsupported version 0x01"}, + std::move(FoundError)); +} + +TEST_F(DebugLineBasicFixture, ParserReportsNonPrologueProblemsWhenParsing) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(DWARF32); + LT.addExtendedOpcode(0x42, DW_LNE_end_sequence, {}); + LineTable <2 = Gen->addLineTable(DWARF32); + LT2.addExtendedOpcode(9, DW_LNE_set_address, + {{0x1234567890abcdef, LineTable::Quad}}); + LT2.addStandardOpcode(DW_LNS_copy, {}); + LT2.addByte(0xbb); + generate(); + + DWARFDebugLine::SectionParser Parser(LineData, *Context, CUs, TUs); + Parser.parseNext(RecordIssue, RecordError); + EXPECT_TRUE(IssueMessage.empty()); + ASSERT_FALSE(Parser.done()); + checkError( + "unexpected line op length at offset 0x00000030 expected 0x42 found 0x01", + std::move(FoundError)); + + // Reset the error state so that it does not confuse the next set of checks. + FoundError = Error::success(); + Parser.parseNext(RecordIssue, RecordError); + + EXPECT_TRUE(Parser.done()); + EXPECT_EQ(IssueMessage, + "last sequence in debug line table is not terminated!"); + EXPECT_TRUE(!FoundError); +} + +TEST_F(DebugLineBasicFixture, + ParserReportsPrologueErrorsInEachTableWhenSkipping) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(DWARF32); + LT.setCustomPrologue({{2, LineTable::Long}, {0, LineTable::Half}}); + LineTable <2 = Gen->addLineTable(DWARF32); + LT2.setCustomPrologue({{2, LineTable::Long}, {1, LineTable::Half}}); + generate(); + + DWARFDebugLine::SectionParser Parser(LineData, *Context, CUs, TUs); + Parser.skip(RecordError); + ASSERT_FALSE(Parser.done()); + Parser.skip(RecordError); + + EXPECT_TRUE(Parser.done()); + + checkError({"parsing line table prologue at offset 0x00000000 found " + "unsupported version 0x00", + "parsing line table prologue at offset 0x00000006 found " + "unsupported version 0x01"}, + std::move(FoundError)); +} + +TEST_F(DebugLineBasicFixture, ParserIgnoresNonPrologueErrorsWhenSkipping) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(DWARF32); + LT.addExtendedOpcode(42, DW_LNE_end_sequence, {}); + generate(); + + DWARFDebugLine::SectionParser Parser(LineData, *Context, CUs, TUs); + Parser.skip(RecordError); - EXPECT_EQ(Line.getOrParseLineTable(LineData, 0, *Context, nullptr), nullptr); + EXPECT_TRUE(Parser.done()); + EXPECT_TRUE(!FoundError); } } // end anonymous namespace diff --git a/llvm/unittests/DebugInfo/DWARF/DwarfGenerator.cpp b/llvm/unittests/DebugInfo/DWARF/DwarfGenerator.cpp index dfbdf99195d..a6aa1b79070 100644 --- a/llvm/unittests/DebugInfo/DWARF/DwarfGenerator.cpp +++ b/llvm/unittests/DebugInfo/DWARF/DwarfGenerator.cpp @@ -106,6 +106,230 @@ dwarfgen::DIE dwarfgen::CompileUnit::getUnitDIE() { } //===----------------------------------------------------------------------===// +/// 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<ValueAndLength> 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<ValueAndLength> 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<ValueAndLength> 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<ValueAndLength> 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,10 @@ StringRef dwarfgen::Generator::generate() { Asm->emitDwarfDIE(*CU->getUnitDIE().Die); } + MS->SwitchSection(MOFI->getDwarfLineSection()); + for (auto < : LineTables) + LT->generate(*MC, *Asm); + MS->Finish(); if (FileBytes.empty()) return StringRef(); @@ -263,7 +491,13 @@ bool dwarfgen::Generator::saveFile(StringRef Path) { } dwarfgen::CompileUnit &dwarfgen::Generator::addCompileUnit() { - CompileUnits.push_back(std::unique_ptr<CompileUnit>( - new CompileUnit(*this, Version, Asm->getPointerSize()))); + CompileUnits.push_back( + make_unique<CompileUnit>(*this, Version, Asm->getPointerSize())); return *CompileUnits.back(); } + +dwarfgen::LineTable &dwarfgen::Generator::addLineTable(DwarfFormat Format) { + LineTables.push_back( + make_unique<LineTable>(*this, Version, Format, Asm->getPointerSize())); + return *LineTables.back(); +} diff --git a/llvm/unittests/DebugInfo/DWARF/DwarfGenerator.h b/llvm/unittests/DebugInfo/DWARF/DwarfGenerator.h index dd7e8709638..e575af2c29f 100644 --- a/llvm/unittests/DebugInfo/DWARF/DwarfGenerator.h +++ b/llvm/unittests/DebugInfo/DWARF/DwarfGenerator.h @@ -153,6 +153,74 @@ public: 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<ValueAndLength> 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<ValueAndLength> 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<ValueAndLength> 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<ValueAndLength> 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<DWARFDebugLine::Prologue> Prologue; + std::vector<ValueAndLength> CustomPrologue; + std::vector<ValueAndLength> 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; + + dwarf::DwarfFormat Format; + uint8_t AddrSize; + uint8_t SegSize; +}; + /// A DWARF generator. /// /// Generate DWARF for unit tests by creating any instance of this class and @@ -173,6 +241,7 @@ class Generator { BumpPtrAllocator Allocator; std::unique_ptr<DwarfStringPool> StringPool; // Entries owned by Allocator. std::vector<std::unique_ptr<CompileUnit>> CompileUnits; + std::vector<std::unique_ptr<LineTable>> LineTables; DIEAbbrevSet Abbreviations; SmallString<4096> FileBytes; @@ -210,9 +279,17 @@ public: /// /// \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(); } |

