Index: llvm/include/llvm/DebugInfo/Symbolize/Symbolize.h =================================================================== --- llvm/include/llvm/DebugInfo/Symbolize/Symbolize.h +++ llvm/include/llvm/DebugInfo/Symbolize/Symbolize.h @@ -243,6 +243,8 @@ std::function Evictor; }; +Optional> getBuildID(const ELFObjectFileBase *Obj); + } // end namespace symbolize } // end namespace llvm Index: llvm/include/llvm/Debuginfod/Debuginfod.h =================================================================== --- llvm/include/llvm/Debuginfod/Debuginfod.h +++ llvm/include/llvm/Debuginfod/Debuginfod.h @@ -7,23 +7,31 @@ //===----------------------------------------------------------------------===// /// /// \file -/// This file contains the declarations of getCachedOrDownloadArtifact and -/// several convenience functions for specific artifact types: -/// getCachedOrDownloadSource, getCachedOrDownloadExecutable, and -/// getCachedOrDownloadDebuginfo. This file also declares -/// getDefaultDebuginfodUrls and getDefaultDebuginfodCacheDirectory. -/// +/// This file contains several declarations for the debuginfod client and +/// server. The client functions are getDefaultDebuginfodUrls, +/// getCachedOrDownloadArtifact, and several convenience functions for specific +/// artifact types: getCachedOrDownloadSource, getCachedOrDownloadExecutable, +/// and getCachedOrDownloadDebuginfo. For the server, this file declares the +/// DebuginfodLogEntry and DebuginfodServer structs, as well as the +/// DebuginfodLog, DebuginfodCollection classes. /// //===----------------------------------------------------------------------===// #ifndef LLVM_DEBUGINFOD_DEBUGINFOD_H #define LLVM_DEBUGINFOD_DEBUGINFOD_H +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Debuginfod/HTTPServer.h" #include "llvm/Support/Error.h" #include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Mutex.h" +#include "llvm/Support/RWMutex.h" +#include "llvm/Support/Timer.h" #include +#include namespace llvm { @@ -35,10 +43,6 @@ /// environment variable. Expected> getDefaultDebuginfodUrls(); -/// Finds a default local file caching directory for the debuginfod client, -/// first checking DEBUGINFOD_CACHE_PATH. -Expected getDefaultDebuginfodCacheDirectory(); - /// Finds a default timeout for debuginfod HTTP requests. Checks /// DEBUGINFOD_TIMEOUT environment variable, default is 90 seconds (90000 ms). std::chrono::milliseconds getDefaultDebuginfodTimeout(); @@ -68,6 +72,68 @@ StringRef UniqueKey, StringRef UrlPath, StringRef CacheDirectoryPath, ArrayRef DebuginfodUrls, std::chrono::milliseconds Timeout); +class ThreadPool; + +struct DebuginfodLogEntry { + std::string Message; + DebuginfodLogEntry() = default; + DebuginfodLogEntry(const Twine &Message); +}; + +class DebuginfodLog { + std::mutex QueueMutex; + std::condition_variable QueueCondition; + std::queue LogEntryQueue; + +public: + // Adds a log entry to end of the queue. + void push(DebuginfodLogEntry Entry); + // Adds a log entry to end of the queue. + void push(const Twine &Message); + // Blocks until there are log entries in the queue, then pops and returns the + // first one. + DebuginfodLogEntry pop(); +}; + +/// Tracks a collection of debuginfod artifacts on the local filesystem. +class DebuginfodCollection { + SmallVector Paths; + sys::RWMutex BinariesMutex; + StringMap Binaries; + sys::RWMutex DebugBinariesMutex; + StringMap DebugBinaries; + Error findBinaries(StringRef Path); + Expected> getDebugBinaryPath(BuildIDRef); + Expected> getBinaryPath(BuildIDRef); + // If the collection has not been updated since MinInterval, call update() and + // return true. Otherwise return false. If update returns an error, return the + // error. + Expected updateIfStale(); + DebuginfodLog &Log; + ThreadPool &Pool; + Timer UpdateTimer; + sys::Mutex UpdateMutex; + + // Minimum update interval, in seconds, for on-demand updates triggered when a + // build-id is not found. + double MinInterval; + +public: + DebuginfodCollection(ArrayRef Paths, DebuginfodLog &Log, + ThreadPool &Pool, double MinInterval); + Error update(); + Error updateForever(std::chrono::milliseconds Interval); + Expected findDebugBinaryPath(BuildIDRef); + Expected findBinaryPath(BuildIDRef); +}; + +struct DebuginfodServer { + HTTPServer Server; + DebuginfodLog &Log; + DebuginfodCollection &Collection; + DebuginfodServer(DebuginfodLog &Log, DebuginfodCollection &Collection); +}; + } // end namespace llvm #endif Index: llvm/lib/DebugInfo/Symbolize/Symbolize.cpp =================================================================== --- llvm/lib/DebugInfo/Symbolize/Symbolize.cpp +++ llvm/lib/DebugInfo/Symbolize/Symbolize.cpp @@ -323,6 +323,8 @@ return {}; } +} // end anonymous namespace + Optional> getBuildID(const ELFObjectFileBase *Obj) { Optional> BuildID; if (auto *O = dyn_cast>(Obj)) @@ -338,8 +340,6 @@ return BuildID; } -} // end anonymous namespace - ObjectFile *LLVMSymbolizer::lookUpDsymFile(const std::string &ExePath, const MachOObjectFile *MachExeObj, const std::string &ArchName) { Index: llvm/lib/Debuginfod/CMakeLists.txt =================================================================== --- llvm/lib/Debuginfod/CMakeLists.txt +++ llvm/lib/Debuginfod/CMakeLists.txt @@ -25,4 +25,5 @@ LINK_COMPONENTS Support Symbolize + DebugInfoDWARF ) Index: llvm/lib/Debuginfod/Debuginfod.cpp =================================================================== --- llvm/lib/Debuginfod/Debuginfod.cpp +++ llvm/lib/Debuginfod/Debuginfod.cpp @@ -8,25 +8,41 @@ /// /// \file /// -/// This file defines the fetchInfo function, which retrieves -/// any of the three supported artifact types: (executable, debuginfo, source -/// file) associated with a build-id from debuginfod servers. If a source file -/// is to be fetched, its absolute path must be specified in the Description -/// argument to fetchInfo. +/// This file contains several definitions for the debuginfod client and server. +/// For the client, this file defines the fetchInfo function. For the server, +/// this file defines the DebuginfodLogEntry and DebuginfodServer structs, as +/// well as the DebuginfodLog, DebuginfodCollection classes. The fetchInfo +/// function retrieves any of the three supported artifact types: (executable, +/// debuginfo, source file) associated with a build-id from debuginfod servers. +/// If a source file is to be fetched, its absolute path must be specified in +/// the Description argument to fetchInfo. The DebuginfodLogEntry, +/// DebuginfodLog, and DebuginfodCollection are used by the DebuginfodServer to +/// scan the local filesystem for binaries and serve the debuginfod protocol. /// //===----------------------------------------------------------------------===// #include "llvm/Debuginfod/Debuginfod.h" #include "llvm/ADT/StringRef.h" +#include "llvm/BinaryFormat/Magic.h" +#include "llvm/DebugInfo/DWARF/DWARFContext.h" +#include "llvm/DebugInfo/Symbolize/Symbolize.h" #include "llvm/Debuginfod/HTTPClient.h" +#include "llvm/Object/Binary.h" +#include "llvm/Object/ELFObjectFile.h" +#include "llvm/Object/ObjectFile.h" #include "llvm/Support/CachePruning.h" #include "llvm/Support/Caching.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileUtilities.h" #include "llvm/Support/Path.h" +#include "llvm/Support/ThreadPool.h" #include "llvm/Support/xxhash.h" +#include + +#define DEBUG_TYPE "Debuginfod" + namespace llvm { static std::string uniqueKey(llvm::StringRef S) { return utostr(xxHash64(S)); } @@ -46,6 +62,8 @@ return DebuginfodUrls; } +/// Finds a default local file caching directory for the debuginfod client, +/// first checking DEBUGINFOD_CACHE_PATH. Expected getDefaultDebuginfodCacheDirectory() { if (const char *CacheDirectoryEnv = std::getenv("DEBUGINFOD_CACHE_PATH")) return CacheDirectoryEnv; @@ -183,4 +201,274 @@ return createStringError(errc::argument_out_of_domain, "build id not found"); } + +DebuginfodLogEntry::DebuginfodLogEntry(const Twine &Message) + : Message(Message.str()) {} + +void DebuginfodLog::push(const Twine &Message) { + push(DebuginfodLogEntry(Message)); +} + +void DebuginfodLog::push(DebuginfodLogEntry Entry) { + { + std::lock_guard Guard(QueueMutex); + LogEntryQueue.push(Entry); + } + QueueCondition.notify_one(); +} + +DebuginfodLogEntry DebuginfodLog::pop() { + { + std::unique_lock Guard(QueueMutex); + // Wait for messages to be pushed into the queue. + QueueCondition.wait(Guard, [&] { return !LogEntryQueue.empty(); }); + } + std::lock_guard Guard(QueueMutex); + if (!LogEntryQueue.size()) + llvm_unreachable("Expected message in the queue."); + + DebuginfodLogEntry Entry = LogEntryQueue.front(); + LogEntryQueue.pop(); + return Entry; +} + +DebuginfodCollection::DebuginfodCollection(ArrayRef PathsRef, + DebuginfodLog &Log, ThreadPool &Pool, + double MinInterval) + : Log(Log), Pool(Pool), MinInterval(MinInterval) { + for (StringRef Path : PathsRef) + Paths.push_back(Path.str()); +} + +Error DebuginfodCollection::update() { + std::lock_guard Guard(UpdateMutex); + + for (const std::string &Path : Paths) { + Log.push("Updating binaries at path " + Path); + if (Error Err = findBinaries(Path)) + return Err; + } + Log.push("Updated collection"); + return Error::success(); +} + +Expected DebuginfodCollection::updateIfStale() { + if (!UpdateTimer.isRunning() || + UpdateTimer.getTotalTime().getWallTime() < MinInterval) + return false; + if (Error Err = update()) + return std::move(Err); + return true; +} + +Error DebuginfodCollection::updateForever(std::chrono::milliseconds Interval) { + while (true) { + if (Error Err = update()) + return Err; + std::this_thread::sleep_for(Interval); + } + llvm_unreachable("updateForever loop should never end"); +} + +static bool isDebugBinary(object::ObjectFile *Object) { + // TODO: handle PDB debuginfo + std::unique_ptr Context = DWARFContext::create( + *Object, DWARFContext::ProcessDebugRelocations::Process); + const DWARFObject &DObj = Context->getDWARFObj(); + unsigned NumSections = 0; + DObj.forEachInfoSections([&](const DWARFSection &S) { NumSections++; }); + return NumSections; +} + +static bool hasELFMagic(StringRef FilePath) { + file_magic Type; + std::error_code EC = identify_magic(FilePath, Type); + if (EC) + return false; + switch (Type) { + case file_magic::elf: + case file_magic::elf_relocatable: + case file_magic::elf_executable: + case file_magic::elf_shared_object: + case file_magic::elf_core: + return true; + default: + return false; + } +} + +Error DebuginfodCollection::findBinaries(StringRef Path) { + std::error_code EC; + + for (sys::fs::recursive_directory_iterator I(Twine(Path), EC), E; I != E; + I.increment(EC)) { + if (EC) + return errorCodeToError(EC); + + std::string FilePath = I->path(); + + Pool.async([FilePath, this]() -> void { + if (!hasELFMagic(FilePath)) + return; + + Expected> BinOrErr = + object::createBinary(FilePath); + + if (!BinOrErr) { + consumeError(BinOrErr.takeError()); + return; + } + object::Binary *Bin = std::move(BinOrErr.get().getBinary()); + if (!Bin->isObject()) + return; + + // TODO: Support non-ELF binaries + object::ELFObjectFileBase *Object = + dyn_cast(Bin); + if (!Object) + return; + + Optional ID = symbolize::getBuildID(Object); + if (!ID) + return; + + std::string IDString = buildIDToString(ID.getValue()); + if (isDebugBinary(Object)) { + std::lock_guard DebugBinariesGuard(DebugBinariesMutex); + DebugBinaries[IDString] = FilePath; + } else { + std::lock_guard BinariesGuard(BinariesMutex); + Binaries[IDString] = FilePath; + } + return; + }); + // Wait for empty queue before proceeding to the next file to avoid + // unbounded memory usage. + Pool.waitQueueSize(); + } + return Error::success(); +} + +Expected> +DebuginfodCollection::getBinaryPath(BuildIDRef ID) { + Log.push("getting binary path of ID " + buildIDToString(ID)); + std::shared_lock Guard(BinariesMutex); + auto Loc = Binaries.find(buildIDToString(ID)); + if (Loc != Binaries.end()) { + std::string Path = Loc->getValue(); + return Path; + } + return None; +} + +Expected> +DebuginfodCollection::getDebugBinaryPath(BuildIDRef ID) { + Log.push("getting debug binary path of ID " + buildIDToString(ID)); + std::shared_lock Guard(DebugBinariesMutex); + auto Loc = DebugBinaries.find(buildIDToString(ID)); + if (Loc != DebugBinaries.end()) { + std::string Path = Loc->getValue(); + return Path; + } + return None; +} + +Expected DebuginfodCollection::findBinaryPath(BuildIDRef ID) { + { + // Check collection; perform on-demand update if stale. + Expected> PathOrErr = getBinaryPath(ID); + if (!PathOrErr) + return PathOrErr.takeError(); + Optional Path = *PathOrErr; + if (!Path) { + Expected UpdatedOrErr = updateIfStale(); + if (!UpdatedOrErr) + return UpdatedOrErr.takeError(); + if (*UpdatedOrErr) { + // Try once more. + PathOrErr = getBinaryPath(ID); + if (!PathOrErr) + return PathOrErr.takeError(); + Path = *PathOrErr; + } + } + if (Path) + return Path.getValue(); + } + + // Try federation. + Expected PathOrErr = getCachedOrDownloadExecutable(ID); + if (!PathOrErr) + consumeError(PathOrErr.takeError()); + + // Fall back to debug binary. + return findDebugBinaryPath(ID); +} + +Expected DebuginfodCollection::findDebugBinaryPath(BuildIDRef ID) { + // Check collection; perform on-demand update if stale. + Expected> PathOrErr = getDebugBinaryPath(ID); + if (!PathOrErr) + return PathOrErr.takeError(); + Optional Path = *PathOrErr; + if (!Path) { + Expected UpdatedOrErr = updateIfStale(); + if (!UpdatedOrErr) + return UpdatedOrErr.takeError(); + if (*UpdatedOrErr) { + // Try once more. + PathOrErr = getBinaryPath(ID); + if (!PathOrErr) + return PathOrErr.takeError(); + Path = *PathOrErr; + } + } + if (Path) + return Path.getValue(); + + // Try federation. + return getCachedOrDownloadDebuginfo(ID); +} + +DebuginfodServer::DebuginfodServer(DebuginfodLog &Log, + DebuginfodCollection &Collection) + : Log(Log), Collection(Collection) { + cantFail( + Server.get(R"(/buildid/(.*)/debuginfo)", [&](HTTPServerRequest Request) { + Log.push("GET " + Request.UrlPath); + std::string IDString; + if (!tryGetFromHex(Request.UrlPathMatches[0], IDString)) { + Request.setResponse( + {404, "text/plain", "Build ID is not a hex string\n"}); + return; + } + BuildID ID(IDString.begin(), IDString.end()); + Expected PathOrErr = Collection.findDebugBinaryPath(ID); + if (Error Err = PathOrErr.takeError()) { + consumeError(std::move(Err)); + Request.setResponse({404, "text/plain", "Build ID not found\n"}); + return; + } + streamFile(Request, *PathOrErr); + })); + cantFail( + Server.get(R"(/buildid/(.*)/executable)", [&](HTTPServerRequest Request) { + Log.push("GET " + Request.UrlPath); + std::string IDString; + if (!tryGetFromHex(Request.UrlPathMatches[0], IDString)) { + Request.setResponse( + {404, "text/plain", "Build ID is not a hex string\n"}); + return; + } + BuildID ID(IDString.begin(), IDString.end()); + Expected PathOrErr = Collection.findBinaryPath(ID); + if (Error Err = PathOrErr.takeError()) { + consumeError(std::move(Err)); + Request.setResponse({404, "text/plain", "Build ID not found\n"}); + return; + } + streamFile(Request, *PathOrErr); + })); +} + } // namespace llvm