//===-- sancov.cc --------------------------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file is a command-line tool for reading and analyzing sanitizer // coverage. //===----------------------------------------------------------------------===// #include "llvm/ADT/STLExtras.h" #include "llvm/DebugInfo/Symbolize/Symbolize.h" #include "llvm/MC/MCAsmInfo.h" #include "llvm/MC/MCContext.h" #include "llvm/MC/MCDisassembler.h" #include "llvm/MC/MCInst.h" #include "llvm/MC/MCInstPrinter.h" #include "llvm/MC/MCInstrAnalysis.h" #include "llvm/MC/MCInstrInfo.h" #include "llvm/MC/MCObjectFileInfo.h" #include "llvm/MC/MCRegisterInfo.h" #include "llvm/MC/MCSubtargetInfo.h" #include "llvm/Object/Archive.h" #include "llvm/Object/Binary.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Errc.h" #include "llvm/Support/ErrorOr.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/LineIterator.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/PrettyStackTrace.h" #include "llvm/Support/Signals.h" #include "llvm/Support/SpecialCaseList.h" #include "llvm/Support/TargetRegistry.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/ToolOutputFile.h" #include "llvm/Support/raw_ostream.h" #include #include #include #include using namespace llvm; namespace { // --------- COMMAND LINE FLAGS --------- enum ActionType { PrintAction, CoveredFunctionsAction, NotCoveredFunctionsAction }; cl::opt Action( cl::desc("Action (required)"), cl::Required, cl::values(clEnumValN(PrintAction, "print", "Print coverage addresses"), clEnumValN(CoveredFunctionsAction, "covered-functions", "Print all covered funcions."), clEnumValN(NotCoveredFunctionsAction, "not-covered-functions", "Print all not covered funcions."), clEnumValEnd)); static cl::list ClInputFiles(cl::Positional, cl::OneOrMore, cl::desc("")); static cl::opt ClBinaryName("obj", cl::Required, cl::desc("Path to object file to be symbolized")); static cl::opt ClDemangle("demangle", cl::init(true), cl::desc("Print demangled function name.")); static cl::opt ClStripPathPrefix( "strip_path_prefix", cl::init(""), cl::desc("Strip this prefix from file paths in reports.")); static cl::opt ClBlacklist("blacklist", cl::init(""), cl::desc("Blacklist file (sanitizer blacklist format).")); static cl::opt ClUseDefaultBlacklist( "use_default_blacklist", cl::init(true), cl::Hidden, cl::desc("Controls if default blacklist should be used.")); static const char *const DefaultBlacklist = "fun:__sanitizer_*"; // --------- FORMAT SPECIFICATION --------- struct FileHeader { uint32_t Bitness; uint32_t Magic; }; static const uint32_t BinCoverageMagic = 0xC0BFFFFF; static const uint32_t Bitness32 = 0xFFFFFF32; static const uint32_t Bitness64 = 0xFFFFFF64; // --------- static void FailIfError(std::error_code Error) { if (!Error) return; errs() << "Error: " << Error.message() << "(" << Error.value() << ")\n"; exit(1); } template static void FailIfError(const ErrorOr &E) { FailIfError(E.getError()); } static void FailIfNotEmpty(const std::string &E) { if (E.empty()) return; errs() << "Error: " << E << "\n"; exit(1); } template static void FailIfEmpty(const std::unique_ptr &Ptr, const std::string &Message) { if (Ptr.get()) return; errs() << "Error: " << Message << "\n"; exit(1); } template static void readInts(const char *Start, const char *End, std::set *Ints) { const T *S = reinterpret_cast(Start); const T *E = reinterpret_cast(End); std::copy(S, E, std::inserter(*Ints, Ints->end())); } struct FileLoc { bool operator<(const FileLoc &RHS) const { return std::tie(FileName, Line) < std::tie(RHS.FileName, RHS.Line); } std::string FileName; uint32_t Line; }; struct FunctionLoc { bool operator<(const FunctionLoc &RHS) const { return std::tie(Loc, FunctionName) < std::tie(RHS.Loc, RHS.FunctionName); } FileLoc Loc; std::string FunctionName; }; std::string stripPathPrefix(std::string Path) { if (ClStripPathPrefix.empty()) return Path; size_t Pos = Path.find(ClStripPathPrefix); if (Pos == std::string::npos) return Path; return Path.substr(Pos + ClStripPathPrefix.size()); } // Compute [FileLoc -> FunctionName] map for given addresses. static std::map computeFunctionsMap(const std::set &Addrs) { std::map Fns; symbolize::LLVMSymbolizer::Options SymbolizerOptions; SymbolizerOptions.Demangle = ClDemangle; SymbolizerOptions.UseSymbolTable = true; symbolize::LLVMSymbolizer Symbolizer(SymbolizerOptions); // Fill in Fns map. for (auto Addr : Addrs) { auto InliningInfo = Symbolizer.symbolizeInlinedCode(ClBinaryName, Addr); FailIfError(InliningInfo); for (uint32_t I = 0; I < InliningInfo->getNumberOfFrames(); ++I) { auto FrameInfo = InliningInfo->getFrame(I); SmallString<256> FileName(FrameInfo.FileName); sys::path::remove_dots(FileName, /* remove_dot_dot */ true); FileLoc Loc = {FileName.str(), FrameInfo.Line}; Fns[Loc] = FrameInfo.FunctionName; } } return Fns; } // Compute functions for given addresses. It keeps only the first // occurence of a function within a file. std::set computeFunctionLocs(const std::set &Addrs) { std::map Fns = computeFunctionsMap(Addrs); std::set Result; std::string LastFileName; std::set ProcessedFunctions; for (const auto &P : Fns) { std::string FileName = P.first.FileName; std::string FunctionName = P.second; if (LastFileName != FileName) ProcessedFunctions.clear(); LastFileName = FileName; if (!ProcessedFunctions.insert(FunctionName).second) continue; Result.insert(FunctionLoc{P.first, P.second}); } return Result; } // Locate __sanitizer_cov* function addresses that are used for coverage // reporting. static std::set findSanitizerCovFunctions(const object::ObjectFile &O) { std::set Result; for (const object::SymbolRef &Symbol : O.symbols()) { ErrorOr AddressOrErr = Symbol.getAddress(); FailIfError(AddressOrErr); ErrorOr NameOrErr = Symbol.getName(); FailIfError(NameOrErr); StringRef Name = NameOrErr.get(); if (Name == "__sanitizer_cov" || Name == "__sanitizer_cov_with_check" || Name == "__sanitizer_cov_trace_func_enter") { Result.insert(AddressOrErr.get()); } } if (Result.empty()) FailIfNotEmpty("__sanitizer_cov* functions not found"); return Result; } // Locate addresses of all coverage points in a file. Coverage point // is defined as the 'address of instruction following __sanitizer_cov // call - 1'. static void getObjectCoveragePoints(const object::ObjectFile &O, std::set *Addrs) { Triple TheTriple("unknown-unknown-unknown"); TheTriple.setArch(Triple::ArchType(O.getArch())); auto TripleName = TheTriple.getTriple(); std::string Error; const Target *TheTarget = TargetRegistry::lookupTarget(TripleName, Error); FailIfNotEmpty(Error); std::unique_ptr STI( TheTarget->createMCSubtargetInfo(TripleName, "", "")); FailIfEmpty(STI, "no subtarget info for target " + TripleName); std::unique_ptr MRI( TheTarget->createMCRegInfo(TripleName)); FailIfEmpty(MRI, "no register info for target " + TripleName); std::unique_ptr AsmInfo( TheTarget->createMCAsmInfo(*MRI, TripleName)); FailIfEmpty(AsmInfo, "no asm info for target " + TripleName); std::unique_ptr MOFI(new MCObjectFileInfo); MCContext Ctx(AsmInfo.get(), MRI.get(), MOFI.get()); std::unique_ptr DisAsm( TheTarget->createMCDisassembler(*STI, Ctx)); FailIfEmpty(DisAsm, "no disassembler info for target " + TripleName); std::unique_ptr MII(TheTarget->createMCInstrInfo()); FailIfEmpty(MII, "no instruction info for target " + TripleName); std::unique_ptr MIA( TheTarget->createMCInstrAnalysis(MII.get())); FailIfEmpty(MIA, "no instruction analysis info for target " + TripleName); auto SanCovAddrs = findSanitizerCovFunctions(O); for (const auto Section : O.sections()) { if (Section.isVirtual() || !Section.isText()) // llvm-objdump does the same. continue; uint64_t SectionAddr = Section.getAddress(); uint64_t SectSize = Section.getSize(); if (!SectSize) continue; StringRef SectionName; FailIfError(Section.getName(SectionName)); StringRef BytesStr; FailIfError(Section.getContents(BytesStr)); ArrayRef Bytes(reinterpret_cast(BytesStr.data()), BytesStr.size()); for (uint64_t Index = 0, Size = 0; Index < Section.getSize(); Index += Size) { MCInst Inst; if (!DisAsm->getInstruction(Inst, Size, Bytes.slice(Index), SectionAddr + Index, nulls(), nulls())) { if (Size == 0) Size = 1; continue; } uint64_t Target; if (MIA->isCall(Inst) && MIA->evaluateBranch(Inst, SectionAddr + Index, Size, Target)) { if (SanCovAddrs.find(Target) != SanCovAddrs.end()) { // Sanitizer coverage uses the address of the next instruction - 1. Addrs->insert(Index + SectionAddr + Size - 1); } } } } } static void getArchiveCoveragePoints(const object::Archive &A, std::set *Addrs) { for (auto &ErrorOrChild : A.children()) { FailIfError(ErrorOrChild); const object::Archive::Child &C = *ErrorOrChild; ErrorOr> ChildOrErr = C.getAsBinary(); FailIfError(ChildOrErr); if (object::ObjectFile *O = dyn_cast(&*ChildOrErr.get())) getObjectCoveragePoints(*O, Addrs); else FailIfError(object::object_error::invalid_file_type); } } // Locate addresses of all coverage points in a file. Coverage point // is defined as the 'address of instruction following __sanitizer_cov // call - 1'. std::set getCoveragePoints(std::string FileName) { std::set Result; ErrorOr> BinaryOrErr = object::createBinary(FileName); FailIfError(BinaryOrErr); object::Binary &Binary = *BinaryOrErr.get().getBinary(); if (object::Archive *A = dyn_cast(&Binary)) getArchiveCoveragePoints(*A, &Result); else if (object::ObjectFile *O = dyn_cast(&Binary)) getObjectCoveragePoints(*O, &Result); else FailIfError(object::object_error::invalid_file_type); return Result; } static std::unique_ptr createDefaultBlacklist() { if (!ClUseDefaultBlacklist) return std::unique_ptr(); std::unique_ptr MB = MemoryBuffer::getMemBuffer(DefaultBlacklist); std::string Error; auto Blacklist = SpecialCaseList::create(MB.get(), Error); FailIfNotEmpty(Error); return Blacklist; } static std::unique_ptr createUserBlacklist() { if (ClBlacklist.empty()) return std::unique_ptr(); return SpecialCaseList::createOrDie({{ClBlacklist}}); } static void printFunctionLocs(const std::set &FnLocs, raw_ostream &OS) { std::unique_ptr DefaultBlacklist = createDefaultBlacklist(); std::unique_ptr UserBlacklist = createUserBlacklist(); for (const FunctionLoc &FnLoc : FnLocs) { if (DefaultBlacklist && DefaultBlacklist->inSection("fun", FnLoc.FunctionName)) continue; if (DefaultBlacklist && DefaultBlacklist->inSection("src", FnLoc.Loc.FileName)) continue; if (UserBlacklist && UserBlacklist->inSection("fun", FnLoc.FunctionName)) continue; if (UserBlacklist && UserBlacklist->inSection("src", FnLoc.Loc.FileName)) continue; OS << stripPathPrefix(FnLoc.Loc.FileName) << ":" << FnLoc.Loc.Line << " " << FnLoc.FunctionName << "\n"; } } class CoverageData { public: // Read single file coverage data. static ErrorOr> read(std::string FileName) { ErrorOr> BufOrErr = MemoryBuffer::getFile(FileName); if (!BufOrErr) return BufOrErr.getError(); std::unique_ptr Buf = std::move(BufOrErr.get()); if (Buf->getBufferSize() < 8) { errs() << "File too small (<8): " << Buf->getBufferSize(); return make_error_code(errc::illegal_byte_sequence); } const FileHeader *Header = reinterpret_cast(Buf->getBufferStart()); if (Header->Magic != BinCoverageMagic) { errs() << "Wrong magic: " << Header->Magic; return make_error_code(errc::illegal_byte_sequence); } auto Addrs = llvm::make_unique>(); switch (Header->Bitness) { case Bitness64: readInts(Buf->getBufferStart() + 8, Buf->getBufferEnd(), Addrs.get()); break; case Bitness32: readInts(Buf->getBufferStart() + 8, Buf->getBufferEnd(), Addrs.get()); break; default: errs() << "Unsupported bitness: " << Header->Bitness; return make_error_code(errc::illegal_byte_sequence); } return std::unique_ptr(new CoverageData(std::move(Addrs))); } // Merge multiple coverage data together. static std::unique_ptr merge(const std::vector> &Covs) { auto Addrs = llvm::make_unique>(); for (const auto &Cov : Covs) Addrs->insert(Cov->Addrs->begin(), Cov->Addrs->end()); return std::unique_ptr(new CoverageData(std::move(Addrs))); } // Read list of files and merges their coverage info. static ErrorOr> readAndMerge(const std::vector &FileNames) { std::vector> Covs; for (const auto &FileName : FileNames) { auto Cov = read(FileName); if (!Cov) return Cov.getError(); Covs.push_back(std::move(Cov.get())); } return merge(Covs); } // Print coverage addresses. void printAddrs(raw_ostream &OS) { for (auto Addr : *Addrs) { OS << "0x"; OS.write_hex(Addr); OS << "\n"; } } // Print list of covered functions. // Line format: : void printCoveredFunctions(raw_ostream &OS) { printFunctionLocs(computeFunctionLocs(*Addrs), OS); } // Print list of not covered functions. // Line format: : void printNotCoveredFunctions(raw_ostream &OS) { std::set AllFns = computeFunctionLocs(getCoveragePoints(ClBinaryName)); std::set CoveredFns = computeFunctionLocs(*Addrs); std::set NotCoveredFns; std::set_difference(AllFns.begin(), AllFns.end(), CoveredFns.begin(), CoveredFns.end(), std::inserter(NotCoveredFns, NotCoveredFns.end())); printFunctionLocs(NotCoveredFns, OS); } private: explicit CoverageData(std::unique_ptr> Addrs) : Addrs(std::move(Addrs)) {} std::unique_ptr> Addrs; }; } // namespace int main(int argc, char **argv) { // Print stack trace if we signal out. sys::PrintStackTraceOnErrorSignal(); PrettyStackTraceProgram X(argc, argv); llvm_shutdown_obj Y; // Call llvm_shutdown() on exit. llvm::InitializeAllTargetInfos(); llvm::InitializeAllTargetMCs(); llvm::InitializeAllDisassemblers(); cl::ParseCommandLineOptions(argc, argv, "Sanitizer Coverage Processing Tool"); auto CovData = CoverageData::readAndMerge(ClInputFiles); FailIfError(CovData); switch (Action) { case PrintAction: { CovData.get()->printAddrs(outs()); return 0; } case CoveredFunctionsAction: { CovData.get()->printCoveredFunctions(outs()); return 0; } case NotCoveredFunctionsAction: { CovData.get()->printNotCoveredFunctions(outs()); return 0; } } llvm_unreachable("unsupported action"); }