diff options
Diffstat (limited to 'tools/sancov/sancov.cc')
-rw-r--r-- | tools/sancov/sancov.cc | 533 |
1 files changed, 533 insertions, 0 deletions
diff --git a/tools/sancov/sancov.cc b/tools/sancov/sancov.cc new file mode 100644 index 000000000000..a07cdbe097a3 --- /dev/null +++ b/tools/sancov/sancov.cc @@ -0,0 +1,533 @@ +//===-- 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 <set> +#include <stdio.h> +#include <string> +#include <vector> + +using namespace llvm; + +namespace { + +// --------- COMMAND LINE FLAGS --------- + +enum ActionType { + PrintAction, + CoveredFunctionsAction, + NotCoveredFunctionsAction +}; + +cl::opt<ActionType> 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<std::string> ClInputFiles(cl::Positional, cl::OneOrMore, + cl::desc("<filenames...>")); + +static cl::opt<std::string> + ClBinaryName("obj", cl::Required, + cl::desc("Path to object file to be symbolized")); + +static cl::opt<bool> + ClDemangle("demangle", cl::init(true), + cl::desc("Print demangled function name.")); + +static cl::opt<std::string> ClStripPathPrefix( + "strip_path_prefix", cl::init(""), + cl::desc("Strip this prefix from file paths in reports.")); + +static cl::opt<std::string> + ClBlacklist("blacklist", cl::init(""), + cl::desc("Blacklist file (sanitizer blacklist format).")); + +static cl::opt<bool> 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 <typename T> static void FailIfError(const ErrorOr<T> &E) { + FailIfError(E.getError()); +} + +static void FailIfNotEmpty(const std::string &E) { + if (E.empty()) + return; + errs() << "Error: " << E << "\n"; + exit(1); +} + +template <typename T> +static void FailIfEmpty(const std::unique_ptr<T> &Ptr, + const std::string &Message) { + if (Ptr.get()) + return; + errs() << "Error: " << Message << "\n"; + exit(1); +} + +template <typename T> +static void readInts(const char *Start, const char *End, + std::set<uint64_t> *Ints) { + const T *S = reinterpret_cast<const T *>(Start); + const T *E = reinterpret_cast<const T *>(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<FileLoc, std::string> +computeFunctionsMap(const std::set<uint64_t> &Addrs) { + std::map<FileLoc, std::string> 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<FunctionLoc> computeFunctionLocs(const std::set<uint64_t> &Addrs) { + std::map<FileLoc, std::string> Fns = computeFunctionsMap(Addrs); + + std::set<FunctionLoc> Result; + std::string LastFileName; + std::set<std::string> 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<uint64_t> +findSanitizerCovFunctions(const object::ObjectFile &O) { + std::set<uint64_t> Result; + + for (const object::SymbolRef &Symbol : O.symbols()) { + ErrorOr<uint64_t> AddressOrErr = Symbol.getAddress(); + FailIfError(AddressOrErr); + + ErrorOr<StringRef> 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<uint64_t> *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<const MCSubtargetInfo> STI( + TheTarget->createMCSubtargetInfo(TripleName, "", "")); + FailIfEmpty(STI, "no subtarget info for target " + TripleName); + + std::unique_ptr<const MCRegisterInfo> MRI( + TheTarget->createMCRegInfo(TripleName)); + FailIfEmpty(MRI, "no register info for target " + TripleName); + + std::unique_ptr<const MCAsmInfo> AsmInfo( + TheTarget->createMCAsmInfo(*MRI, TripleName)); + FailIfEmpty(AsmInfo, "no asm info for target " + TripleName); + + std::unique_ptr<const MCObjectFileInfo> MOFI(new MCObjectFileInfo); + MCContext Ctx(AsmInfo.get(), MRI.get(), MOFI.get()); + std::unique_ptr<MCDisassembler> DisAsm( + TheTarget->createMCDisassembler(*STI, Ctx)); + FailIfEmpty(DisAsm, "no disassembler info for target " + TripleName); + + std::unique_ptr<const MCInstrInfo> MII(TheTarget->createMCInstrInfo()); + FailIfEmpty(MII, "no instruction info for target " + TripleName); + + std::unique_ptr<const MCInstrAnalysis> 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<uint8_t> Bytes(reinterpret_cast<const uint8_t *>(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<uint64_t> *Addrs) { + for (auto &ErrorOrChild : A.children()) { + FailIfError(ErrorOrChild); + const object::Archive::Child &C = *ErrorOrChild; + ErrorOr<std::unique_ptr<object::Binary>> ChildOrErr = C.getAsBinary(); + FailIfError(ChildOrErr); + if (object::ObjectFile *O = + dyn_cast<object::ObjectFile>(&*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<uint64_t> getCoveragePoints(std::string FileName) { + std::set<uint64_t> Result; + + ErrorOr<object::OwningBinary<object::Binary>> BinaryOrErr = + object::createBinary(FileName); + FailIfError(BinaryOrErr); + + object::Binary &Binary = *BinaryOrErr.get().getBinary(); + if (object::Archive *A = dyn_cast<object::Archive>(&Binary)) + getArchiveCoveragePoints(*A, &Result); + else if (object::ObjectFile *O = dyn_cast<object::ObjectFile>(&Binary)) + getObjectCoveragePoints(*O, &Result); + else + FailIfError(object::object_error::invalid_file_type); + + return Result; +} + +static std::unique_ptr<SpecialCaseList> createDefaultBlacklist() { + if (!ClUseDefaultBlacklist) + return std::unique_ptr<SpecialCaseList>(); + std::unique_ptr<MemoryBuffer> MB = + MemoryBuffer::getMemBuffer(DefaultBlacklist); + std::string Error; + auto Blacklist = SpecialCaseList::create(MB.get(), Error); + FailIfNotEmpty(Error); + return Blacklist; +} + +static std::unique_ptr<SpecialCaseList> createUserBlacklist() { + if (ClBlacklist.empty()) + return std::unique_ptr<SpecialCaseList>(); + + return SpecialCaseList::createOrDie({{ClBlacklist}}); +} + +static void printFunctionLocs(const std::set<FunctionLoc> &FnLocs, + raw_ostream &OS) { + std::unique_ptr<SpecialCaseList> DefaultBlacklist = createDefaultBlacklist(); + std::unique_ptr<SpecialCaseList> 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<std::unique_ptr<CoverageData>> read(std::string FileName) { + ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr = + MemoryBuffer::getFile(FileName); + if (!BufOrErr) + return BufOrErr.getError(); + std::unique_ptr<MemoryBuffer> 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<const FileHeader *>(Buf->getBufferStart()); + + if (Header->Magic != BinCoverageMagic) { + errs() << "Wrong magic: " << Header->Magic; + return make_error_code(errc::illegal_byte_sequence); + } + + auto Addrs = llvm::make_unique<std::set<uint64_t>>(); + + switch (Header->Bitness) { + case Bitness64: + readInts<uint64_t>(Buf->getBufferStart() + 8, Buf->getBufferEnd(), + Addrs.get()); + break; + case Bitness32: + readInts<uint32_t>(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<CoverageData>(new CoverageData(std::move(Addrs))); + } + + // Merge multiple coverage data together. + static std::unique_ptr<CoverageData> + merge(const std::vector<std::unique_ptr<CoverageData>> &Covs) { + auto Addrs = llvm::make_unique<std::set<uint64_t>>(); + + for (const auto &Cov : Covs) + Addrs->insert(Cov->Addrs->begin(), Cov->Addrs->end()); + + return std::unique_ptr<CoverageData>(new CoverageData(std::move(Addrs))); + } + + // Read list of files and merges their coverage info. + static ErrorOr<std::unique_ptr<CoverageData>> + readAndMerge(const std::vector<std::string> &FileNames) { + std::vector<std::unique_ptr<CoverageData>> 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: <file_name>:<line> <function_name> + void printCoveredFunctions(raw_ostream &OS) { + printFunctionLocs(computeFunctionLocs(*Addrs), OS); + } + + // Print list of not covered functions. + // Line format: <file_name>:<line> <function_name> + void printNotCoveredFunctions(raw_ostream &OS) { + std::set<FunctionLoc> AllFns = + computeFunctionLocs(getCoveragePoints(ClBinaryName)); + std::set<FunctionLoc> CoveredFns = computeFunctionLocs(*Addrs); + + std::set<FunctionLoc> 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<std::set<uint64_t>> Addrs) + : Addrs(std::move(Addrs)) {} + + std::unique_ptr<std::set<uint64_t>> 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"); +} |