Run DCE after a LoopFlatten test to reduce spurious output [nfc]
[llvm-project.git] / llvm / lib / Debuginfod / Debuginfod.cpp
blobfa4c1a0499f059e225854819e78abe9276c0e288
1 //===-- llvm/Debuginfod/Debuginfod.cpp - Debuginfod client library --------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 ///
9 /// \file
10 ///
11 /// This file contains several definitions for the debuginfod client and server.
12 /// For the client, this file defines the fetchInfo function. For the server,
13 /// this file defines the DebuginfodLogEntry and DebuginfodServer structs, as
14 /// well as the DebuginfodLog, DebuginfodCollection classes. The fetchInfo
15 /// function retrieves any of the three supported artifact types: (executable,
16 /// debuginfo, source file) associated with a build-id from debuginfod servers.
17 /// If a source file is to be fetched, its absolute path must be specified in
18 /// the Description argument to fetchInfo. The DebuginfodLogEntry,
19 /// DebuginfodLog, and DebuginfodCollection are used by the DebuginfodServer to
20 /// scan the local filesystem for binaries and serve the debuginfod protocol.
21 ///
22 //===----------------------------------------------------------------------===//
24 #include "llvm/Debuginfod/Debuginfod.h"
25 #include "llvm/ADT/StringExtras.h"
26 #include "llvm/ADT/StringRef.h"
27 #include "llvm/BinaryFormat/Magic.h"
28 #include "llvm/DebugInfo/DWARF/DWARFContext.h"
29 #include "llvm/DebugInfo/Symbolize/Symbolize.h"
30 #include "llvm/Debuginfod/HTTPClient.h"
31 #include "llvm/Object/BuildID.h"
32 #include "llvm/Object/ELFObjectFile.h"
33 #include "llvm/Support/CachePruning.h"
34 #include "llvm/Support/Caching.h"
35 #include "llvm/Support/Errc.h"
36 #include "llvm/Support/Error.h"
37 #include "llvm/Support/FileUtilities.h"
38 #include "llvm/Support/MemoryBuffer.h"
39 #include "llvm/Support/Path.h"
40 #include "llvm/Support/ThreadPool.h"
41 #include "llvm/Support/xxhash.h"
43 #include <atomic>
44 #include <thread>
46 namespace llvm {
48 using llvm::object::BuildIDRef;
50 static std::string uniqueKey(llvm::StringRef S) {
51 return utostr(xxh3_64bits(S));
54 // Returns a binary BuildID as a normalized hex string.
55 // Uses lowercase for compatibility with common debuginfod servers.
56 static std::string buildIDToString(BuildIDRef ID) {
57 return llvm::toHex(ID, /*LowerCase=*/true);
60 bool canUseDebuginfod() {
61 return HTTPClient::isAvailable() && !getDefaultDebuginfodUrls().empty();
64 SmallVector<StringRef> getDefaultDebuginfodUrls() {
65 const char *DebuginfodUrlsEnv = std::getenv("DEBUGINFOD_URLS");
66 if (DebuginfodUrlsEnv == nullptr)
67 return SmallVector<StringRef>();
69 SmallVector<StringRef> DebuginfodUrls;
70 StringRef(DebuginfodUrlsEnv).split(DebuginfodUrls, " ");
71 return DebuginfodUrls;
74 /// Finds a default local file caching directory for the debuginfod client,
75 /// first checking DEBUGINFOD_CACHE_PATH.
76 Expected<std::string> getDefaultDebuginfodCacheDirectory() {
77 if (const char *CacheDirectoryEnv = std::getenv("DEBUGINFOD_CACHE_PATH"))
78 return CacheDirectoryEnv;
80 SmallString<64> CacheDirectory;
81 if (!sys::path::cache_directory(CacheDirectory))
82 return createStringError(
83 errc::io_error, "Unable to determine appropriate cache directory.");
84 sys::path::append(CacheDirectory, "llvm-debuginfod", "client");
85 return std::string(CacheDirectory);
88 std::chrono::milliseconds getDefaultDebuginfodTimeout() {
89 long Timeout;
90 const char *DebuginfodTimeoutEnv = std::getenv("DEBUGINFOD_TIMEOUT");
91 if (DebuginfodTimeoutEnv &&
92 to_integer(StringRef(DebuginfodTimeoutEnv).trim(), Timeout, 10))
93 return std::chrono::milliseconds(Timeout * 1000);
95 return std::chrono::milliseconds(90 * 1000);
98 /// The following functions fetch a debuginfod artifact to a file in a local
99 /// cache and return the cached file path. They first search the local cache,
100 /// followed by the debuginfod servers.
102 Expected<std::string> getCachedOrDownloadSource(BuildIDRef ID,
103 StringRef SourceFilePath) {
104 SmallString<64> UrlPath;
105 sys::path::append(UrlPath, sys::path::Style::posix, "buildid",
106 buildIDToString(ID), "source",
107 sys::path::convert_to_slash(SourceFilePath));
108 return getCachedOrDownloadArtifact(uniqueKey(UrlPath), UrlPath);
111 Expected<std::string> getCachedOrDownloadExecutable(BuildIDRef ID) {
112 SmallString<64> UrlPath;
113 sys::path::append(UrlPath, sys::path::Style::posix, "buildid",
114 buildIDToString(ID), "executable");
115 return getCachedOrDownloadArtifact(uniqueKey(UrlPath), UrlPath);
118 Expected<std::string> getCachedOrDownloadDebuginfo(BuildIDRef ID) {
119 SmallString<64> UrlPath;
120 sys::path::append(UrlPath, sys::path::Style::posix, "buildid",
121 buildIDToString(ID), "debuginfo");
122 return getCachedOrDownloadArtifact(uniqueKey(UrlPath), UrlPath);
125 // General fetching function.
126 Expected<std::string> getCachedOrDownloadArtifact(StringRef UniqueKey,
127 StringRef UrlPath) {
128 SmallString<10> CacheDir;
130 Expected<std::string> CacheDirOrErr = getDefaultDebuginfodCacheDirectory();
131 if (!CacheDirOrErr)
132 return CacheDirOrErr.takeError();
133 CacheDir = *CacheDirOrErr;
135 return getCachedOrDownloadArtifact(UniqueKey, UrlPath, CacheDir,
136 getDefaultDebuginfodUrls(),
137 getDefaultDebuginfodTimeout());
140 namespace {
142 /// A simple handler which streams the returned data to a cache file. The cache
143 /// file is only created if a 200 OK status is observed.
144 class StreamedHTTPResponseHandler : public HTTPResponseHandler {
145 using CreateStreamFn =
146 std::function<Expected<std::unique_ptr<CachedFileStream>>()>;
147 CreateStreamFn CreateStream;
148 HTTPClient &Client;
149 std::unique_ptr<CachedFileStream> FileStream;
151 public:
152 StreamedHTTPResponseHandler(CreateStreamFn CreateStream, HTTPClient &Client)
153 : CreateStream(CreateStream), Client(Client) {}
154 virtual ~StreamedHTTPResponseHandler() = default;
156 Error handleBodyChunk(StringRef BodyChunk) override;
159 } // namespace
161 Error StreamedHTTPResponseHandler::handleBodyChunk(StringRef BodyChunk) {
162 if (!FileStream) {
163 unsigned Code = Client.responseCode();
164 if (Code && Code != 200)
165 return Error::success();
166 Expected<std::unique_ptr<CachedFileStream>> FileStreamOrError =
167 CreateStream();
168 if (!FileStreamOrError)
169 return FileStreamOrError.takeError();
170 FileStream = std::move(*FileStreamOrError);
172 *FileStream->OS << BodyChunk;
173 return Error::success();
176 // An over-accepting simplification of the HTTP RFC 7230 spec.
177 static bool isHeader(StringRef S) {
178 StringRef Name;
179 StringRef Value;
180 std::tie(Name, Value) = S.split(':');
181 if (Name.empty() || Value.empty())
182 return false;
183 return all_of(Name, [](char C) { return llvm::isPrint(C) && C != ' '; }) &&
184 all_of(Value, [](char C) { return llvm::isPrint(C) || C == '\t'; });
187 static SmallVector<std::string, 0> getHeaders() {
188 const char *Filename = getenv("DEBUGINFOD_HEADERS_FILE");
189 if (!Filename)
190 return {};
191 ErrorOr<std::unique_ptr<MemoryBuffer>> HeadersFile =
192 MemoryBuffer::getFile(Filename, /*IsText=*/true);
193 if (!HeadersFile)
194 return {};
196 SmallVector<std::string, 0> Headers;
197 uint64_t LineNumber = 0;
198 for (StringRef Line : llvm::split((*HeadersFile)->getBuffer(), '\n')) {
199 LineNumber++;
200 if (!Line.empty() && Line.back() == '\r')
201 Line = Line.drop_back();
202 if (!isHeader(Line)) {
203 if (!all_of(Line, llvm::isSpace))
204 WithColor::warning()
205 << "could not parse debuginfod header: " << Filename << ':'
206 << LineNumber << '\n';
207 continue;
209 Headers.emplace_back(Line);
211 return Headers;
214 Expected<std::string> getCachedOrDownloadArtifact(
215 StringRef UniqueKey, StringRef UrlPath, StringRef CacheDirectoryPath,
216 ArrayRef<StringRef> DebuginfodUrls, std::chrono::milliseconds Timeout) {
217 SmallString<64> AbsCachedArtifactPath;
218 sys::path::append(AbsCachedArtifactPath, CacheDirectoryPath,
219 "llvmcache-" + UniqueKey);
221 Expected<FileCache> CacheOrErr =
222 localCache("Debuginfod-client", ".debuginfod-client", CacheDirectoryPath);
223 if (!CacheOrErr)
224 return CacheOrErr.takeError();
226 FileCache Cache = *CacheOrErr;
227 // We choose an arbitrary Task parameter as we do not make use of it.
228 unsigned Task = 0;
229 Expected<AddStreamFn> CacheAddStreamOrErr = Cache(Task, UniqueKey, "");
230 if (!CacheAddStreamOrErr)
231 return CacheAddStreamOrErr.takeError();
232 AddStreamFn &CacheAddStream = *CacheAddStreamOrErr;
233 if (!CacheAddStream)
234 return std::string(AbsCachedArtifactPath);
235 // The artifact was not found in the local cache, query the debuginfod
236 // servers.
237 if (!HTTPClient::isAvailable())
238 return createStringError(errc::io_error,
239 "No working HTTP client is available.");
241 if (!HTTPClient::IsInitialized)
242 return createStringError(
243 errc::io_error,
244 "A working HTTP client is available, but it is not initialized. To "
245 "allow Debuginfod to make HTTP requests, call HTTPClient::initialize() "
246 "at the beginning of main.");
248 HTTPClient Client;
249 Client.setTimeout(Timeout);
250 for (StringRef ServerUrl : DebuginfodUrls) {
251 SmallString<64> ArtifactUrl;
252 sys::path::append(ArtifactUrl, sys::path::Style::posix, ServerUrl, UrlPath);
254 // Perform the HTTP request and if successful, write the response body to
255 // the cache.
257 StreamedHTTPResponseHandler Handler(
258 [&]() { return CacheAddStream(Task, ""); }, Client);
259 HTTPRequest Request(ArtifactUrl);
260 Request.Headers = getHeaders();
261 Error Err = Client.perform(Request, Handler);
262 if (Err)
263 return std::move(Err);
265 unsigned Code = Client.responseCode();
266 if (Code && Code != 200)
267 continue;
270 Expected<CachePruningPolicy> PruningPolicyOrErr =
271 parseCachePruningPolicy(std::getenv("DEBUGINFOD_CACHE_POLICY"));
272 if (!PruningPolicyOrErr)
273 return PruningPolicyOrErr.takeError();
274 pruneCache(CacheDirectoryPath, *PruningPolicyOrErr);
276 // Return the path to the artifact on disk.
277 return std::string(AbsCachedArtifactPath);
280 return createStringError(errc::argument_out_of_domain, "build id not found");
283 DebuginfodLogEntry::DebuginfodLogEntry(const Twine &Message)
284 : Message(Message.str()) {}
286 void DebuginfodLog::push(const Twine &Message) {
287 push(DebuginfodLogEntry(Message));
290 void DebuginfodLog::push(DebuginfodLogEntry Entry) {
292 std::lock_guard<std::mutex> Guard(QueueMutex);
293 LogEntryQueue.push(Entry);
295 QueueCondition.notify_one();
298 DebuginfodLogEntry DebuginfodLog::pop() {
300 std::unique_lock<std::mutex> Guard(QueueMutex);
301 // Wait for messages to be pushed into the queue.
302 QueueCondition.wait(Guard, [&] { return !LogEntryQueue.empty(); });
304 std::lock_guard<std::mutex> Guard(QueueMutex);
305 if (!LogEntryQueue.size())
306 llvm_unreachable("Expected message in the queue.");
308 DebuginfodLogEntry Entry = LogEntryQueue.front();
309 LogEntryQueue.pop();
310 return Entry;
313 DebuginfodCollection::DebuginfodCollection(ArrayRef<StringRef> PathsRef,
314 DebuginfodLog &Log, ThreadPool &Pool,
315 double MinInterval)
316 : Log(Log), Pool(Pool), MinInterval(MinInterval) {
317 for (StringRef Path : PathsRef)
318 Paths.push_back(Path.str());
321 Error DebuginfodCollection::update() {
322 std::lock_guard<sys::Mutex> Guard(UpdateMutex);
323 if (UpdateTimer.isRunning())
324 UpdateTimer.stopTimer();
325 UpdateTimer.clear();
326 for (const std::string &Path : Paths) {
327 Log.push("Updating binaries at path " + Path);
328 if (Error Err = findBinaries(Path))
329 return Err;
331 Log.push("Updated collection");
332 UpdateTimer.startTimer();
333 return Error::success();
336 Expected<bool> DebuginfodCollection::updateIfStale() {
337 if (!UpdateTimer.isRunning())
338 return false;
339 UpdateTimer.stopTimer();
340 double Time = UpdateTimer.getTotalTime().getWallTime();
341 UpdateTimer.startTimer();
342 if (Time < MinInterval)
343 return false;
344 if (Error Err = update())
345 return std::move(Err);
346 return true;
349 Error DebuginfodCollection::updateForever(std::chrono::milliseconds Interval) {
350 while (true) {
351 if (Error Err = update())
352 return Err;
353 std::this_thread::sleep_for(Interval);
355 llvm_unreachable("updateForever loop should never end");
358 static bool hasELFMagic(StringRef FilePath) {
359 file_magic Type;
360 std::error_code EC = identify_magic(FilePath, Type);
361 if (EC)
362 return false;
363 switch (Type) {
364 case file_magic::elf:
365 case file_magic::elf_relocatable:
366 case file_magic::elf_executable:
367 case file_magic::elf_shared_object:
368 case file_magic::elf_core:
369 return true;
370 default:
371 return false;
375 Error DebuginfodCollection::findBinaries(StringRef Path) {
376 std::error_code EC;
377 sys::fs::recursive_directory_iterator I(Twine(Path), EC), E;
378 std::mutex IteratorMutex;
379 ThreadPoolTaskGroup IteratorGroup(Pool);
380 for (unsigned WorkerIndex = 0; WorkerIndex < Pool.getThreadCount();
381 WorkerIndex++) {
382 IteratorGroup.async([&, this]() -> void {
383 std::string FilePath;
384 while (true) {
386 // Check if iteration is over or there is an error during iteration
387 std::lock_guard<std::mutex> Guard(IteratorMutex);
388 if (I == E || EC)
389 return;
390 // Grab a file path from the directory iterator and advance the
391 // iterator.
392 FilePath = I->path();
393 I.increment(EC);
396 // Inspect the file at this path to determine if it is debuginfo.
397 if (!hasELFMagic(FilePath))
398 continue;
400 Expected<object::OwningBinary<object::Binary>> BinOrErr =
401 object::createBinary(FilePath);
403 if (!BinOrErr) {
404 consumeError(BinOrErr.takeError());
405 continue;
407 object::Binary *Bin = std::move(BinOrErr.get().getBinary());
408 if (!Bin->isObject())
409 continue;
411 // TODO: Support non-ELF binaries
412 object::ELFObjectFileBase *Object =
413 dyn_cast<object::ELFObjectFileBase>(Bin);
414 if (!Object)
415 continue;
417 BuildIDRef ID = getBuildID(Object);
418 if (ID.empty())
419 continue;
421 std::string IDString = buildIDToString(ID);
422 if (Object->hasDebugInfo()) {
423 std::lock_guard<sys::RWMutex> DebugBinariesGuard(DebugBinariesMutex);
424 (void)DebugBinaries.try_emplace(IDString, std::move(FilePath));
425 } else {
426 std::lock_guard<sys::RWMutex> BinariesGuard(BinariesMutex);
427 (void)Binaries.try_emplace(IDString, std::move(FilePath));
432 IteratorGroup.wait();
433 std::unique_lock<std::mutex> Guard(IteratorMutex);
434 if (EC)
435 return errorCodeToError(EC);
436 return Error::success();
439 Expected<std::optional<std::string>>
440 DebuginfodCollection::getBinaryPath(BuildIDRef ID) {
441 Log.push("getting binary path of ID " + buildIDToString(ID));
442 std::shared_lock<sys::RWMutex> Guard(BinariesMutex);
443 auto Loc = Binaries.find(buildIDToString(ID));
444 if (Loc != Binaries.end()) {
445 std::string Path = Loc->getValue();
446 return Path;
448 return std::nullopt;
451 Expected<std::optional<std::string>>
452 DebuginfodCollection::getDebugBinaryPath(BuildIDRef ID) {
453 Log.push("getting debug binary path of ID " + buildIDToString(ID));
454 std::shared_lock<sys::RWMutex> Guard(DebugBinariesMutex);
455 auto Loc = DebugBinaries.find(buildIDToString(ID));
456 if (Loc != DebugBinaries.end()) {
457 std::string Path = Loc->getValue();
458 return Path;
460 return std::nullopt;
463 Expected<std::string> DebuginfodCollection::findBinaryPath(BuildIDRef ID) {
465 // Check collection; perform on-demand update if stale.
466 Expected<std::optional<std::string>> PathOrErr = getBinaryPath(ID);
467 if (!PathOrErr)
468 return PathOrErr.takeError();
469 std::optional<std::string> Path = *PathOrErr;
470 if (!Path) {
471 Expected<bool> UpdatedOrErr = updateIfStale();
472 if (!UpdatedOrErr)
473 return UpdatedOrErr.takeError();
474 if (*UpdatedOrErr) {
475 // Try once more.
476 PathOrErr = getBinaryPath(ID);
477 if (!PathOrErr)
478 return PathOrErr.takeError();
479 Path = *PathOrErr;
482 if (Path)
483 return *Path;
486 // Try federation.
487 Expected<std::string> PathOrErr = getCachedOrDownloadExecutable(ID);
488 if (!PathOrErr)
489 consumeError(PathOrErr.takeError());
491 // Fall back to debug binary.
492 return findDebugBinaryPath(ID);
495 Expected<std::string> DebuginfodCollection::findDebugBinaryPath(BuildIDRef ID) {
496 // Check collection; perform on-demand update if stale.
497 Expected<std::optional<std::string>> PathOrErr = getDebugBinaryPath(ID);
498 if (!PathOrErr)
499 return PathOrErr.takeError();
500 std::optional<std::string> Path = *PathOrErr;
501 if (!Path) {
502 Expected<bool> UpdatedOrErr = updateIfStale();
503 if (!UpdatedOrErr)
504 return UpdatedOrErr.takeError();
505 if (*UpdatedOrErr) {
506 // Try once more.
507 PathOrErr = getBinaryPath(ID);
508 if (!PathOrErr)
509 return PathOrErr.takeError();
510 Path = *PathOrErr;
513 if (Path)
514 return *Path;
516 // Try federation.
517 return getCachedOrDownloadDebuginfo(ID);
520 DebuginfodServer::DebuginfodServer(DebuginfodLog &Log,
521 DebuginfodCollection &Collection)
522 : Log(Log), Collection(Collection) {
523 cantFail(
524 Server.get(R"(/buildid/(.*)/debuginfo)", [&](HTTPServerRequest Request) {
525 Log.push("GET " + Request.UrlPath);
526 std::string IDString;
527 if (!tryGetFromHex(Request.UrlPathMatches[0], IDString)) {
528 Request.setResponse(
529 {404, "text/plain", "Build ID is not a hex string\n"});
530 return;
532 object::BuildID ID(IDString.begin(), IDString.end());
533 Expected<std::string> PathOrErr = Collection.findDebugBinaryPath(ID);
534 if (Error Err = PathOrErr.takeError()) {
535 consumeError(std::move(Err));
536 Request.setResponse({404, "text/plain", "Build ID not found\n"});
537 return;
539 streamFile(Request, *PathOrErr);
540 }));
541 cantFail(
542 Server.get(R"(/buildid/(.*)/executable)", [&](HTTPServerRequest Request) {
543 Log.push("GET " + Request.UrlPath);
544 std::string IDString;
545 if (!tryGetFromHex(Request.UrlPathMatches[0], IDString)) {
546 Request.setResponse(
547 {404, "text/plain", "Build ID is not a hex string\n"});
548 return;
550 object::BuildID ID(IDString.begin(), IDString.end());
551 Expected<std::string> PathOrErr = Collection.findBinaryPath(ID);
552 if (Error Err = PathOrErr.takeError()) {
553 consumeError(std::move(Err));
554 Request.setResponse({404, "text/plain", "Build ID not found\n"});
555 return;
557 streamFile(Request, *PathOrErr);
558 }));
561 } // namespace llvm