#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 <atomic>
namespace llvm {
static std::string uniqueKey(llvm::StringRef S) { return utostr(xxHash64(S)); }
static std::string buildIDToString(BuildIDRef ID) {
return llvm::toHex(ID, true);
}
Expected<SmallVector<StringRef>> getDefaultDebuginfodUrls() {
const char *DebuginfodUrlsEnv = std::getenv("DEBUGINFOD_URLS");
if (DebuginfodUrlsEnv == nullptr)
return SmallVector<StringRef>();
SmallVector<StringRef> DebuginfodUrls;
StringRef(DebuginfodUrlsEnv).split(DebuginfodUrls, " ");
return DebuginfodUrls;
}
Expected<std::string> getDefaultDebuginfodCacheDirectory() {
if (const char *CacheDirectoryEnv = std::getenv("DEBUGINFOD_CACHE_PATH"))
return CacheDirectoryEnv;
SmallString<64> CacheDirectory;
if (!sys::path::cache_directory(CacheDirectory))
return createStringError(
errc::io_error, "Unable to determine appropriate cache directory.");
sys::path::append(CacheDirectory, "llvm-debuginfod", "client");
return std::string(CacheDirectory);
}
std::chrono::milliseconds getDefaultDebuginfodTimeout() {
long Timeout;
const char *DebuginfodTimeoutEnv = std::getenv("DEBUGINFOD_TIMEOUT");
if (DebuginfodTimeoutEnv &&
to_integer(StringRef(DebuginfodTimeoutEnv).trim(), Timeout, 10))
return std::chrono::milliseconds(Timeout * 1000);
return std::chrono::milliseconds(90 * 1000);
}
Expected<std::string> getCachedOrDownloadSource(BuildIDRef ID,
StringRef SourceFilePath) {
SmallString<64> UrlPath;
sys::path::append(UrlPath, sys::path::Style::posix, "buildid",
buildIDToString(ID), "source",
sys::path::convert_to_slash(SourceFilePath));
return getCachedOrDownloadArtifact(uniqueKey(UrlPath), UrlPath);
}
Expected<std::string> getCachedOrDownloadExecutable(BuildIDRef ID) {
SmallString<64> UrlPath;
sys::path::append(UrlPath, sys::path::Style::posix, "buildid",
buildIDToString(ID), "executable");
return getCachedOrDownloadArtifact(uniqueKey(UrlPath), UrlPath);
}
Expected<std::string> getCachedOrDownloadDebuginfo(BuildIDRef ID) {
SmallString<64> UrlPath;
sys::path::append(UrlPath, sys::path::Style::posix, "buildid",
buildIDToString(ID), "debuginfo");
return getCachedOrDownloadArtifact(uniqueKey(UrlPath), UrlPath);
}
Expected<std::string> getCachedOrDownloadArtifact(StringRef UniqueKey,
StringRef UrlPath) {
SmallString<10> CacheDir;
Expected<std::string> CacheDirOrErr = getDefaultDebuginfodCacheDirectory();
if (!CacheDirOrErr)
return CacheDirOrErr.takeError();
CacheDir = *CacheDirOrErr;
Expected<SmallVector<StringRef>> DebuginfodUrlsOrErr =
getDefaultDebuginfodUrls();
if (!DebuginfodUrlsOrErr)
return DebuginfodUrlsOrErr.takeError();
SmallVector<StringRef> &DebuginfodUrls = *DebuginfodUrlsOrErr;
return getCachedOrDownloadArtifact(UniqueKey, UrlPath, CacheDir,
DebuginfodUrls,
getDefaultDebuginfodTimeout());
}
namespace {
class StreamedHTTPResponseHandler : public HTTPResponseHandler {
using CreateStreamFn =
std::function<Expected<std::unique_ptr<CachedFileStream>>()>;
CreateStreamFn CreateStream;
HTTPClient &Client;
std::unique_ptr<CachedFileStream> FileStream;
public:
StreamedHTTPResponseHandler(CreateStreamFn CreateStream, HTTPClient &Client)
: CreateStream(CreateStream), Client(Client) {}
virtual ~StreamedHTTPResponseHandler() = default;
Error handleBodyChunk(StringRef BodyChunk) override;
};
}
Error StreamedHTTPResponseHandler::handleBodyChunk(StringRef BodyChunk) {
if (!FileStream) {
if (Client.responseCode() != 200)
return Error::success();
Expected<std::unique_ptr<CachedFileStream>> FileStreamOrError =
CreateStream();
if (!FileStreamOrError)
return FileStreamOrError.takeError();
FileStream = std::move(*FileStreamOrError);
}
*FileStream->OS << BodyChunk;
return Error::success();
}
Expected<std::string> getCachedOrDownloadArtifact(
StringRef UniqueKey, StringRef UrlPath, StringRef CacheDirectoryPath,
ArrayRef<StringRef> DebuginfodUrls, std::chrono::milliseconds Timeout) {
SmallString<64> AbsCachedArtifactPath;
sys::path::append(AbsCachedArtifactPath, CacheDirectoryPath,
"llvmcache-" + UniqueKey);
Expected<FileCache> CacheOrErr =
localCache("Debuginfod-client", ".debuginfod-client", CacheDirectoryPath);
if (!CacheOrErr)
return CacheOrErr.takeError();
FileCache Cache = *CacheOrErr;
unsigned Task = 0;
Expected<AddStreamFn> CacheAddStreamOrErr = Cache(Task, UniqueKey);
if (!CacheAddStreamOrErr)
return CacheAddStreamOrErr.takeError();
AddStreamFn &CacheAddStream = *CacheAddStreamOrErr;
if (!CacheAddStream)
return std::string(AbsCachedArtifactPath);
if (!HTTPClient::isAvailable())
return createStringError(errc::io_error,
"No working HTTP client is available.");
if (!HTTPClient::IsInitialized)
return createStringError(
errc::io_error,
"A working HTTP client is available, but it is not initialized. To "
"allow Debuginfod to make HTTP requests, call HTTPClient::initialize() "
"at the beginning of main.");
HTTPClient Client;
Client.setTimeout(Timeout);
for (StringRef ServerUrl : DebuginfodUrls) {
SmallString<64> ArtifactUrl;
sys::path::append(ArtifactUrl, sys::path::Style::posix, ServerUrl, UrlPath);
StreamedHTTPResponseHandler Handler([&]() { return CacheAddStream(Task); },
Client);
HTTPRequest Request(ArtifactUrl);
Error Err = Client.perform(Request, Handler);
if (Err)
return std::move(Err);
if (Client.responseCode() != 200)
continue;
return std::string(AbsCachedArtifactPath);
}
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<std::mutex> Guard(QueueMutex);
LogEntryQueue.push(Entry);
}
QueueCondition.notify_one();
}
DebuginfodLogEntry DebuginfodLog::pop() {
{
std::unique_lock<std::mutex> Guard(QueueMutex);
QueueCondition.wait(Guard, [&] { return !LogEntryQueue.empty(); });
}
std::lock_guard<std::mutex> Guard(QueueMutex);
if (!LogEntryQueue.size())
llvm_unreachable("Expected message in the queue.");
DebuginfodLogEntry Entry = LogEntryQueue.front();
LogEntryQueue.pop();
return Entry;
}
DebuginfodCollection::DebuginfodCollection(ArrayRef<StringRef> 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<sys::Mutex> Guard(UpdateMutex);
if (UpdateTimer.isRunning())
UpdateTimer.stopTimer();
UpdateTimer.clear();
for (const std::string &Path : Paths) {
Log.push("Updating binaries at path " + Path);
if (Error Err = findBinaries(Path))
return Err;
}
Log.push("Updated collection");
UpdateTimer.startTimer();
return Error::success();
}
Expected<bool> DebuginfodCollection::updateIfStale() {
if (!UpdateTimer.isRunning())
return false;
UpdateTimer.stopTimer();
double Time = UpdateTimer.getTotalTime().getWallTime();
UpdateTimer.startTimer();
if (Time < 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) {
std::unique_ptr<DWARFContext> 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;
sys::fs::recursive_directory_iterator I(Twine(Path), EC), E;
std::mutex IteratorMutex;
ThreadPoolTaskGroup IteratorGroup(Pool);
for (unsigned WorkerIndex = 0; WorkerIndex < Pool.getThreadCount();
WorkerIndex++) {
IteratorGroup.async([&, this]() -> void {
std::string FilePath;
while (true) {
{
std::lock_guard<std::mutex> Guard(IteratorMutex);
if (I == E || EC)
return;
FilePath = I->path();
I.increment(EC);
}
if (!hasELFMagic(FilePath))
continue;
Expected<object::OwningBinary<object::Binary>> BinOrErr =
object::createBinary(FilePath);
if (!BinOrErr) {
consumeError(BinOrErr.takeError());
continue;
}
object::Binary *Bin = std::move(BinOrErr.get().getBinary());
if (!Bin->isObject())
continue;
object::ELFObjectFileBase *Object =
dyn_cast<object::ELFObjectFileBase>(Bin);
if (!Object)
continue;
Optional<BuildIDRef> ID = symbolize::getBuildID(Object);
if (!ID)
continue;
std::string IDString = buildIDToString(ID.value());
if (isDebugBinary(Object)) {
std::lock_guard<sys::RWMutex> DebugBinariesGuard(DebugBinariesMutex);
DebugBinaries[IDString] = FilePath;
} else {
std::lock_guard<sys::RWMutex> BinariesGuard(BinariesMutex);
Binaries[IDString] = FilePath;
}
}
});
}
IteratorGroup.wait();
std::unique_lock<std::mutex> Guard(IteratorMutex);
if (EC)
return errorCodeToError(EC);
return Error::success();
}
Expected<Optional<std::string>>
DebuginfodCollection::getBinaryPath(BuildIDRef ID) {
Log.push("getting binary path of ID " + buildIDToString(ID));
std::shared_lock<sys::RWMutex> Guard(BinariesMutex);
auto Loc = Binaries.find(buildIDToString(ID));
if (Loc != Binaries.end()) {
std::string Path = Loc->getValue();
return Path;
}
return None;
}
Expected<Optional<std::string>>
DebuginfodCollection::getDebugBinaryPath(BuildIDRef ID) {
Log.push("getting debug binary path of ID " + buildIDToString(ID));
std::shared_lock<sys::RWMutex> Guard(DebugBinariesMutex);
auto Loc = DebugBinaries.find(buildIDToString(ID));
if (Loc != DebugBinaries.end()) {
std::string Path = Loc->getValue();
return Path;
}
return None;
}
Expected<std::string> DebuginfodCollection::findBinaryPath(BuildIDRef ID) {
{
Expected<Optional<std::string>> PathOrErr = getBinaryPath(ID);
if (!PathOrErr)
return PathOrErr.takeError();
Optional<std::string> Path = *PathOrErr;
if (!Path) {
Expected<bool> UpdatedOrErr = updateIfStale();
if (!UpdatedOrErr)
return UpdatedOrErr.takeError();
if (*UpdatedOrErr) {
PathOrErr = getBinaryPath(ID);
if (!PathOrErr)
return PathOrErr.takeError();
Path = *PathOrErr;
}
}
if (Path)
return Path.value();
}
Expected<std::string> PathOrErr = getCachedOrDownloadExecutable(ID);
if (!PathOrErr)
consumeError(PathOrErr.takeError());
return findDebugBinaryPath(ID);
}
Expected<std::string> DebuginfodCollection::findDebugBinaryPath(BuildIDRef ID) {
Expected<Optional<std::string>> PathOrErr = getDebugBinaryPath(ID);
if (!PathOrErr)
return PathOrErr.takeError();
Optional<std::string> Path = *PathOrErr;
if (!Path) {
Expected<bool> UpdatedOrErr = updateIfStale();
if (!UpdatedOrErr)
return UpdatedOrErr.takeError();
if (*UpdatedOrErr) {
PathOrErr = getBinaryPath(ID);
if (!PathOrErr)
return PathOrErr.takeError();
Path = *PathOrErr;
}
}
if (Path)
return Path.value();
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<std::string> 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<std::string> 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);
}));
}
}