Index: lib/sanitizer_common/CMakeLists.txt =================================================================== --- lib/sanitizer_common/CMakeLists.txt +++ lib/sanitizer_common/CMakeLists.txt @@ -156,3 +156,6 @@ if(COMPILER_RT_INCLUDE_TESTS) add_subdirectory(tests) endif() + +add_llvm_tool(sancov sancov.cc) +target_link_libraries(sancov LLVMSupport LLVMSymbolize LLVMObject LLVMDebugInfoDWARF LLVMDebugInfoPDB) Index: lib/sanitizer_common/sancov.cc =================================================================== --- /dev/null +++ lib/sanitizer_common/sancov.cc @@ -0,0 +1,312 @@ +//===-- 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 +#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, "coveredfns", + "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")); + +// --------- 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 bool removeDotPaths(SmallVectorImpl *Path) { + using namespace llvm::sys; + + SmallVector ComponentStack; + StringRef P(Path->data(), Path->size()); + + // Skip the root path, then look for traversal in the components. + StringRef Rel = path::relative_path(P); + for (StringRef C : llvm::make_range(path::begin(Rel), path::end(Rel))) { + if (C == ".") + continue; + if (C == "..") { + if (!ComponentStack.empty()) + ComponentStack.pop_back(); + continue; + } + ComponentStack.push_back(C); + } + + SmallString<256> Buffer = path::root_path(P); + for (StringRef C : ComponentStack) + path::append(Buffer, C); + + bool Changed = (*Path != Buffer); + Path->swap(Buffer); + return Changed; +} + +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); +} + +static std::string demangle(std::string Name) { + if (Name.substr(0, 2) != "_Z") { + return Name; + } + + int status = 0; + char *DemangledName = + abi::__cxa_demangle(Name.c_str(), nullptr, nullptr, &status); + if (status != 0) + return Name; + std::string Result = DemangledName; + free(DemangledName); + return Result; +} + +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); + } + + std::unique_ptr> Addrs(new std::vector()); + + 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::unique_ptr> Addrs(new std::vector()); + int Sz = std::accumulate( + Covs.begin(), Covs.end(), 0, + [](const int &A, const std::unique_ptr &B) { + return A + B->Addrs->size(); + }); + Addrs->reserve(Sz); + for (const auto &Cov : Covs) { + Addrs->insert(Addrs->end(), Cov->Addrs->begin(), Cov->Addrs->end()); + } + std::sort(Addrs->begin(), Addrs->end()); + Addrs->erase(std::unique(Addrs->begin(), Addrs->end()), 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 &out) { + for (auto Addr : *Addrs) { + out << "0x"; + out.write_hex(Addr); + out << "\n"; + } + } + + // Print list of covered functions. + // Line format: : + void printCoveredFns(raw_ostream &out) { + if (Addrs->empty()) + return; + 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); + removeDotPaths(&FileName); + uint32_t Line = FrameInfo.Line; + std::string FunctionName = demangle(FrameInfo.FunctionName); + + FileLoc Loc{FileName.str(), Line}; + Fns[Loc] = 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.find(FunctionName) != ProcessedFunctions.end()) { + continue; + } + ProcessedFunctions.insert(FunctionName); + + 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()->printCoveredFns(outs()); + return 0; + } + } +}