diff --git a/compiler-rt/test/profile/Linux/binary-id-lookup.c b/compiler-rt/test/profile/Linux/binary-id-lookup.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/profile/Linux/binary-id-lookup.c @@ -0,0 +1,26 @@ +// REQUIRES: linux +// RUN: split-file %s %t +// RUN: %clang_profgen -Wl,--build-id=0x12345678 -fcoverage-mapping -O2 -shared %t/foo.c -o %t/libfoo.so +// RUN: %clang_profgen -Wl,--build-id=0xabcd1234 -fcoverage-mapping -O2 %t/main.c -L%t -lfoo -o %t.main +// RUN: env LLVM_PROFILE_FILE=%t.profraw LD_LIBRARY_PATH=%t %run %t.main +// RUN: mkdir -p %t/.build-id/12 %t/.build-id/ab +// RUN: cp %t/libfoo.so %t/.build-id/12/345678.debug +// RUN: echo "bad" > %t/.build-id/ab/cd1234.debug +// RUN: llvm-profdata merge -o %t.profdata %t.profraw +// RUN: llvm-cov show -instr-profile %t.profdata -debug-file-directory %t %t.main | FileCheck %s + +// CHECK: foo +// CHECK: bar +// CHECK: main + +//--- foo.c +void foo(void) {} + +//--- main.c +void foo(void); +void bar(void) {} +int main() { + foo(); + bar(); + return 0; +} diff --git a/llvm/docs/CommandGuide/llvm-cov.rst b/llvm/docs/CommandGuide/llvm-cov.rst --- a/llvm/docs/CommandGuide/llvm-cov.rst +++ b/llvm/docs/CommandGuide/llvm-cov.rst @@ -349,6 +349,18 @@ coverage >= high, red when coverage < low, and yellow otherwise. Both high and low should be between 0-100 and high > low. +.. option:: -debuginfod + +Attempt to look up coverage mapping from objects using debuginfod. This is +attempted by default for binary IDs present in the profile but not provided on +the command line, so long as debuginfod is compiled in and configured via +DEBUGINFOD_URLS. + +.. option:: -debug-file-directory= + +Provides a directory to search for objects corresponding to binary IDs in the +profile. + .. program:: llvm-cov report .. _llvm-cov-report: @@ -418,6 +430,18 @@ when binaries have been compiled with one of `-fcoverage-prefix-map` `-fcoverage-compilation-dir`, or `-ffile-compilation-dir`. +.. option:: -debuginfod + +Attempt to look up coverage mapping from objects using debuginfod. This is +attempted by default for binary IDs present in the profile but not provided on +the command line, so long as debuginfod is compiled in and configured via +DEBUGINFOD_URLS. + +.. option:: -debug-file-directory= + +Provides a directory to search for objects corresponding to binary IDs in the +profile. + .. program:: llvm-cov export .. _llvm-cov-export: @@ -492,3 +516,15 @@ Directory used as a base for relative coverage mapping paths. Only applicable when binaries have been compiled with one of `-fcoverage-prefix-map` `-fcoverage-compilation-dir`, or `-ffile-compilation-dir`. + +.. option:: -debuginfod + +Attempt to look up coverage mapping from objects using debuginfod. This is +attempted by default for binary IDs present in the profile but not provided on +the command line, so long as debuginfod is compiled in and configured via +DEBUGINFOD_URLS. + +.. option:: -debug-file-directory= + +Provides a directory to search for objects corresponding to binary IDs in the +profile. diff --git a/llvm/include/llvm/Debuginfod/Debuginfod.h b/llvm/include/llvm/Debuginfod/Debuginfod.h --- a/llvm/include/llvm/Debuginfod/Debuginfod.h +++ b/llvm/include/llvm/Debuginfod/Debuginfod.h @@ -38,6 +38,10 @@ namespace llvm { +/// Returns false if a debuginfod lookup can be determined to have no chance of +/// succeeding. +bool canUseDebuginfod(); + /// Finds default array of Debuginfod server URLs by checking DEBUGINFOD_URLS /// environment variable. Expected> getDefaultDebuginfodUrls(); diff --git a/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h b/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h --- a/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h +++ b/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h @@ -22,6 +22,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/ADT/iterator.h" #include "llvm/ADT/iterator_range.h" +#include "llvm/Object/BuildID.h" #include "llvm/ProfileData/InstrProf.h" #include "llvm/Support/Alignment.h" #include "llvm/Support/Compiler.h" @@ -43,6 +44,10 @@ class IndexedInstrProfReader; +namespace object { +class BuildIDFetcher; +} // namespace object + namespace coverage { class CoverageMappingReader; @@ -580,6 +585,13 @@ ArrayRef> CoverageReaders, IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage); + // Load coverage records from file. + static Error + loadFromFile(StringRef Filename, StringRef Arch, StringRef CompilationDir, + IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage, + bool &DataFound, + SmallVectorImpl *FoundBinaryIDs = nullptr); + /// Add a function record corresponding to \p Record. Error loadFunctionRecord(const CoverageMappingRecord &Record, IndexedInstrProfReader &ProfileReader); @@ -605,7 +617,8 @@ /// Ignores non-instrumented object files unless all are not instrumented. static Expected> load(ArrayRef ObjectFilenames, StringRef ProfileFilename, - ArrayRef Arches = None, StringRef CompilationDir = ""); + ArrayRef Arches = None, StringRef CompilationDir = "", + const object::BuildIDFetcher* BIDFetcher = nullptr); /// The number of functions that couldn't have their profiles mapped. /// diff --git a/llvm/include/llvm/ProfileData/Coverage/CoverageMappingReader.h b/llvm/include/llvm/ProfileData/Coverage/CoverageMappingReader.h --- a/llvm/include/llvm/ProfileData/Coverage/CoverageMappingReader.h +++ b/llvm/include/llvm/ProfileData/Coverage/CoverageMappingReader.h @@ -205,7 +205,8 @@ static Expected>> create(MemoryBufferRef ObjectBuffer, StringRef Arch, SmallVectorImpl> &ObjectFileBuffers, - StringRef CompilationDir = ""); + StringRef CompilationDir = "", + SmallVectorImpl *BinaryIDs = nullptr); static Expected> createCoverageReaderFromBuffer(StringRef Coverage, diff --git a/llvm/lib/Debuginfod/Debuginfod.cpp b/llvm/lib/Debuginfod/Debuginfod.cpp --- a/llvm/lib/Debuginfod/Debuginfod.cpp +++ b/llvm/lib/Debuginfod/Debuginfod.cpp @@ -53,6 +53,17 @@ return llvm::toHex(ID, /*LowerCase=*/true); } +bool canUseDebuginfod() { + if (!HTTPClient::isAvailable()) + return false; + Expected> Urls = getDefaultDebuginfodUrls(); + if (!Urls) { + consumeError(Urls.takeError()); + return false; + } + return !Urls->empty(); +} + Expected> getDefaultDebuginfodUrls() { const char *DebuginfodUrlsEnv = std::getenv("DEBUGINFOD_URLS"); if (DebuginfodUrlsEnv == nullptr) diff --git a/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp b/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp --- a/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp +++ b/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp @@ -19,6 +19,7 @@ #include "llvm/ADT/SmallBitVector.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Object/BuildID.h" #include "llvm/ProfileData/Coverage/CoverageMappingReader.h" #include "llvm/ProfileData/InstrProfReader.h" #include "llvm/Support/Debug.h" @@ -343,10 +344,49 @@ }); } +Error CoverageMapping::loadFromFile( + StringRef Filename, StringRef Arch, StringRef CompilationDir, + IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage, + bool &DataFound, SmallVectorImpl *FoundBinaryIDs) { + auto CovMappingBufOrErr = MemoryBuffer::getFileOrSTDIN( + Filename, /*IsText=*/false, /*RequiresNullTerminator=*/false); + if (std::error_code EC = CovMappingBufOrErr.getError()) + return createFileError(Filename, errorCodeToError(EC)); + MemoryBufferRef CovMappingBufRef = + CovMappingBufOrErr.get()->getMemBufferRef(); + SmallVector, 4> Buffers; + + SmallVector BinaryIDs; + auto CoverageReadersOrErr = BinaryCoverageReader::create( + CovMappingBufRef, Arch, Buffers, CompilationDir, + FoundBinaryIDs ? &BinaryIDs : nullptr); + if (Error E = CoverageReadersOrErr.takeError()) { + E = handleMaybeNoDataFoundError(std::move(E)); + if (E) + return createFileError(Filename, std::move(E)); + return E; + } + + SmallVector, 4> Readers; + for (auto &Reader : CoverageReadersOrErr.get()) + Readers.push_back(std::move(Reader)); + if (FoundBinaryIDs && !Readers.empty()) { + llvm::append_range(*FoundBinaryIDs, + llvm::map_range(BinaryIDs, [](object::BuildIDRef BID) { + return object::BuildID(BID); + })); + } + DataFound |= !Readers.empty(); + if (Error E = loadFromReaders(Readers, ProfileReader, Coverage)) + return createFileError(Filename, std::move(E)); + return Error::success(); +} + Expected> CoverageMapping::load(ArrayRef ObjectFilenames, StringRef ProfileFilename, ArrayRef Arches, - StringRef CompilationDir) { + StringRef CompilationDir, + const object::BuildIDFetcher* BIDFetcher) { auto ProfileReaderOrErr = IndexedInstrProfReader::create(ProfileFilename); if (Error E = ProfileReaderOrErr.takeError()) return createFileError(ProfileFilename, std::move(E)); @@ -354,32 +394,55 @@ auto Coverage = std::unique_ptr(new CoverageMapping()); bool DataFound = false; + auto GetArch = [&](size_t Idx) { + if (Arches.empty()) + return StringRef(); + if (Arches.size() == 1) + return Arches.front(); + return Arches[Idx]; + }; + + SmallVector FoundBinaryIDs; for (const auto &File : llvm::enumerate(ObjectFilenames)) { - auto CovMappingBufOrErr = MemoryBuffer::getFileOrSTDIN( - File.value(), /*IsText=*/false, /*RequiresNullTerminator=*/false); - if (std::error_code EC = CovMappingBufOrErr.getError()) - return createFileError(File.value(), errorCodeToError(EC)); - StringRef Arch = Arches.empty() ? StringRef() : Arches[File.index()]; - MemoryBufferRef CovMappingBufRef = - CovMappingBufOrErr.get()->getMemBufferRef(); - SmallVector, 4> Buffers; - auto CoverageReadersOrErr = BinaryCoverageReader::create( - CovMappingBufRef, Arch, Buffers, CompilationDir); - if (Error E = CoverageReadersOrErr.takeError()) { - E = handleMaybeNoDataFoundError(std::move(E)); - if (E) - return createFileError(File.value(), std::move(E)); - // E == success (originally a no_data_found error). - continue; + if (Error E = + loadFromFile(File.value(), GetArch(File.index()), CompilationDir, + *ProfileReader, *Coverage, DataFound, &FoundBinaryIDs)) + return E; + } + + if (BIDFetcher) { + const auto &Compare = [](object::BuildIDRef A, object::BuildIDRef B) { + return StringRef(reinterpret_cast(A.data()), A.size()) < + StringRef(reinterpret_cast(B.data()), B.size()); + }; + std::vector ProfileBinaryIDs; + if (Error E = ProfileReader->readBinaryIds(ProfileBinaryIDs)) + return createFileError(ProfileFilename, std::move(E)); + llvm::sort(ProfileBinaryIDs, Compare); + std::unique(ProfileBinaryIDs.begin(), ProfileBinaryIDs.end(), Compare); + + SmallVector BinaryIDsToFetch; + if (!ProfileBinaryIDs.empty()) { + llvm::sort(FoundBinaryIDs, Compare); + std::unique(FoundBinaryIDs.begin(), FoundBinaryIDs.end(), Compare); + std::set_difference( + ProfileBinaryIDs.begin(), ProfileBinaryIDs.end(), + FoundBinaryIDs.begin(), FoundBinaryIDs.end(), + std::inserter(BinaryIDsToFetch, BinaryIDsToFetch.end()), Compare); } - SmallVector, 4> Readers; - for (auto &Reader : CoverageReadersOrErr.get()) - Readers.push_back(std::move(Reader)); - DataFound |= !Readers.empty(); - if (Error E = loadFromReaders(Readers, *ProfileReader, *Coverage)) - return createFileError(File.value(), std::move(E)); + for (object::BuildIDRef BinaryID : BinaryIDsToFetch) { + Optional PathOpt = BIDFetcher->fetch(BinaryID); + if (!PathOpt) + continue; + std::string Path = std::move(*PathOpt); + StringRef Arch = Arches.size() == 1 ? Arches.front() : StringRef(); + if (Error E = loadFromFile(Path, Arch, CompilationDir, *ProfileReader, + *Coverage, DataFound)) + return E; + } } + // If no readers were created, either no objects were provided or none of them // had coverage data. Return an error in the latter case. if (!DataFound && !ObjectFilenames.empty()) diff --git a/llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp b/llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp --- a/llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp +++ b/llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp @@ -954,7 +954,8 @@ static Expected> loadBinaryFormat(std::unique_ptr Bin, StringRef Arch, - StringRef CompilationDir = "") { + StringRef CompilationDir = "", + Optional *BinaryID = nullptr) { std::unique_ptr OF; if (auto *Universal = dyn_cast(Bin.get())) { // If we have a universal binary, try to look up the object for the @@ -1052,6 +1053,9 @@ FuncRecords = std::move(WritableBuffer); } + if (BinaryID) + *BinaryID = getBuildID(OF.get()); + return BinaryCoverageReader::createCoverageReaderFromBuffer( CoverageMapping, std::move(FuncRecords), std::move(ProfileNames), BytesInAddress, Endian, CompilationDir); @@ -1074,7 +1078,8 @@ BinaryCoverageReader::create( MemoryBufferRef ObjectBuffer, StringRef Arch, SmallVectorImpl> &ObjectFileBuffers, - StringRef CompilationDir) { + StringRef CompilationDir, + SmallVectorImpl *BinaryIDs) { std::vector> Readers; if (ObjectBuffer.getBuffer().startswith(TestingFormatMagic)) { @@ -1114,7 +1119,7 @@ return BinaryCoverageReader::create( ArchiveOrErr.get()->getMemoryBufferRef(), Arch, ObjectFileBuffers, - CompilationDir); + CompilationDir, BinaryIDs); } } @@ -1127,7 +1132,8 @@ return ChildBufOrErr.takeError(); auto ChildReadersOrErr = BinaryCoverageReader::create( - ChildBufOrErr.get(), Arch, ObjectFileBuffers, CompilationDir); + ChildBufOrErr.get(), Arch, ObjectFileBuffers, CompilationDir, + BinaryIDs); if (!ChildReadersOrErr) return ChildReadersOrErr.takeError(); for (auto &Reader : ChildReadersOrErr.get()) @@ -1146,10 +1152,14 @@ return std::move(Readers); } - auto ReaderOrErr = loadBinaryFormat(std::move(Bin), Arch, CompilationDir); + Optional BinaryID; + auto ReaderOrErr = loadBinaryFormat(std::move(Bin), Arch, CompilationDir, + BinaryIDs ? &BinaryID : nullptr); if (!ReaderOrErr) return ReaderOrErr.takeError(); Readers.push_back(std::move(ReaderOrErr.get())); + if (BinaryID) + BinaryIDs->push_back(*BinaryID); return std::move(Readers); } diff --git a/llvm/tools/llvm-cov/CMakeLists.txt b/llvm/tools/llvm-cov/CMakeLists.txt --- a/llvm/tools/llvm-cov/CMakeLists.txt +++ b/llvm/tools/llvm-cov/CMakeLists.txt @@ -14,3 +14,5 @@ SourceCoverageViewText.cpp TestingSupport.cpp ) + +target_link_libraries(llvm-cov PRIVATE LLVMDebuginfod) diff --git a/llvm/tools/llvm-cov/CodeCoverage.cpp b/llvm/tools/llvm-cov/CodeCoverage.cpp --- a/llvm/tools/llvm-cov/CodeCoverage.cpp +++ b/llvm/tools/llvm-cov/CodeCoverage.cpp @@ -23,6 +23,10 @@ #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Triple.h" +#include "llvm/Debuginfod/BuildIDFetcher.h" +#include "llvm/Debuginfod/Debuginfod.h" +#include "llvm/Debuginfod/HTTPClient.h" +#include "llvm/Object/BuildID.h" #include "llvm/ProfileData/Coverage/CoverageMapping.h" #include "llvm/ProfileData/InstrProfReader.h" #include "llvm/Support/CommandLine.h" @@ -178,6 +182,8 @@ /// Allowlist from -name-allowlist to be used for filtering. std::unique_ptr NameAllowlist; + + std::unique_ptr BIDFetcher; }; } @@ -434,7 +440,7 @@ ObjectFilename); auto CoverageOrErr = CoverageMapping::load(ObjectFilenames, PGOFilename, CoverageArches, - ViewOpts.CompilationDirectory); + ViewOpts.CompilationDirectory, BIDFetcher.get()); if (Error E = CoverageOrErr.takeError()) { error("Failed to load coverage: " + toString(std::move(E))); return nullptr; @@ -643,6 +649,14 @@ cl::opt DebugDump("dump", cl::Optional, cl::desc("Show internal debug dump")); + cl::list DebugFileDirectory( + "debug-file-directory", cl::Optional, + cl::desc("Directories to search for object files by build ID")); + cl::opt Debuginfod( + "debuginfod", cl::Optional, + cl::desc("Use debuginfod to look up object files from profile."), + cl::init(canUseDebuginfod())); + cl::opt Format( "format", cl::desc("Output format for line-based coverage reports"), cl::values(clEnumValN(CoverageViewOptions::OutputFormat::Text, "text", @@ -745,12 +759,16 @@ auto commandLineParser = [&, this](int argc, const char **argv) -> int { cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n"); ViewOpts.Debug = DebugDump; + if (Debuginfod) + BIDFetcher = std::make_unique(DebugFileDirectory); + else + BIDFetcher = std::make_unique(DebugFileDirectory); if (!CovFilename.empty()) ObjectFilenames.emplace_back(CovFilename); for (const std::string &Filename : CovFilenames) ObjectFilenames.emplace_back(Filename); - if (ObjectFilenames.empty()) { + if (ObjectFilenames.empty() && !Debuginfod && DebugFileDirectory.empty()) { errs() << "No filenames specified!\n"; ::exit(1); } @@ -863,10 +881,8 @@ } CoverageArches.emplace_back(Arch); } - if (CoverageArches.size() == 1) - CoverageArches.insert(CoverageArches.end(), ObjectFilenames.size() - 1, - CoverageArches[0]); - if (CoverageArches.size() != ObjectFilenames.size()) { + if (CoverageArches.size() != 1 && + CoverageArches.size() != ObjectFilenames.size()) { error("Number of architectures doesn't match the number of objects"); return 1; } diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp --- a/llvm/tools/llvm-objdump/llvm-objdump.cpp +++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp @@ -3176,9 +3176,7 @@ // Initialize debuginfod. const bool ShouldUseDebuginfodByDefault = - InputArgs.hasArg(OBJDUMP_build_id) || - (HTTPClient::isAvailable() && - !ExitOnErr(getDefaultDebuginfodUrls()).empty()); + InputArgs.hasArg(OBJDUMP_build_id) || canUseDebuginfod(); std::vector DebugFileDirectories = InputArgs.getAllArgValues(OBJDUMP_debug_file_directory); if (InputArgs.hasFlag(OBJDUMP_debuginfod, OBJDUMP_no_debuginfod, diff --git a/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp b/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp --- a/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp +++ b/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp @@ -440,13 +440,7 @@ LLVMSymbolizer Symbolizer(Opts); - // A debuginfod lookup could succeed if a HTTP client is available and at - // least one backing URL is configured. - bool ShouldUseDebuginfodByDefault = - HTTPClient::isAvailable() && - !ExitOnErr(getDefaultDebuginfodUrls()).empty(); - if (Args.hasFlag(OPT_debuginfod, OPT_no_debuginfod, - ShouldUseDebuginfodByDefault)) + if (Args.hasFlag(OPT_debuginfod, OPT_no_debuginfod, canUseDebuginfod())) enableDebuginfod(Symbolizer, Args); if (Args.hasArg(OPT_filter_markup)) {