diff --git a/llvm/tools/sancov/CMakeLists.txt b/llvm/tools/sancov/CMakeLists.txt new file mode 100644 index 000000000000..f891f779e8c9 --- /dev/null +++ b/llvm/tools/sancov/CMakeLists.txt @@ -0,0 +1,11 @@ +set(LLVM_LINK_COMPONENTS + DebugInfoDWARF + DebugInfoPDB + Object + Support + Symbolize + ) + +add_llvm_tool(sancov + sancov.cc + ) diff --git a/llvm/tools/sancov/Makefile b/llvm/tools/sancov/Makefile new file mode 100644 index 000000000000..1114fe052d4d --- /dev/null +++ b/llvm/tools/sancov/Makefile @@ -0,0 +1,17 @@ +##===- tools/sancov/Makefile ----------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +LEVEL := ../.. +TOOLNAME := sancov +LINK_COMPONENTS := DebugInfoDWARF DebugInfoPDB Object Support Symbolize + +# This tool has no plugins, optimize startup time. +TOOL_NO_EXPORTS := 1 + +include $(LEVEL)/Makefile.common diff --git a/llvm/tools/sancov/sancov.cc b/llvm/tools/sancov/sancov.cc new file mode 100644 index 000000000000..a611153e468b --- /dev/null +++ b/llvm/tools/sancov/sancov.cc @@ -0,0 +1,264 @@ +//===-- 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/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/ToolOutputFile.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include +#include + +using namespace llvm; + +namespace { + +// --------- COMMAND LINE FLAGS --------- + +enum ActionType { PrintAction, CoveredFunctionsAction }; + +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."), + 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.")); + +// --------- 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; + +// --------- + +template static void FailIfError(const ErrorOr &E) { + if (E) + return; + + auto Error = E.getError(); + errs() << "Error: " << Error.message() << "(" << Error.value() << ")\n"; + exit(-2); +} + +template +static void readInts(const char *Start, const char *End, + std::vector *V) { + const T *S = reinterpret_cast(Start); + const T *E = reinterpret_cast(End); + V->reserve(E - S); + std::copy(S, E, std::back_inserter(*V)); +} + +static std::string CommonPrefix(std::string A, std::string B) { + if (A.size() > B.size()) + return std::string(B.begin(), + std::mismatch(B.begin(), B.end(), A.begin()).first); + else + return std::string(A.begin(), + std::mismatch(A.begin(), A.end(), B.begin()).first); +} + +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 = 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) { + std::set Addrs; + + for (const auto &Cov : Covs) + Addrs.insert(Cov->Addrs->begin(), Cov->Addrs->end()); + + auto AddrsVector = make_unique>( + Addrs.begin(), Addrs.end()); + return std::unique_ptr( + new CoverageData(std::move(AddrsVector))); + } + + // 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 &out) { + for (auto Addr : *Addrs) { + out << "0x"; + out.write_hex(Addr); + out << "\n"; + } + } + + // Print list of covered functions. + // Line format: : + void printCoveredFunctions(raw_ostream &out) { + if (Addrs->empty()) + return; + symbolize::LLVMSymbolizer::Options SymbolizerOptions; + SymbolizerOptions.Demangle = ClDemangle; + symbolize::LLVMSymbolizer Symbolizer; + + struct FileLoc { + std::string FileName; + uint32_t Line; + bool operator<(const FileLoc &Rhs) const { + return std::tie(FileName, Line) < std::tie(Rhs.FileName, Rhs.Line); + } + }; + + // FileLoc -> FunctionName + std::map Fns; + + // 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; + } + } + + // Compute file names common prefix. + std::string FilePrefix = Fns.begin()->first.FileName; + for (const auto &P : Fns) + FilePrefix = CommonPrefix(FilePrefix, P.first.FileName); + + // Print first function occurence in a file. + { + std::string LastFileName; + std::set ProcessedFunctions; + + for (const auto &P : Fns) { + std::string FileName = P.first.FileName; + std::string FunctionName = P.second; + uint32_t Line = P.first.Line; + + if (LastFileName != FileName) + ProcessedFunctions.clear(); + LastFileName = FileName; + + if (!ProcessedFunctions.insert(FunctionName).second) + continue; + + out << FileName.substr(FilePrefix.size()) << ":" << Line << " " + << FunctionName << "\n"; + } + } + } + + 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. + + 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; + } + } +}