From ce5526513bd4af0529df844e5d2bfa5a154971dc Mon Sep 17 00:00:00 2001 From: Greg Clayton Date: Mon, 1 May 2017 22:07:02 +0000 Subject: [PATCH] Adds initial llvm-dwarfdump --verify support with unit tests. lldb-dwarfdump gets a new "--verify" option that will verify a single file's DWARF debug info and will print out any errors that it finds. It will return an non-zero exit status if verification fails, and a zero exit status if verification succeeds. Adding the --quiet option will suppress any output the STDOUT or STDERR. The first part of the verify does the following: - verifies that all CU relative references (DW_FORM_ref1, DW_FORM_ref2, DW_FORM_ref4, DW_FORM_ref8, DW_FORM_ref_udata) have valid CU offsets - verifies that all DW_FORM_ref_addr references have valid .debug_info offsets - verifies that all DW_AT_ranges attributes have valid .debug_ranges offsets - verifies that all DW_AT_stmt_list attributes have valid .debug_line offsets - verifies that all DW_FORM_strp attributes have valid .debug_str offsets Unit tests were added for each of the above cases. Differential Revision: https://reviews.llvm.org/D32707 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@301844 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/llvm/DebugInfo/DIContext.h | 4 + include/llvm/DebugInfo/DWARF/DWARFContext.h | 2 + include/llvm/DebugInfo/DWARF/DWARFFormValue.h | 1 + lib/DebugInfo/DWARF/DWARFContext.cpp | 113 ++++++++ lib/DebugInfo/DWARF/DWARFFormValue.cpp | 6 +- tools/llvm-dwarfdump/llvm-dwarfdump.cpp | 53 +++- .../DebugInfo/DWARF/DWARFDebugInfoTest.cpp | 241 ++++++++++++++++++ 7 files changed, 417 insertions(+), 3 deletions(-) diff --git a/include/llvm/DebugInfo/DIContext.h b/include/llvm/DebugInfo/DIContext.h index e3386a8dcd2..d51408122fc 100644 --- a/include/llvm/DebugInfo/DIContext.h +++ b/include/llvm/DebugInfo/DIContext.h @@ -161,6 +161,10 @@ public: virtual void dump(raw_ostream &OS, DIDumpType DumpType = DIDT_All, bool DumpEH = false, bool SummarizeTypes = false) = 0; + virtual bool verify(raw_ostream &OS, DIDumpType DumpType = DIDT_All) { + // No verifier? Just say things went well. + return true; + } virtual DILineInfo getLineInfoForAddress(uint64_t Address, DILineInfoSpecifier Specifier = DILineInfoSpecifier()) = 0; virtual DILineInfoTable getLineInfoForAddressRange(uint64_t Address, diff --git a/include/llvm/DebugInfo/DWARF/DWARFContext.h b/include/llvm/DebugInfo/DWARF/DWARFContext.h index d89e2c684cd..3c04c6716ea 100644 --- a/include/llvm/DebugInfo/DWARF/DWARFContext.h +++ b/include/llvm/DebugInfo/DWARF/DWARFContext.h @@ -106,6 +106,8 @@ public: void dump(raw_ostream &OS, DIDumpType DumpType = DIDT_All, bool DumpEH = false, bool SummarizeTypes = false) override; + bool verify(raw_ostream &OS, DIDumpType DumpType = DIDT_All) override; + typedef DWARFUnitSection::iterator_range cu_iterator_range; typedef DWARFUnitSection::iterator_range tu_iterator_range; typedef iterator_range tu_section_iterator_range; diff --git a/include/llvm/DebugInfo/DWARF/DWARFFormValue.h b/include/llvm/DebugInfo/DWARF/DWARFFormValue.h index c8d7a0c1ac7..36b27228f5c 100644 --- a/include/llvm/DebugInfo/DWARF/DWARFFormValue.h +++ b/include/llvm/DebugInfo/DWARF/DWARFFormValue.h @@ -59,6 +59,7 @@ public: DWARFFormValue(dwarf::Form F = dwarf::Form(0)) : Form(F) {} dwarf::Form getForm() const { return Form; } + uint64_t getRawUValue() const { return Value.uval; } void setForm(dwarf::Form F) { Form = F; } void setUValue(uint64_t V) { Value.uval = V; } void setSValue(int64_t V) { Value.sval = V; } diff --git a/lib/DebugInfo/DWARF/DWARFContext.cpp b/lib/DebugInfo/DWARF/DWARFContext.cpp index 7e8d04672c0..b4ecbf805d1 100644 --- a/lib/DebugInfo/DWARF/DWARFContext.cpp +++ b/lib/DebugInfo/DWARF/DWARFContext.cpp @@ -284,6 +284,119 @@ void DWARFContext::dump(raw_ostream &OS, DIDumpType DumpType, bool DumpEH, getStringSection(), isLittleEndian()); } +bool DWARFContext::verify(raw_ostream &OS, DIDumpType DumpType) { + bool Success = true; + if (DumpType == DIDT_All || DumpType == DIDT_Info) { + OS << "Verifying .debug_info...\n"; + for (const auto &CU : compile_units()) { + unsigned NumDies = CU->getNumDIEs(); + for (unsigned I = 0; I < NumDies; ++I) { + auto Die = CU->getDIEAtIndex(I); + const auto Tag = Die.getTag(); + if (Tag == DW_TAG_null) + continue; + for (auto AttrValue : Die.attributes()) { + const auto Attr = AttrValue.Attr; + const auto Form = AttrValue.Value.getForm(); + switch (Attr) { + case DW_AT_ranges: + // Make sure the offset in the DW_AT_ranges attribute is valid. + if (auto SectionOffset = AttrValue.Value.getAsSectionOffset()) { + if (*SectionOffset >= getRangeSection().Data.size()) { + Success = false; + OS << "error: DW_AT_ranges offset is beyond .debug_ranges " + "bounds:\n"; + Die.dump(OS, 0); + OS << "\n"; + } + } else { + Success = false; + OS << "error: DIE has invalid DW_AT_ranges encoding:\n"; + Die.dump(OS, 0); + OS << "\n"; + } + break; + case DW_AT_stmt_list: + // Make sure the offset in the DW_AT_stmt_list attribute is valid. + if (auto SectionOffset = AttrValue.Value.getAsSectionOffset()) { + if (*SectionOffset >= getLineSection().Data.size()) { + Success = false; + OS << "error: DW_AT_stmt_list offset is beyond .debug_line " + "bounds: " + << format("0x%08" PRIx32, *SectionOffset) << "\n"; + CU->getUnitDIE().dump(OS, 0); + OS << "\n"; + } + } else { + Success = false; + OS << "error: DIE has invalid DW_AT_stmt_list encoding:\n"; + Die.dump(OS, 0); + OS << "\n"; + } + break; + + default: + break; + } + switch (Form) { + case DW_FORM_ref1: + case DW_FORM_ref2: + case DW_FORM_ref4: + case DW_FORM_ref8: + case DW_FORM_ref_udata: { + // Verify all CU relative references are valid CU offsets. + Optional RefVal = AttrValue.Value.getAsReference(); + assert(RefVal); + if (RefVal) { + auto DieCU = Die.getDwarfUnit(); + auto CUSize = DieCU->getNextUnitOffset() - DieCU->getOffset(); + auto CUOffset = AttrValue.Value.getRawUValue(); + if (CUOffset >= CUSize) { + Success = false; + OS << "error: " << FormEncodingString(Form) << " CU offset " + << format("0x%08" PRIx32, CUOffset) + << " is invalid (must be less than CU size of " + << format("0x%08" PRIx32, CUSize) << "):\n"; + Die.dump(OS, 0); + OS << "\n"; + } + } + break; + } + case DW_FORM_ref_addr: { + // Verify all absolute DIE references have valid offsets in the + // .debug_info section. + Optional RefVal = AttrValue.Value.getAsReference(); + assert(RefVal); + if (RefVal && *RefVal >= getInfoSection().Data.size()) { + Success = false; + OS << "error: DW_FORM_ref_addr offset beyond .debug_info " + "bounds:\n"; + Die.dump(OS, 0); + OS << "\n"; + } + break; + } + case DW_FORM_strp: { + auto SecOffset = AttrValue.Value.getAsSectionOffset(); + assert(SecOffset); // DW_FORM_strp is a section offset. + if (SecOffset && *SecOffset >= getStringSection().size()) { + Success = false; + OS << "error: DW_FORM_strp offset beyond .debug_str bounds:\n"; + Die.dump(OS, 0); + OS << "\n"; + } + break; + } + default: + break; + } + } + } + } + } + return Success; +} const DWARFUnitIndex &DWARFContext::getCUIndex() { if (CUIndex) return *CUIndex; diff --git a/lib/DebugInfo/DWARF/DWARFFormValue.cpp b/lib/DebugInfo/DWARF/DWARFFormValue.cpp index 28592e4dfb6..7f827de8924 100644 --- a/lib/DebugInfo/DWARF/DWARFFormValue.cpp +++ b/lib/DebugInfo/DWARF/DWARFFormValue.cpp @@ -309,8 +309,10 @@ bool DWARFFormValue::isFormClass(DWARFFormValue::FormClass FC) const { } // In DWARF3 DW_FORM_data4 and DW_FORM_data8 served also as a section offset. // Don't check for DWARF version here, as some producers may still do this - // by mistake. - return (Form == DW_FORM_data4 || Form == DW_FORM_data8) && + // by mistake. Also accept DW_FORM_strp since this is .debug_str section + // offset. + return (Form == DW_FORM_data4 || Form == DW_FORM_data8 || + Form == DW_FORM_strp) && FC == FC_SectionOffset; } diff --git a/tools/llvm-dwarfdump/llvm-dwarfdump.cpp b/tools/llvm-dwarfdump/llvm-dwarfdump.cpp index 84fa0e4d2d9..8ecf1848099 100644 --- a/tools/llvm-dwarfdump/llvm-dwarfdump.cpp +++ b/tools/llvm-dwarfdump/llvm-dwarfdump.cpp @@ -78,6 +78,11 @@ static cl::opt SummarizeTypes("summarize-types", cl::desc("Abbreviate the description of type unit entries")); +static cl::opt Verify("verify", cl::desc("Verify the DWARF debug info")); + +static cl::opt Quiet("quiet", + cl::desc("Use with -verify to not emit to STDOUT.")); + static void error(StringRef Filename, std::error_code EC) { if (!EC) return; @@ -116,6 +121,46 @@ static void DumpInput(StringRef Filename) { } } +static bool VerifyObjectFile(ObjectFile &Obj, Twine Filename) { + std::unique_ptr DICtx(new DWARFContextInMemory(Obj)); + + // Verify the DWARF and exit with non-zero exit status if verification + // fails. + raw_ostream &stream = Quiet ? nulls() : outs(); + stream << "Verifying " << Filename.str() << ":\tfile format " + << Obj.getFileFormatName() << "\n"; + bool Result = DICtx->verify(stream, DumpType); + if (Result) + stream << "No errors.\n"; + else + stream << "Errors detected.\n"; + return Result; +} + +static bool VerifyInput(StringRef Filename) { + ErrorOr> BuffOrErr = + MemoryBuffer::getFileOrSTDIN(Filename); + error(Filename, BuffOrErr.getError()); + std::unique_ptr Buff = std::move(BuffOrErr.get()); + + Expected> BinOrErr = + object::createBinary(Buff->getMemBufferRef()); + if (!BinOrErr) + error(Filename, errorToErrorCode(BinOrErr.takeError())); + + bool Result = true; + if (auto *Obj = dyn_cast(BinOrErr->get())) + Result = VerifyObjectFile(*Obj, Filename); + else if (auto *Fat = dyn_cast(BinOrErr->get())) + for (auto &ObjForArch : Fat->objects()) { + auto MachOOrErr = ObjForArch.getAsObjectFile(); + error(Filename, errorToErrorCode(MachOOrErr.takeError())); + if (!VerifyObjectFile(**MachOOrErr, Filename + " (" + ObjForArch.getArchFlagName() + ")")) + Result = false; + } + return Result; +} + /// If the input path is a .dSYM bundle (as created by the dsymutil tool), /// replace it with individual entries for each of the object files inside the /// bundle otherwise return the input path. @@ -168,7 +213,13 @@ int main(int argc, char **argv) { Objects.insert(Objects.end(), Objs.begin(), Objs.end()); } - std::for_each(Objects.begin(), Objects.end(), DumpInput); + if (Verify) { + // If we encountered errors during verify, exit with a non-zero exit status. + if (!std::all_of(Objects.begin(), Objects.end(), VerifyInput)) + exit(1); + } else { + std::for_each(Objects.begin(), Objects.end(), DumpInput); + } return EXIT_SUCCESS; } diff --git a/unittests/DebugInfo/DWARF/DWARFDebugInfoTest.cpp b/unittests/DebugInfo/DWARF/DWARFDebugInfoTest.cpp index 2078e3a96a8..3f0e3dab72b 100644 --- a/unittests/DebugInfo/DWARF/DWARFDebugInfoTest.cpp +++ b/unittests/DebugInfo/DWARF/DWARFDebugInfoTest.cpp @@ -1667,4 +1667,245 @@ TEST(DWARFDebugInfo, TestImplicitConstAbbrevs) { EXPECT_EQ(DIEs.find(Val2)->second, AbbrevPtrVal2); } +TEST(DWARFDebugInfo, TestDwarfVerifyInvalidCURef) { + // Create a single compile unit with a single function that has a DW_AT_type + // that is CU relative. The CU offset is not valid becuase it is larger than + // the compile unit itself. + + const char *yamldata = R"( + debug_str: + - '' + - /tmp/main.c + - main + debug_abbrev: + - Code: 0x00000001 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Code: 0x00000002 + Tag: DW_TAG_subprogram + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_type + Form: DW_FORM_ref4 + debug_info: + - Length: + TotalLength: 22 + Version: 4 + AbbrOffset: 0 + AddrSize: 8 + Entries: + - AbbrCode: 0x00000001 + Values: + - Value: 0x0000000000000001 + - AbbrCode: 0x00000002 + Values: + - Value: 0x000000000000000D + - Value: 0x0000000000001234 + - AbbrCode: 0x00000000 + Values: + )"; + auto ErrOrSections = DWARFYAML::EmitDebugSections(StringRef(yamldata)); + ASSERT_TRUE((bool)ErrOrSections); + + auto &DebugSections = *ErrOrSections; + + DWARFContextInMemory DwarfContext(DebugSections, 8); + + std::string str; + raw_string_ostream strm(str); + EXPECT_FALSE(DwarfContext.verify(strm, DIDT_All)); + const char *err = "error: DW_FORM_ref4 CU offset 0x00001234 is invalid " + "(must be less than CU size of 0x0000001a):"; + EXPECT_TRUE(strm.str().find(err) != std::string::npos); +} + +TEST(DWARFDebugInfo, TestDwarfVerifyInvalidRefAddr) { + // Create a single compile unit with a single function that has an invalid + // DW_AT_type with an invalid .debug_info offset in its DW_FORM_ref_addr. + const char *yamldata = R"( + debug_str: + - '' + - /tmp/main.c + - main + debug_abbrev: + - Code: 0x00000001 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Code: 0x00000002 + Tag: DW_TAG_subprogram + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_type + Form: DW_FORM_ref_addr + debug_info: + - Length: + TotalLength: 22 + Version: 4 + AbbrOffset: 0 + AddrSize: 8 + Entries: + - AbbrCode: 0x00000001 + Values: + - Value: 0x0000000000000001 + - AbbrCode: 0x00000002 + Values: + - Value: 0x000000000000000D + - Value: 0x0000000000001234 + - AbbrCode: 0x00000000 + Values: + )"; + auto ErrOrSections = DWARFYAML::EmitDebugSections(StringRef(yamldata)); + ASSERT_TRUE((bool)ErrOrSections); + + auto &DebugSections = *ErrOrSections; + + DWARFContextInMemory DwarfContext(DebugSections, 8); + + std::string str; + raw_string_ostream strm(str); + EXPECT_FALSE(DwarfContext.verify(strm, DIDT_All)); + strm.flush(); + const char *err = "error: DW_FORM_ref_addr offset beyond .debug_info bounds:"; + EXPECT_TRUE(strm.str().find(err) != std::string::npos); +} + +TEST(DWARFDebugInfo, TestDwarfVerifyInvalidRanges) { + // Create a single compile unit with a DW_AT_ranges whose section offset + // isn't valid. + const char *yamldata = R"( + debug_str: + - '' + - /tmp/main.c + debug_abbrev: + - Code: 0x00000001 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_ranges + Form: DW_FORM_sec_offset + debug_info: + - Length: + TotalLength: 16 + Version: 4 + AbbrOffset: 0 + AddrSize: 8 + Entries: + - AbbrCode: 0x00000001 + Values: + - Value: 0x0000000000000001 + - Value: 0x0000000000001000 + + )"; + auto ErrOrSections = DWARFYAML::EmitDebugSections(StringRef(yamldata)); + ASSERT_TRUE((bool)ErrOrSections); + + auto &DebugSections = *ErrOrSections; + + DWARFContextInMemory DwarfContext(DebugSections, 8); + + std::string str; + raw_string_ostream strm(str); + EXPECT_FALSE(DwarfContext.verify(strm, DIDT_All)); + strm.flush(); + const char *err = "error: DW_AT_ranges offset is beyond .debug_ranges " + "bounds:"; + EXPECT_TRUE(strm.str().find(err) != std::string::npos); +} + +TEST(DWARFDebugInfo, TestDwarfVerifyInvalidStmtList) { + // Create a single compile unit with a DW_AT_stmt_list whose section offset + // isn't valid. + const char *yamldata = R"( + debug_str: + - '' + - /tmp/main.c + debug_abbrev: + - Code: 0x00000001 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_stmt_list + Form: DW_FORM_sec_offset + debug_info: + - Length: + TotalLength: 16 + Version: 4 + AbbrOffset: 0 + AddrSize: 8 + Entries: + - AbbrCode: 0x00000001 + Values: + - Value: 0x0000000000000001 + - Value: 0x0000000000001000 + + )"; + auto ErrOrSections = DWARFYAML::EmitDebugSections(StringRef(yamldata)); + ASSERT_TRUE((bool)ErrOrSections); + + auto &DebugSections = *ErrOrSections; + + DWARFContextInMemory DwarfContext(DebugSections, 8); + + std::string str; + raw_string_ostream strm(str); + EXPECT_FALSE(DwarfContext.verify(strm, DIDT_All)); + strm.flush(); + const char *err = "error: DW_AT_stmt_list offset is beyond .debug_line " + "bounds: 0x00001000"; + EXPECT_TRUE(strm.str().find(err) != std::string::npos); +} + +TEST(DWARFDebugInfo, TestDwarfVerifyInvalidStrp) { + // Create a single compile unit with a single function that has an invalid + // DW_FORM_strp for the DW_AT_name. + const char *yamldata = R"( + debug_str: + - '' + debug_abbrev: + - Code: 0x00000001 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_strp + debug_info: + - Length: + TotalLength: 12 + Version: 4 + AbbrOffset: 0 + AddrSize: 8 + Entries: + - AbbrCode: 0x00000001 + Values: + - Value: 0x0000000000001234 + )"; + auto ErrOrSections = DWARFYAML::EmitDebugSections(StringRef(yamldata)); + ASSERT_TRUE((bool)ErrOrSections); + + auto &DebugSections = *ErrOrSections; + + DWARFContextInMemory DwarfContext(DebugSections, 8); + + std::string str; + raw_string_ostream strm(str); + EXPECT_FALSE(DwarfContext.verify(strm, DIDT_All)); + strm.flush(); + const char *err = "error: DW_FORM_strp offset beyond .debug_str bounds:"; + EXPECT_TRUE(strm.str().find(err) != std::string::npos); +} + } // end anonymous namespace -- 2.50.1