1267 lines · cpp
1//===-- sancov.cpp --------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8// This file is a command-line tool for reading and analyzing sanitizer9// coverage.10//===----------------------------------------------------------------------===//11#include "llvm/ADT/STLExtras.h"12#include "llvm/ADT/StringExtras.h"13#include "llvm/ADT/Twine.h"14#include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"15#include "llvm/DebugInfo/Symbolize/Symbolize.h"16#include "llvm/MC/MCAsmInfo.h"17#include "llvm/MC/MCContext.h"18#include "llvm/MC/MCDisassembler/MCDisassembler.h"19#include "llvm/MC/MCInst.h"20#include "llvm/MC/MCInstrAnalysis.h"21#include "llvm/MC/MCInstrInfo.h"22#include "llvm/MC/MCObjectFileInfo.h"23#include "llvm/MC/MCRegisterInfo.h"24#include "llvm/MC/MCSubtargetInfo.h"25#include "llvm/MC/MCTargetOptions.h"26#include "llvm/MC/TargetRegistry.h"27#include "llvm/Object/Archive.h"28#include "llvm/Object/Binary.h"29#include "llvm/Object/COFF.h"30#include "llvm/Object/MachO.h"31#include "llvm/Object/ObjectFile.h"32#include "llvm/Option/ArgList.h"33#include "llvm/Option/Option.h"34#include "llvm/Support/Casting.h"35#include "llvm/Support/CommandLine.h"36#include "llvm/Support/Errc.h"37#include "llvm/Support/ErrorOr.h"38#include "llvm/Support/FileSystem.h"39#include "llvm/Support/JSON.h"40#include "llvm/Support/LLVMDriver.h"41#include "llvm/Support/MD5.h"42#include "llvm/Support/MemoryBuffer.h"43#include "llvm/Support/Path.h"44#include "llvm/Support/Regex.h"45#include "llvm/Support/SHA1.h"46#include "llvm/Support/SourceMgr.h"47#include "llvm/Support/SpecialCaseList.h"48#include "llvm/Support/TargetSelect.h"49#include "llvm/Support/VirtualFileSystem.h"50#include "llvm/Support/YAMLParser.h"51#include "llvm/Support/raw_ostream.h"52 53#include <set>54#include <vector>55 56using namespace llvm;57 58namespace {59 60// Command-line option boilerplate.61namespace {62using namespace llvm::opt;63enum ID {64 OPT_INVALID = 0, // This is not an option ID.65#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),66#include "Opts.inc"67#undef OPTION68};69 70#define OPTTABLE_STR_TABLE_CODE71#include "Opts.inc"72#undef OPTTABLE_STR_TABLE_CODE73 74#define OPTTABLE_PREFIXES_TABLE_CODE75#include "Opts.inc"76#undef OPTTABLE_PREFIXES_TABLE_CODE77 78static constexpr opt::OptTable::Info InfoTable[] = {79#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),80#include "Opts.inc"81#undef OPTION82};83 84class SancovOptTable : public opt::GenericOptTable {85public:86 SancovOptTable()87 : GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {}88};89} // namespace90 91// --------- COMMAND LINE FLAGS ---------92 93enum ActionType {94 CoveredFunctionsAction,95 HtmlReportAction,96 MergeAction,97 NotCoveredFunctionsAction,98 PrintAction,99 PrintCovPointsAction,100 StatsAction,101 SymbolizeAction102};103 104static ActionType Action;105static std::vector<std::string> ClInputFiles;106static bool ClDemangle;107static bool ClSkipDeadFiles;108static bool ClUseDefaultIgnorelist;109static std::string ClStripPathPrefix;110static std::string ClIgnorelist;111 112static const char *const DefaultIgnorelistStr = "fun:__sanitizer_.*\n"113 "src:/usr/include/.*\n"114 "src:.*/libc\\+\\+/.*\n";115 116// --------- FORMAT SPECIFICATION ---------117 118struct FileHeader {119 uint32_t Bitness;120 uint32_t Magic;121};122 123static const uint32_t BinCoverageMagic = 0xC0BFFFFF;124static const uint32_t Bitness32 = 0xFFFFFF32;125static const uint32_t Bitness64 = 0xFFFFFF64;126 127static const Regex SancovFileRegex("(.*)\\.[0-9]+\\.sancov");128static const Regex SymcovFileRegex(".*\\.symcov");129 130// --------- MAIN DATASTRUCTURES ----------131 132// Contents of .sancov file: list of coverage point addresses that were133// executed.134struct RawCoverage {135 explicit RawCoverage(std::unique_ptr<std::set<uint64_t>> Addrs)136 : Addrs(std::move(Addrs)) {}137 138 // Read binary .sancov file.139 static ErrorOr<std::unique_ptr<RawCoverage>>140 read(const std::string &FileName);141 142 std::unique_ptr<std::set<uint64_t>> Addrs;143};144 145// Coverage point has an opaque Id and corresponds to multiple source locations.146struct CoveragePoint {147 explicit CoveragePoint(const std::string &Id) : Id(Id) {}148 149 std::string Id;150 SmallVector<DILineInfo, 1> Locs;151};152 153// Symcov file content: set of covered Ids plus information about all available154// coverage points.155struct SymbolizedCoverage {156 // Read json .symcov file.157 static std::unique_ptr<SymbolizedCoverage> read(const std::string &InputFile);158 159 std::set<std::string> CoveredIds;160 std::string BinaryHash;161 std::vector<CoveragePoint> Points;162};163 164struct CoverageStats {165 size_t AllPoints;166 size_t CovPoints;167 size_t AllFns;168 size_t CovFns;169};170 171// --------- ERROR HANDLING ---------172 173static void fail(const llvm::Twine &E) {174 errs() << "ERROR: " << E << "\n";175 exit(1);176}177 178static void failIf(bool B, const llvm::Twine &E) {179 if (B)180 fail(E);181}182 183static void failIfError(std::error_code Error) {184 if (!Error)185 return;186 errs() << "ERROR: " << Error.message() << "(" << Error.value() << ")\n";187 exit(1);188}189 190template <typename T> static void failIfError(const ErrorOr<T> &E) {191 failIfError(E.getError());192}193 194static void failIfError(Error Err) {195 if (Err) {196 logAllUnhandledErrors(std::move(Err), errs(), "ERROR: ");197 exit(1);198 }199}200 201template <typename T> static void failIfError(Expected<T> &E) {202 failIfError(E.takeError());203}204 205static void failIfNotEmpty(const llvm::Twine &E) {206 if (E.str().empty())207 return;208 fail(E);209}210 211template <typename T>212static void failIfEmpty(const std::unique_ptr<T> &Ptr,213 const std::string &Message) {214 if (Ptr.get())215 return;216 fail(Message);217}218 219// ----------- Coverage I/O ----------220template <typename T>221static void readInts(const char *Start, const char *End,222 std::set<uint64_t> *Ints) {223 const T *S = reinterpret_cast<const T *>(Start);224 const T *E = reinterpret_cast<const T *>(End);225 std::copy(S, E, std::inserter(*Ints, Ints->end()));226}227 228ErrorOr<std::unique_ptr<RawCoverage>>229RawCoverage::read(const std::string &FileName) {230 ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =231 MemoryBuffer::getFile(FileName);232 if (!BufOrErr)233 return BufOrErr.getError();234 std::unique_ptr<MemoryBuffer> Buf = std::move(BufOrErr.get());235 if (Buf->getBufferSize() < 8) {236 errs() << "File too small (<8): " << Buf->getBufferSize() << '\n';237 return make_error_code(errc::illegal_byte_sequence);238 }239 const FileHeader *Header =240 reinterpret_cast<const FileHeader *>(Buf->getBufferStart());241 242 if (Header->Magic != BinCoverageMagic) {243 errs() << "Wrong magic: " << Header->Magic << '\n';244 return make_error_code(errc::illegal_byte_sequence);245 }246 247 auto Addrs = std::make_unique<std::set<uint64_t>>();248 249 switch (Header->Bitness) {250 case Bitness64:251 readInts<uint64_t>(Buf->getBufferStart() + 8, Buf->getBufferEnd(),252 Addrs.get());253 break;254 case Bitness32:255 readInts<uint32_t>(Buf->getBufferStart() + 8, Buf->getBufferEnd(),256 Addrs.get());257 break;258 default:259 errs() << "Unsupported bitness: " << Header->Bitness << '\n';260 return make_error_code(errc::illegal_byte_sequence);261 }262 263 // Ignore slots that are zero, so a runtime implementation is not required264 // to compactify the data.265 Addrs->erase(0);266 267 return std::make_unique<RawCoverage>(std::move(Addrs));268}269 270// Print coverage addresses.271raw_ostream &operator<<(raw_ostream &OS, const RawCoverage &CoverageData) {272 for (auto Addr : *CoverageData.Addrs) {273 OS << "0x";274 OS.write_hex(Addr);275 OS << "\n";276 }277 return OS;278}279 280static raw_ostream &operator<<(raw_ostream &OS, const CoverageStats &Stats) {281 OS << "all-edges: " << Stats.AllPoints << "\n";282 OS << "cov-edges: " << Stats.CovPoints << "\n";283 OS << "all-functions: " << Stats.AllFns << "\n";284 OS << "cov-functions: " << Stats.CovFns << "\n";285 return OS;286}287 288// Output symbolized information for coverage points in JSON.289// Format:290// {291// '<file_name>' : {292// '<function_name>' : {293// '<point_id'> : '<line_number>:'<column_number'.294// ....295// }296// }297// }298static void operator<<(json::OStream &W,299 const std::vector<CoveragePoint> &Points) {300 // Group points by file.301 std::map<std::string, std::vector<const CoveragePoint *>> PointsByFile;302 for (const auto &Point : Points) {303 for (const DILineInfo &Loc : Point.Locs) {304 PointsByFile[Loc.FileName].push_back(&Point);305 }306 }307 308 for (const auto &P : PointsByFile) {309 std::string FileName = P.first;310 std::map<std::string, std::vector<const CoveragePoint *>> PointsByFn;311 for (auto PointPtr : P.second) {312 for (const DILineInfo &Loc : PointPtr->Locs) {313 PointsByFn[Loc.FunctionName].push_back(PointPtr);314 }315 }316 317 W.attributeObject(P.first, [&] {318 // Group points by function.319 for (const auto &P : PointsByFn) {320 std::string FunctionName = P.first;321 std::set<std::string> WrittenIds;322 323 W.attributeObject(FunctionName, [&] {324 for (const CoveragePoint *Point : P.second) {325 for (const auto &Loc : Point->Locs) {326 if (Loc.FileName != FileName || Loc.FunctionName != FunctionName)327 continue;328 if (!WrittenIds.insert(Point->Id).second)329 continue;330 331 // Output <point_id> : "<line>:<col>".332 W.attribute(Point->Id,333 (utostr(Loc.Line) + ":" + utostr(Loc.Column)));334 }335 }336 });337 }338 });339 }340}341 342static void operator<<(json::OStream &W, const SymbolizedCoverage &C) {343 W.object([&] {344 W.attributeArray("covered-points", [&] {345 for (const std::string &P : C.CoveredIds) {346 W.value(P);347 }348 });349 W.attribute("binary-hash", C.BinaryHash);350 W.attributeObject("point-symbol-info", [&] { W << C.Points; });351 });352}353 354static std::string parseScalarString(yaml::Node *N) {355 SmallString<64> StringStorage;356 yaml::ScalarNode *S = dyn_cast<yaml::ScalarNode>(N);357 failIf(!S, "expected string");358 return std::string(S->getValue(StringStorage));359}360 361std::unique_ptr<SymbolizedCoverage>362SymbolizedCoverage::read(const std::string &InputFile) {363 auto Coverage(std::make_unique<SymbolizedCoverage>());364 365 std::map<std::string, CoveragePoint> Points;366 ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =367 MemoryBuffer::getFile(InputFile);368 failIfError(BufOrErr);369 370 SourceMgr SM;371 yaml::Stream S(**BufOrErr, SM);372 373 yaml::document_iterator DI = S.begin();374 failIf(DI == S.end(), "empty document: " + InputFile);375 yaml::Node *Root = DI->getRoot();376 failIf(!Root, "expecting root node: " + InputFile);377 yaml::MappingNode *Top = dyn_cast<yaml::MappingNode>(Root);378 failIf(!Top, "expecting mapping node: " + InputFile);379 380 for (auto &KVNode : *Top) {381 auto Key = parseScalarString(KVNode.getKey());382 383 if (Key == "covered-points") {384 yaml::SequenceNode *Points =385 dyn_cast<yaml::SequenceNode>(KVNode.getValue());386 failIf(!Points, "expected array: " + InputFile);387 388 for (auto I = Points->begin(), E = Points->end(); I != E; ++I) {389 Coverage->CoveredIds.insert(parseScalarString(&*I));390 }391 } else if (Key == "binary-hash") {392 Coverage->BinaryHash = parseScalarString(KVNode.getValue());393 } else if (Key == "point-symbol-info") {394 yaml::MappingNode *PointSymbolInfo =395 dyn_cast<yaml::MappingNode>(KVNode.getValue());396 failIf(!PointSymbolInfo, "expected mapping node: " + InputFile);397 398 for (auto &FileKVNode : *PointSymbolInfo) {399 auto Filename = parseScalarString(FileKVNode.getKey());400 401 yaml::MappingNode *FileInfo =402 dyn_cast<yaml::MappingNode>(FileKVNode.getValue());403 failIf(!FileInfo, "expected mapping node: " + InputFile);404 405 for (auto &FunctionKVNode : *FileInfo) {406 auto FunctionName = parseScalarString(FunctionKVNode.getKey());407 408 yaml::MappingNode *FunctionInfo =409 dyn_cast<yaml::MappingNode>(FunctionKVNode.getValue());410 failIf(!FunctionInfo, "expected mapping node: " + InputFile);411 412 for (auto &PointKVNode : *FunctionInfo) {413 auto PointId = parseScalarString(PointKVNode.getKey());414 auto Loc = parseScalarString(PointKVNode.getValue());415 416 size_t ColonPos = Loc.find(':');417 failIf(ColonPos == std::string::npos, "expected ':': " + InputFile);418 419 auto LineStr = Loc.substr(0, ColonPos);420 auto ColStr = Loc.substr(ColonPos + 1, Loc.size());421 422 DILineInfo LineInfo;423 LineInfo.FileName = Filename;424 LineInfo.FunctionName = FunctionName;425 char *End;426 LineInfo.Line = std::strtoul(LineStr.c_str(), &End, 10);427 LineInfo.Column = std::strtoul(ColStr.c_str(), &End, 10);428 429 CoveragePoint *CoveragePoint =430 &Points.try_emplace(PointId, PointId).first->second;431 CoveragePoint->Locs.push_back(LineInfo);432 }433 }434 }435 } else {436 errs() << "Ignoring unknown key: " << Key << "\n";437 }438 }439 440 for (auto &KV : Points) {441 Coverage->Points.push_back(KV.second);442 }443 444 return Coverage;445}446 447// ---------- MAIN FUNCTIONALITY ----------448 449std::string stripPathPrefix(std::string Path) {450 if (ClStripPathPrefix.empty())451 return Path;452 size_t Pos = Path.find(ClStripPathPrefix);453 if (Pos == std::string::npos)454 return Path;455 return Path.substr(Pos + ClStripPathPrefix.size());456}457 458static std::unique_ptr<symbolize::LLVMSymbolizer> createSymbolizer() {459 symbolize::LLVMSymbolizer::Options SymbolizerOptions;460 SymbolizerOptions.Demangle = ClDemangle;461 SymbolizerOptions.UseSymbolTable = true;462 return std::make_unique<symbolize::LLVMSymbolizer>(SymbolizerOptions);463}464 465static std::string normalizeFilename(const std::string &FileName) {466 SmallString<256> S(FileName);467 sys::path::remove_dots(S, /* remove_dot_dot */ true);468 return stripPathPrefix(sys::path::convert_to_slash(std::string(S)));469}470 471class Ignorelists {472public:473 Ignorelists()474 : DefaultIgnorelist(createDefaultIgnorelist()),475 UserIgnorelist(createUserIgnorelist()) {}476 477 bool isIgnorelisted(const DILineInfo &I) {478 if (DefaultIgnorelist &&479 DefaultIgnorelist->inSection("sancov", "fun", I.FunctionName))480 return true;481 if (DefaultIgnorelist &&482 DefaultIgnorelist->inSection("sancov", "src", I.FileName))483 return true;484 if (UserIgnorelist &&485 UserIgnorelist->inSection("sancov", "fun", I.FunctionName))486 return true;487 if (UserIgnorelist &&488 UserIgnorelist->inSection("sancov", "src", I.FileName))489 return true;490 return false;491 }492 493private:494 static std::unique_ptr<SpecialCaseList> createDefaultIgnorelist() {495 if (!ClUseDefaultIgnorelist)496 return std::unique_ptr<SpecialCaseList>();497 std::unique_ptr<MemoryBuffer> MB =498 MemoryBuffer::getMemBuffer(DefaultIgnorelistStr);499 std::string Error;500 auto Ignorelist = SpecialCaseList::create(MB.get(), Error);501 failIfNotEmpty(Error);502 return Ignorelist;503 }504 505 static std::unique_ptr<SpecialCaseList> createUserIgnorelist() {506 if (ClIgnorelist.empty())507 return std::unique_ptr<SpecialCaseList>();508 return SpecialCaseList::createOrDie({{ClIgnorelist}},509 *vfs::getRealFileSystem());510 }511 std::unique_ptr<SpecialCaseList> DefaultIgnorelist;512 std::unique_ptr<SpecialCaseList> UserIgnorelist;513};514 515static std::vector<CoveragePoint>516getCoveragePoints(const std::string &ObjectFile,517 const std::set<uint64_t> &Addrs,518 const std::set<uint64_t> &CoveredAddrs) {519 std::vector<CoveragePoint> Result;520 auto Symbolizer(createSymbolizer());521 Ignorelists Ig;522 523 std::set<std::string> CoveredFiles;524 if (ClSkipDeadFiles) {525 for (auto Addr : CoveredAddrs) {526 // TODO: it would be neccessary to set proper section index here.527 // object::SectionedAddress::UndefSection works for only absolute528 // addresses.529 object::SectionedAddress ModuleAddress = {530 Addr, object::SectionedAddress::UndefSection};531 532 auto LineInfo = Symbolizer->symbolizeCode(ObjectFile, ModuleAddress);533 failIfError(LineInfo);534 CoveredFiles.insert(LineInfo->FileName);535 auto InliningInfo =536 Symbolizer->symbolizeInlinedCode(ObjectFile, ModuleAddress);537 failIfError(InliningInfo);538 for (uint32_t I = 0; I < InliningInfo->getNumberOfFrames(); ++I) {539 auto FrameInfo = InliningInfo->getFrame(I);540 CoveredFiles.insert(FrameInfo.FileName);541 }542 }543 }544 545 for (auto Addr : Addrs) {546 std::set<DILineInfo> Infos; // deduplicate debug info.547 548 // TODO: it would be neccessary to set proper section index here.549 // object::SectionedAddress::UndefSection works for only absolute addresses.550 object::SectionedAddress ModuleAddress = {551 Addr, object::SectionedAddress::UndefSection};552 553 auto LineInfo = Symbolizer->symbolizeCode(ObjectFile, ModuleAddress);554 failIfError(LineInfo);555 if (ClSkipDeadFiles &&556 CoveredFiles.find(LineInfo->FileName) == CoveredFiles.end())557 continue;558 LineInfo->FileName = normalizeFilename(LineInfo->FileName);559 if (Ig.isIgnorelisted(*LineInfo))560 continue;561 562 auto Id = utohexstr(Addr, true);563 auto Point = CoveragePoint(Id);564 Infos.insert(*LineInfo);565 Point.Locs.push_back(*LineInfo);566 567 auto InliningInfo =568 Symbolizer->symbolizeInlinedCode(ObjectFile, ModuleAddress);569 failIfError(InliningInfo);570 for (uint32_t I = 0; I < InliningInfo->getNumberOfFrames(); ++I) {571 auto FrameInfo = InliningInfo->getFrame(I);572 if (ClSkipDeadFiles &&573 CoveredFiles.find(FrameInfo.FileName) == CoveredFiles.end())574 continue;575 FrameInfo.FileName = normalizeFilename(FrameInfo.FileName);576 if (Ig.isIgnorelisted(FrameInfo))577 continue;578 if (Infos.insert(FrameInfo).second)579 Point.Locs.push_back(FrameInfo);580 }581 582 Result.push_back(Point);583 }584 585 return Result;586}587 588static bool isCoveragePointSymbol(StringRef Name) {589 return Name == "__sanitizer_cov" || Name == "__sanitizer_cov_with_check" ||590 Name == "__sanitizer_cov_trace_func_enter" ||591 Name == "__sanitizer_cov_trace_pc_guard" ||592 // Mac has '___' prefix593 Name == "___sanitizer_cov" || Name == "___sanitizer_cov_with_check" ||594 Name == "___sanitizer_cov_trace_func_enter" ||595 Name == "___sanitizer_cov_trace_pc_guard";596}597 598// Locate __sanitizer_cov* function addresses inside the stubs table on MachO.599static void findMachOIndirectCovFunctions(const object::MachOObjectFile &O,600 std::set<uint64_t> *Result) {601 MachO::dysymtab_command Dysymtab = O.getDysymtabLoadCommand();602 MachO::symtab_command Symtab = O.getSymtabLoadCommand();603 604 for (const auto &Load : O.load_commands()) {605 if (Load.C.cmd == MachO::LC_SEGMENT_64) {606 MachO::segment_command_64 Seg = O.getSegment64LoadCommand(Load);607 for (unsigned J = 0; J < Seg.nsects; ++J) {608 MachO::section_64 Sec = O.getSection64(Load, J);609 610 uint32_t SectionType = Sec.flags & MachO::SECTION_TYPE;611 if (SectionType == MachO::S_SYMBOL_STUBS) {612 uint32_t Stride = Sec.reserved2;613 uint32_t Cnt = Sec.size / Stride;614 uint32_t N = Sec.reserved1;615 for (uint32_t J = 0; J < Cnt && N + J < Dysymtab.nindirectsyms; J++) {616 uint32_t IndirectSymbol =617 O.getIndirectSymbolTableEntry(Dysymtab, N + J);618 uint64_t Addr = Sec.addr + J * Stride;619 if (IndirectSymbol < Symtab.nsyms) {620 object::SymbolRef Symbol = *(O.getSymbolByIndex(IndirectSymbol));621 Expected<StringRef> Name = Symbol.getName();622 failIfError(Name);623 if (isCoveragePointSymbol(Name.get())) {624 Result->insert(Addr);625 }626 }627 }628 }629 }630 }631 if (Load.C.cmd == MachO::LC_SEGMENT) {632 errs() << "ERROR: 32 bit MachO binaries not supported\n";633 }634 }635}636 637// Locate __sanitizer_cov* function addresses that are used for coverage638// reporting.639static std::set<uint64_t>640findSanitizerCovFunctions(const object::ObjectFile &O) {641 std::set<uint64_t> Result;642 643 for (const object::SymbolRef &Symbol : O.symbols()) {644 Expected<uint64_t> AddressOrErr = Symbol.getAddress();645 failIfError(AddressOrErr);646 uint64_t Address = AddressOrErr.get();647 648 Expected<StringRef> NameOrErr = Symbol.getName();649 failIfError(NameOrErr);650 StringRef Name = NameOrErr.get();651 652 Expected<uint32_t> FlagsOrErr = Symbol.getFlags();653 // TODO: Test this error.654 failIfError(FlagsOrErr);655 uint32_t Flags = FlagsOrErr.get();656 657 if (!(Flags & object::BasicSymbolRef::SF_Undefined) &&658 isCoveragePointSymbol(Name)) {659 Result.insert(Address);660 }661 }662 663 if (const auto *CO = dyn_cast<object::COFFObjectFile>(&O)) {664 for (const object::ExportDirectoryEntryRef &Export :665 CO->export_directories()) {666 uint32_t RVA;667 failIfError(Export.getExportRVA(RVA));668 669 StringRef Name;670 failIfError(Export.getSymbolName(Name));671 672 if (isCoveragePointSymbol(Name))673 Result.insert(CO->getImageBase() + RVA);674 }675 }676 677 if (const auto *MO = dyn_cast<object::MachOObjectFile>(&O)) {678 findMachOIndirectCovFunctions(*MO, &Result);679 }680 681 return Result;682}683 684// Ported from685// compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.h:GetPreviousInstructionPc686// GetPreviousInstructionPc.687static uint64_t getPreviousInstructionPc(uint64_t PC, Triple TheTriple) {688 if (TheTriple.isARM())689 return (PC - 3) & (~1);690 if (TheTriple.isMIPS() || TheTriple.isSPARC())691 return PC - 8;692 if (TheTriple.isRISCV())693 return PC - 2;694 if (TheTriple.isX86() || TheTriple.isSystemZ())695 return PC - 1;696 return PC - 4;697}698 699// Locate addresses of all coverage points in a file. Coverage point700// is defined as the 'address of instruction following __sanitizer_cov701// call - 1'.702static void getObjectCoveragePoints(const object::ObjectFile &O,703 std::set<uint64_t> *Addrs) {704 Triple TheTriple("unknown-unknown-unknown");705 TheTriple.setArch(Triple::ArchType(O.getArch()));706 auto TripleName = TheTriple.getTriple();707 708 std::string Error;709 const Target *TheTarget = TargetRegistry::lookupTarget(TheTriple, Error);710 failIfNotEmpty(Error);711 712 std::unique_ptr<const MCSubtargetInfo> STI(713 TheTarget->createMCSubtargetInfo(TheTriple, "", ""));714 failIfEmpty(STI, "no subtarget info for target " + TripleName);715 716 std::unique_ptr<const MCRegisterInfo> MRI(717 TheTarget->createMCRegInfo(TheTriple));718 failIfEmpty(MRI, "no register info for target " + TripleName);719 720 MCTargetOptions MCOptions;721 std::unique_ptr<const MCAsmInfo> AsmInfo(722 TheTarget->createMCAsmInfo(*MRI, TheTriple, MCOptions));723 failIfEmpty(AsmInfo, "no asm info for target " + TripleName);724 725 MCContext Ctx(TheTriple, AsmInfo.get(), MRI.get(), STI.get());726 std::unique_ptr<MCDisassembler> DisAsm(727 TheTarget->createMCDisassembler(*STI, Ctx));728 failIfEmpty(DisAsm, "no disassembler info for target " + TripleName);729 730 std::unique_ptr<const MCInstrInfo> MII(TheTarget->createMCInstrInfo());731 failIfEmpty(MII, "no instruction info for target " + TripleName);732 733 std::unique_ptr<MCInstrAnalysis> MIA(734 TheTarget->createMCInstrAnalysis(MII.get()));735 failIfEmpty(MIA, "no instruction analysis info for target " + TripleName);736 737 auto SanCovAddrs = findSanitizerCovFunctions(O);738 if (SanCovAddrs.empty())739 fail("__sanitizer_cov* functions not found");740 741 for (object::SectionRef Section : O.sections()) {742 if (Section.isVirtual() || !Section.isText()) // llvm-objdump does the same.743 continue;744 uint64_t SectionAddr = Section.getAddress();745 uint64_t SectSize = Section.getSize();746 if (!SectSize)747 continue;748 749 Expected<StringRef> BytesStr = Section.getContents();750 failIfError(BytesStr);751 ArrayRef<uint8_t> Bytes = arrayRefFromStringRef(*BytesStr);752 753 if (MIA)754 MIA->resetState();755 756 for (uint64_t Index = 0, Size = 0; Index < Section.getSize();757 Index += Size) {758 MCInst Inst;759 ArrayRef<uint8_t> ThisBytes = Bytes.slice(Index);760 uint64_t ThisAddr = SectionAddr + Index;761 if (!DisAsm->getInstruction(Inst, Size, ThisBytes, ThisAddr, nulls())) {762 if (Size == 0)763 Size = std::min<uint64_t>(764 ThisBytes.size(),765 DisAsm->suggestBytesToSkip(ThisBytes, ThisAddr));766 MIA->resetState();767 continue;768 }769 uint64_t Addr = Index + SectionAddr;770 // Sanitizer coverage uses the address of the next instruction - 1.771 uint64_t CovPoint = getPreviousInstructionPc(Addr + Size, TheTriple);772 uint64_t Target;773 if (MIA->isCall(Inst) &&774 MIA->evaluateBranch(Inst, SectionAddr + Index, Size, Target) &&775 SanCovAddrs.find(Target) != SanCovAddrs.end())776 Addrs->insert(CovPoint);777 MIA->updateState(Inst, Addr);778 }779 }780}781 782static void783visitObjectFiles(const object::Archive &A,784 function_ref<void(const object::ObjectFile &)> Fn) {785 Error Err = Error::success();786 for (auto &C : A.children(Err)) {787 Expected<std::unique_ptr<object::Binary>> ChildOrErr = C.getAsBinary();788 failIfError(ChildOrErr);789 if (auto *O = dyn_cast<object::ObjectFile>(&*ChildOrErr.get()))790 Fn(*O);791 else792 failIfError(object::object_error::invalid_file_type);793 }794 failIfError(std::move(Err));795}796 797static void798visitObjectFiles(const std::string &FileName,799 function_ref<void(const object::ObjectFile &)> Fn) {800 Expected<object::OwningBinary<object::Binary>> BinaryOrErr =801 object::createBinary(FileName);802 if (!BinaryOrErr)803 failIfError(BinaryOrErr);804 805 object::Binary &Binary = *BinaryOrErr.get().getBinary();806 if (object::Archive *A = dyn_cast<object::Archive>(&Binary))807 visitObjectFiles(*A, Fn);808 else if (object::ObjectFile *O = dyn_cast<object::ObjectFile>(&Binary))809 Fn(*O);810 else811 failIfError(object::object_error::invalid_file_type);812}813 814static std::set<uint64_t>815findSanitizerCovFunctions(const std::string &FileName) {816 std::set<uint64_t> Result;817 visitObjectFiles(FileName, [&](const object::ObjectFile &O) {818 auto Addrs = findSanitizerCovFunctions(O);819 Result.insert(Addrs.begin(), Addrs.end());820 });821 return Result;822}823 824// Locate addresses of all coverage points in a file. Coverage point825// is defined as the 'address of instruction following __sanitizer_cov826// call - 1'.827static std::set<uint64_t> findCoveragePointAddrs(const std::string &FileName) {828 std::set<uint64_t> Result;829 visitObjectFiles(FileName, [&](const object::ObjectFile &O) {830 getObjectCoveragePoints(O, &Result);831 });832 return Result;833}834 835static void printCovPoints(const std::string &ObjFile, raw_ostream &OS) {836 for (uint64_t Addr : findCoveragePointAddrs(ObjFile)) {837 OS << "0x";838 OS.write_hex(Addr);839 OS << "\n";840 }841}842 843static ErrorOr<bool> isCoverageFile(const std::string &FileName) {844 auto ShortFileName = llvm::sys::path::filename(FileName);845 if (!SancovFileRegex.match(ShortFileName))846 return false;847 848 ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =849 MemoryBuffer::getFile(FileName);850 if (!BufOrErr) {851 errs() << "Warning: " << BufOrErr.getError().message() << "("852 << BufOrErr.getError().value()853 << "), filename: " << llvm::sys::path::filename(FileName) << "\n";854 return BufOrErr.getError();855 }856 std::unique_ptr<MemoryBuffer> Buf = std::move(BufOrErr.get());857 if (Buf->getBufferSize() < 8) {858 return false;859 }860 const FileHeader *Header =861 reinterpret_cast<const FileHeader *>(Buf->getBufferStart());862 return Header->Magic == BinCoverageMagic;863}864 865static bool isSymbolizedCoverageFile(const std::string &FileName) {866 auto ShortFileName = llvm::sys::path::filename(FileName);867 return SymcovFileRegex.match(ShortFileName);868}869 870static std::unique_ptr<SymbolizedCoverage>871symbolize(const RawCoverage &Data, const std::string ObjectFile) {872 auto Coverage = std::make_unique<SymbolizedCoverage>();873 874 ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =875 MemoryBuffer::getFile(ObjectFile);876 failIfError(BufOrErr);877 SHA1 Hasher;878 Hasher.update((*BufOrErr)->getBuffer());879 Coverage->BinaryHash = toHex(Hasher.final());880 881 Ignorelists Ig;882 auto Symbolizer(createSymbolizer());883 884 for (uint64_t Addr : *Data.Addrs) {885 // TODO: it would be neccessary to set proper section index here.886 // object::SectionedAddress::UndefSection works for only absolute addresses.887 auto LineInfo = Symbolizer->symbolizeCode(888 ObjectFile, {Addr, object::SectionedAddress::UndefSection});889 failIfError(LineInfo);890 if (Ig.isIgnorelisted(*LineInfo))891 continue;892 893 Coverage->CoveredIds.insert(utohexstr(Addr, true));894 }895 896 std::set<uint64_t> AllAddrs = findCoveragePointAddrs(ObjectFile);897 if (!llvm::includes(AllAddrs, *Data.Addrs)) {898 fail("Coverage points in binary and .sancov file do not match.");899 }900 Coverage->Points = getCoveragePoints(ObjectFile, AllAddrs, *Data.Addrs);901 return Coverage;902}903 904struct FileFn {905 bool operator<(const FileFn &RHS) const {906 return std::tie(FileName, FunctionName) <907 std::tie(RHS.FileName, RHS.FunctionName);908 }909 910 std::string FileName;911 std::string FunctionName;912};913 914static std::set<FileFn>915computeFunctions(const std::vector<CoveragePoint> &Points) {916 std::set<FileFn> Fns;917 for (const auto &Point : Points) {918 for (const auto &Loc : Point.Locs) {919 Fns.insert(FileFn{Loc.FileName, Loc.FunctionName});920 }921 }922 return Fns;923}924 925static std::set<FileFn>926computeNotCoveredFunctions(const SymbolizedCoverage &Coverage) {927 auto Fns = computeFunctions(Coverage.Points);928 929 for (const auto &Point : Coverage.Points) {930 if (Coverage.CoveredIds.find(Point.Id) == Coverage.CoveredIds.end())931 continue;932 933 for (const auto &Loc : Point.Locs) {934 Fns.erase(FileFn{Loc.FileName, Loc.FunctionName});935 }936 }937 938 return Fns;939}940 941static std::set<FileFn>942computeCoveredFunctions(const SymbolizedCoverage &Coverage) {943 auto AllFns = computeFunctions(Coverage.Points);944 std::set<FileFn> Result;945 946 for (const auto &Point : Coverage.Points) {947 if (Coverage.CoveredIds.find(Point.Id) == Coverage.CoveredIds.end())948 continue;949 950 for (const auto &Loc : Point.Locs) {951 Result.insert(FileFn{Loc.FileName, Loc.FunctionName});952 }953 }954 955 return Result;956}957 958typedef std::map<FileFn, std::pair<uint32_t, uint32_t>> FunctionLocs;959// finds first location in a file for each function.960static FunctionLocs resolveFunctions(const SymbolizedCoverage &Coverage,961 const std::set<FileFn> &Fns) {962 FunctionLocs Result;963 for (const auto &Point : Coverage.Points) {964 for (const auto &Loc : Point.Locs) {965 FileFn Fn = FileFn{Loc.FileName, Loc.FunctionName};966 if (Fns.find(Fn) == Fns.end())967 continue;968 969 auto P = std::make_pair(Loc.Line, Loc.Column);970 auto [It, Inserted] = Result.try_emplace(Fn, P);971 if (!Inserted && It->second > P)972 It->second = P;973 }974 }975 return Result;976}977 978static void printFunctionLocs(const FunctionLocs &FnLocs, raw_ostream &OS) {979 for (const auto &P : FnLocs) {980 OS << stripPathPrefix(P.first.FileName) << ":" << P.second.first << " "981 << P.first.FunctionName << "\n";982 }983}984CoverageStats computeStats(const SymbolizedCoverage &Coverage) {985 CoverageStats Stats = {Coverage.Points.size(), Coverage.CoveredIds.size(),986 computeFunctions(Coverage.Points).size(),987 computeCoveredFunctions(Coverage).size()};988 return Stats;989}990 991// Print list of covered functions.992// Line format: <file_name>:<line> <function_name>993static void printCoveredFunctions(const SymbolizedCoverage &CovData,994 raw_ostream &OS) {995 auto CoveredFns = computeCoveredFunctions(CovData);996 printFunctionLocs(resolveFunctions(CovData, CoveredFns), OS);997}998 999// Print list of not covered functions.1000// Line format: <file_name>:<line> <function_name>1001static void printNotCoveredFunctions(const SymbolizedCoverage &CovData,1002 raw_ostream &OS) {1003 auto NotCoveredFns = computeNotCoveredFunctions(CovData);1004 printFunctionLocs(resolveFunctions(CovData, NotCoveredFns), OS);1005}1006 1007// Read list of files and merges their coverage info.1008static void readAndPrintRawCoverage(const std::vector<std::string> &FileNames,1009 raw_ostream &OS) {1010 for (const auto &FileName : FileNames) {1011 auto Cov = RawCoverage::read(FileName);1012 if (!Cov)1013 continue;1014 OS << *Cov.get();1015 }1016}1017 1018static std::unique_ptr<SymbolizedCoverage>1019merge(const std::vector<std::unique_ptr<SymbolizedCoverage>> &Coverages) {1020 if (Coverages.empty())1021 return nullptr;1022 1023 auto Result = std::make_unique<SymbolizedCoverage>();1024 1025 for (size_t I = 0; I < Coverages.size(); ++I) {1026 const SymbolizedCoverage &Coverage = *Coverages[I];1027 std::string Prefix;1028 if (Coverages.size() > 1) {1029 // prefix is not needed when there's only one file.1030 Prefix = utostr(I);1031 }1032 1033 for (const auto &Id : Coverage.CoveredIds) {1034 Result->CoveredIds.insert(Prefix + Id);1035 }1036 1037 for (const auto &CovPoint : Coverage.Points) {1038 CoveragePoint NewPoint(CovPoint);1039 NewPoint.Id = Prefix + CovPoint.Id;1040 Result->Points.push_back(NewPoint);1041 }1042 }1043 1044 if (Coverages.size() == 1) {1045 Result->BinaryHash = Coverages[0]->BinaryHash;1046 }1047 1048 return Result;1049}1050 1051static std::unique_ptr<SymbolizedCoverage>1052readSymbolizeAndMergeCmdArguments(std::vector<std::string> FileNames) {1053 std::vector<std::unique_ptr<SymbolizedCoverage>> Coverages;1054 1055 {1056 // Short name => file name.1057 std::map<std::string, std::string, std::less<>> ObjFiles;1058 std::string FirstObjFile;1059 std::set<std::string> CovFiles;1060 1061 // Partition input values into coverage/object files.1062 for (const auto &FileName : FileNames) {1063 if (isSymbolizedCoverageFile(FileName)) {1064 Coverages.push_back(SymbolizedCoverage::read(FileName));1065 }1066 1067 auto ErrorOrIsCoverage = isCoverageFile(FileName);1068 if (!ErrorOrIsCoverage)1069 continue;1070 if (ErrorOrIsCoverage.get()) {1071 CovFiles.insert(FileName);1072 } else {1073 auto ShortFileName = llvm::sys::path::filename(FileName);1074 if (ObjFiles.find(ShortFileName) != ObjFiles.end()) {1075 fail("Duplicate binary file with a short name: " + ShortFileName);1076 }1077 1078 ObjFiles[std::string(ShortFileName)] = FileName;1079 if (FirstObjFile.empty())1080 FirstObjFile = FileName;1081 }1082 }1083 1084 SmallVector<StringRef, 2> Components;1085 1086 // Object file => list of corresponding coverage file names.1087 std::map<std::string, std::vector<std::string>> CoverageByObjFile;1088 for (const auto &FileName : CovFiles) {1089 auto ShortFileName = llvm::sys::path::filename(FileName);1090 auto Ok = SancovFileRegex.match(ShortFileName, &Components);1091 if (!Ok) {1092 fail("Can't match coverage file name against "1093 "<module_name>.<pid>.sancov pattern: " +1094 FileName);1095 }1096 1097 auto Iter = ObjFiles.find(Components[1]);1098 if (Iter == ObjFiles.end()) {1099 fail("Object file for coverage not found: " + FileName);1100 }1101 1102 CoverageByObjFile[Iter->second].push_back(FileName);1103 };1104 1105 for (const auto &Pair : ObjFiles) {1106 auto FileName = Pair.second;1107 if (CoverageByObjFile.find(FileName) == CoverageByObjFile.end())1108 errs() << "WARNING: No coverage file for " << FileName << "\n";1109 }1110 1111 // Read raw coverage and symbolize it.1112 for (const auto &Pair : CoverageByObjFile) {1113 if (findSanitizerCovFunctions(Pair.first).empty()) {1114 errs()1115 << "WARNING: Ignoring " << Pair.first1116 << " and its coverage because __sanitizer_cov* functions were not "1117 "found.\n";1118 continue;1119 }1120 1121 for (const std::string &CoverageFile : Pair.second) {1122 auto DataOrError = RawCoverage::read(CoverageFile);1123 failIfError(DataOrError);1124 Coverages.push_back(symbolize(*DataOrError.get(), Pair.first));1125 }1126 }1127 }1128 1129 return merge(Coverages);1130}1131 1132} // namespace1133 1134static void parseArgs(int Argc, char **Argv) {1135 SancovOptTable Tbl;1136 llvm::BumpPtrAllocator A;1137 llvm::StringSaver Saver{A};1138 opt::InputArgList Args =1139 Tbl.parseArgs(Argc, Argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) {1140 llvm::errs() << Msg << '\n';1141 std::exit(1);1142 });1143 1144 if (Args.hasArg(OPT_help)) {1145 Tbl.printHelp(1146 llvm::outs(),1147 "sancov [options] <action> <binary files...> <.sancov files...> "1148 "<.symcov files...>",1149 "Sanitizer Coverage Processing Tool (sancov)\n\n"1150 " This tool can extract various coverage-related information from: \n"1151 " coverage-instrumented binary files, raw .sancov files and their "1152 "symbolized .symcov version.\n"1153 " Depending on chosen action the tool expects different input files:\n"1154 " -print-coverage-pcs - coverage-instrumented binary files\n"1155 " -print-coverage - .sancov files\n"1156 " <other actions> - .sancov files & corresponding binary "1157 "files, .symcov files\n");1158 std::exit(0);1159 }1160 1161 if (Args.hasArg(OPT_version)) {1162 cl::PrintVersionMessage();1163 std::exit(0);1164 }1165 1166 if (Args.hasMultipleArgs(OPT_action_grp)) {1167 fail("Only one action option is allowed");1168 }1169 1170 for (const opt::Arg *A : Args.filtered(OPT_INPUT)) {1171 ClInputFiles.emplace_back(A->getValue());1172 }1173 1174 if (const llvm::opt::Arg *A = Args.getLastArg(OPT_action_grp)) {1175 switch (A->getOption().getID()) {1176 case OPT_print:1177 Action = ActionType::PrintAction;1178 break;1179 case OPT_printCoveragePcs:1180 Action = ActionType::PrintCovPointsAction;1181 break;1182 case OPT_coveredFunctions:1183 Action = ActionType::CoveredFunctionsAction;1184 break;1185 case OPT_notCoveredFunctions:1186 Action = ActionType::NotCoveredFunctionsAction;1187 break;1188 case OPT_printCoverageStats:1189 Action = ActionType::StatsAction;1190 break;1191 case OPT_htmlReport:1192 Action = ActionType::HtmlReportAction;1193 break;1194 case OPT_symbolize:1195 Action = ActionType::SymbolizeAction;1196 break;1197 case OPT_merge:1198 Action = ActionType::MergeAction;1199 break;1200 default:1201 fail("Invalid Action");1202 }1203 }1204 1205 ClDemangle = Args.hasFlag(OPT_demangle, OPT_no_demangle, true);1206 ClSkipDeadFiles = Args.hasFlag(OPT_skipDeadFiles, OPT_no_skipDeadFiles, true);1207 ClUseDefaultIgnorelist =1208 Args.hasFlag(OPT_useDefaultIgnoreList, OPT_no_useDefaultIgnoreList, true);1209 1210 ClStripPathPrefix = Args.getLastArgValue(OPT_stripPathPrefix_EQ);1211 ClIgnorelist = Args.getLastArgValue(OPT_ignorelist_EQ);1212}1213 1214int sancov_main(int Argc, char **Argv, const llvm::ToolContext &) {1215 llvm::InitializeAllTargetInfos();1216 llvm::InitializeAllTargetMCs();1217 llvm::InitializeAllDisassemblers();1218 1219 parseArgs(Argc, Argv);1220 1221 // -print doesn't need object files.1222 if (Action == PrintAction) {1223 readAndPrintRawCoverage(ClInputFiles, outs());1224 return 0;1225 }1226 if (Action == PrintCovPointsAction) {1227 // -print-coverage-points doesn't need coverage files.1228 for (const std::string &ObjFile : ClInputFiles) {1229 printCovPoints(ObjFile, outs());1230 }1231 return 0;1232 }1233 1234 auto Coverage = readSymbolizeAndMergeCmdArguments(ClInputFiles);1235 failIf(!Coverage, "No valid coverage files given.");1236 1237 switch (Action) {1238 case CoveredFunctionsAction: {1239 printCoveredFunctions(*Coverage, outs());1240 return 0;1241 }1242 case NotCoveredFunctionsAction: {1243 printNotCoveredFunctions(*Coverage, outs());1244 return 0;1245 }1246 case StatsAction: {1247 outs() << computeStats(*Coverage);1248 return 0;1249 }1250 case MergeAction:1251 case SymbolizeAction: { // merge & symbolize are synonims.1252 json::OStream W(outs(), 2);1253 W << *Coverage;1254 return 0;1255 }1256 case HtmlReportAction:1257 errs() << "-html-report option is removed: "1258 "use -symbolize & coverage-report-server.py instead\n";1259 return 1;1260 case PrintAction:1261 case PrintCovPointsAction:1262 llvm_unreachable("unsupported action");1263 }1264 1265 return 0;1266}1267