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//===----------------------------------------------------------------------===//
23
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/HTTP/HTTPClient.h"
31#include "llvm/HTTP/StreamedHTTPResponseHandler.h"
32#include "llvm/Object/BuildID.h"
33#include "llvm/Object/ELFObjectFile.h"
34#include "llvm/Support/CachePruning.h"
35#include "llvm/Support/Caching.h"
36#include "llvm/Support/Errc.h"
37#include "llvm/Support/Error.h"
38#include "llvm/Support/FileUtilities.h"
39#include "llvm/Support/MemoryBuffer.h"
40#include "llvm/Support/Path.h"
41#include "llvm/Support/ThreadPool.h"
42
43#include <optional>
44#include <thread>
45
46namespace llvm {
47
48using llvm::object::BuildIDRef;
49
50namespace {
51std::optional<SmallVector<StringRef>> DebuginfodUrls;
52// Many Readers/Single Writer lock protecting the global debuginfod URL list.
53llvm::sys::RWMutex UrlsMutex;
54} // namespace
55
56std::string getDebuginfodCacheKey(llvm::StringRef S) {
57 return utostr(X: xxh3_64bits(data: S));
58}
59
60// Returns a binary BuildID as a normalized hex string.
61// Uses lowercase for compatibility with common debuginfod servers.
62static std::string buildIDToString(BuildIDRef ID) {
63 return llvm::toHex(Input: ID, /*LowerCase=*/true);
64}
65
66bool canUseDebuginfod() {
67 return HTTPClient::isAvailable() && !getDefaultDebuginfodUrls().empty();
68}
69
70SmallVector<StringRef> getDefaultDebuginfodUrls() {
71 std::shared_lock<llvm::sys::RWMutex> ReadGuard(UrlsMutex);
72 if (!DebuginfodUrls) {
73 // Only read from the environment variable if the user hasn't already
74 // set the value.
75 ReadGuard.unlock();
76 std::unique_lock<llvm::sys::RWMutex> WriteGuard(UrlsMutex);
77 DebuginfodUrls = SmallVector<StringRef>();
78 if (const char *DebuginfodUrlsEnv = std::getenv(name: "DEBUGINFOD_URLS")) {
79 StringRef(DebuginfodUrlsEnv)
80 .split(A&: DebuginfodUrls.value(), Separator: " ", MaxSplit: -1, KeepEmpty: false);
81 }
82 WriteGuard.unlock();
83 ReadGuard.lock();
84 }
85 return DebuginfodUrls.value();
86}
87
88// Set the default debuginfod URL list, override the environment variable.
89void setDefaultDebuginfodUrls(const SmallVector<StringRef> &URLs) {
90 std::unique_lock<llvm::sys::RWMutex> WriteGuard(UrlsMutex);
91 DebuginfodUrls = URLs;
92}
93
94/// Finds a default local file caching directory for the debuginfod client,
95/// first checking DEBUGINFOD_CACHE_PATH.
96Expected<std::string> getDefaultDebuginfodCacheDirectory() {
97 if (const char *CacheDirectoryEnv = std::getenv(name: "DEBUGINFOD_CACHE_PATH"))
98 return CacheDirectoryEnv;
99
100 SmallString<64> CacheDirectory;
101 if (!sys::path::cache_directory(result&: CacheDirectory))
102 return createStringError(
103 EC: errc::io_error, S: "Unable to determine appropriate cache directory.");
104 sys::path::append(path&: CacheDirectory, a: "llvm-debuginfod", b: "client");
105 return std::string(CacheDirectory);
106}
107
108std::chrono::milliseconds getDefaultDebuginfodTimeout() {
109 long Timeout;
110 const char *DebuginfodTimeoutEnv = std::getenv(name: "DEBUGINFOD_TIMEOUT");
111 if (DebuginfodTimeoutEnv &&
112 to_integer(S: StringRef(DebuginfodTimeoutEnv).trim(), Num&: Timeout, Base: 10))
113 return std::chrono::milliseconds(Timeout * 1000);
114
115 return std::chrono::milliseconds(90 * 1000);
116}
117
118/// The following functions fetch a debuginfod artifact to a file in a local
119/// cache and return the cached file path. They first search the local cache,
120/// followed by the debuginfod servers.
121
122std::string getDebuginfodSourceUrlPath(BuildIDRef ID,
123 StringRef SourceFilePath) {
124 SmallString<64> UrlPath;
125 sys::path::append(path&: UrlPath, style: sys::path::Style::posix, a: "buildid",
126 b: buildIDToString(ID), c: "source",
127 d: sys::path::convert_to_slash(path: SourceFilePath));
128 return std::string(UrlPath);
129}
130
131Expected<std::string> getCachedOrDownloadSource(BuildIDRef ID,
132 StringRef SourceFilePath) {
133 std::string UrlPath = getDebuginfodSourceUrlPath(ID, SourceFilePath);
134 return getCachedOrDownloadArtifact(UniqueKey: getDebuginfodCacheKey(S: UrlPath), UrlPath);
135}
136
137std::string getDebuginfodExecutableUrlPath(BuildIDRef ID) {
138 SmallString<64> UrlPath;
139 sys::path::append(path&: UrlPath, style: sys::path::Style::posix, a: "buildid",
140 b: buildIDToString(ID), c: "executable");
141 return std::string(UrlPath);
142}
143
144Expected<std::string> getCachedOrDownloadExecutable(BuildIDRef ID) {
145 std::string UrlPath = getDebuginfodExecutableUrlPath(ID);
146 return getCachedOrDownloadArtifact(UniqueKey: getDebuginfodCacheKey(S: UrlPath), UrlPath);
147}
148
149std::string getDebuginfodDebuginfoUrlPath(BuildIDRef ID) {
150 SmallString<64> UrlPath;
151 sys::path::append(path&: UrlPath, style: sys::path::Style::posix, a: "buildid",
152 b: buildIDToString(ID), c: "debuginfo");
153 return std::string(UrlPath);
154}
155
156Expected<std::string> getCachedOrDownloadDebuginfo(BuildIDRef ID) {
157 std::string UrlPath = getDebuginfodDebuginfoUrlPath(ID);
158 return getCachedOrDownloadArtifact(UniqueKey: getDebuginfodCacheKey(S: UrlPath), UrlPath);
159}
160
161// General fetching function.
162Expected<std::string> getCachedOrDownloadArtifact(StringRef UniqueKey,
163 StringRef UrlPath) {
164 SmallString<10> CacheDir;
165
166 Expected<std::string> CacheDirOrErr = getDefaultDebuginfodCacheDirectory();
167 if (!CacheDirOrErr)
168 return CacheDirOrErr.takeError();
169 CacheDir = *CacheDirOrErr;
170
171 return getCachedOrDownloadArtifact(UniqueKey, UrlPath, CacheDirectoryPath: CacheDir,
172 DebuginfodUrls: getDefaultDebuginfodUrls(),
173 Timeout: getDefaultDebuginfodTimeout());
174}
175
176// An over-accepting simplification of the HTTP RFC 7230 spec.
177static bool isHeader(StringRef S) {
178 StringRef Name;
179 StringRef Value;
180 std::tie(args&: Name, args&: Value) = S.split(Separator: ':');
181 if (Name.empty() || Value.empty())
182 return false;
183 return all_of(Range&: Name, P: [](char C) { return llvm::isPrint(C) && C != ' '; }) &&
184 all_of(Range&: Value, P: [](char C) { return llvm::isPrint(C) || C == '\t'; });
185}
186
187static SmallVector<std::string, 0> getHeaders() {
188 const char *Filename = getenv(name: "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 {};
195
196 SmallVector<std::string, 0> Headers;
197 uint64_t LineNumber = 0;
198 for (StringRef Line : llvm::split(Str: (*HeadersFile)->getBuffer(), Separator: '\n')) {
199 LineNumber++;
200 Line.consume_back(Suffix: "\r");
201 if (!isHeader(S: Line)) {
202 if (!all_of(Range&: Line, P: llvm::isSpace))
203 WithColor::warning()
204 << "could not parse debuginfod header: " << Filename << ':'
205 << LineNumber << '\n';
206 continue;
207 }
208 Headers.emplace_back(Args&: Line);
209 }
210 return Headers;
211}
212
213Expected<std::string> getCachedOrDownloadArtifact(
214 StringRef UniqueKey, StringRef UrlPath, StringRef CacheDirectoryPath,
215 ArrayRef<StringRef> DebuginfodUrls, std::chrono::milliseconds Timeout) {
216 SmallString<64> AbsCachedArtifactPath;
217 sys::path::append(path&: AbsCachedArtifactPath, a: CacheDirectoryPath,
218 b: "llvmcache-" + UniqueKey);
219
220 Expected<FileCache> CacheOrErr =
221 localCache(CacheNameRef: "Debuginfod-client", TempFilePrefixRef: ".debuginfod-client", CacheDirectoryPathRef: CacheDirectoryPath);
222 if (!CacheOrErr)
223 return CacheOrErr.takeError();
224
225 FileCache Cache = *CacheOrErr;
226 // We choose an arbitrary Task parameter as we do not make use of it.
227 unsigned Task = 0;
228 Expected<AddStreamFn> CacheAddStreamOrErr = Cache(Task, UniqueKey, "");
229 if (!CacheAddStreamOrErr)
230 return CacheAddStreamOrErr.takeError();
231 AddStreamFn &CacheAddStream = *CacheAddStreamOrErr;
232 if (!CacheAddStream)
233 return std::string(AbsCachedArtifactPath);
234 // The artifact was not found in the local cache, query the debuginfod
235 // servers.
236 if (!HTTPClient::isAvailable())
237 return createStringError(EC: errc::io_error,
238 S: "No working HTTP client is available.");
239
240 if (!HTTPClient::IsInitialized)
241 return createStringError(
242 EC: errc::io_error,
243 S: "A working HTTP client is available, but it is not initialized. To "
244 "allow Debuginfod to make HTTP requests, call HTTPClient::initialize() "
245 "at the beginning of main.");
246
247 HTTPClient Client;
248 Client.setTimeout(Timeout);
249 for (StringRef ServerUrl : DebuginfodUrls) {
250 SmallString<64> ArtifactUrl;
251 sys::path::append(path&: ArtifactUrl, style: sys::path::Style::posix, a: ServerUrl, b: UrlPath);
252
253 // Perform the HTTP request and if successful, write the response body to
254 // the cache.
255 {
256 StreamedHTTPResponseHandler Handler(
257 [&]() { return CacheAddStream(Task, ""); }, Client);
258 HTTPRequest Request(ArtifactUrl);
259 Request.Headers = getHeaders();
260 Error Err = Client.perform(Request, Handler);
261 if (Err)
262 return std::move(Err);
263 if ((Err = Handler.commit()))
264 return std::move(Err);
265
266 unsigned Code = Client.responseCode();
267 if (Code && Code != 200)
268 continue;
269 }
270
271 Expected<CachePruningPolicy> PruningPolicyOrErr =
272 parseCachePruningPolicy(PolicyStr: std::getenv(name: "DEBUGINFOD_CACHE_POLICY"));
273 if (!PruningPolicyOrErr)
274 return PruningPolicyOrErr.takeError();
275
276 Expected<bool> PrunedOrErr =
277 pruneCache(Path: CacheDirectoryPath, Policy: *PruningPolicyOrErr);
278 // Log the error but continue execution: failure to prune the cache is not
279 // fatal.
280 if (!PrunedOrErr)
281 logAllUnhandledErrors(E: PrunedOrErr.takeError(), OS&: WithColor::warning());
282
283 // Return the path to the artifact on disk.
284 return std::string(AbsCachedArtifactPath);
285 }
286
287 return createStringError(EC: errc::argument_out_of_domain, S: "build id not found");
288}
289
290DebuginfodLogEntry::DebuginfodLogEntry(const Twine &Message)
291 : Message(Message.str()) {}
292
293void DebuginfodLog::push(const Twine &Message) {
294 push(Entry: DebuginfodLogEntry(Message));
295}
296
297void DebuginfodLog::push(DebuginfodLogEntry Entry) {
298 {
299 std::lock_guard<std::mutex> Guard(QueueMutex);
300 LogEntryQueue.push(x: Entry);
301 }
302 QueueCondition.notify_one();
303}
304
305DebuginfodLogEntry DebuginfodLog::pop() {
306 {
307 std::unique_lock<std::mutex> Guard(QueueMutex);
308 // Wait for messages to be pushed into the queue.
309 QueueCondition.wait(lock&: Guard, p: [&] { return !LogEntryQueue.empty(); });
310 }
311 std::lock_guard<std::mutex> Guard(QueueMutex);
312 if (!LogEntryQueue.size())
313 llvm_unreachable("Expected message in the queue.");
314
315 DebuginfodLogEntry Entry = LogEntryQueue.front();
316 LogEntryQueue.pop();
317 return Entry;
318}
319
320DebuginfodCollection::DebuginfodCollection(ArrayRef<StringRef> PathsRef,
321 DebuginfodLog &Log,
322 ThreadPoolInterface &Pool,
323 double MinInterval)
324 : Log(Log), Pool(Pool), MinInterval(MinInterval) {
325 for (StringRef Path : PathsRef)
326 Paths.push_back(Elt: Path.str());
327}
328
329Error DebuginfodCollection::update() {
330 std::lock_guard<sys::Mutex> Guard(UpdateMutex);
331 if (UpdateTimer.isRunning())
332 UpdateTimer.stopTimer();
333 UpdateTimer.clear();
334 for (const std::string &Path : Paths) {
335 Log.push(Message: "Updating binaries at path " + Path);
336 if (Error Err = findBinaries(Path))
337 return Err;
338 }
339 Log.push(Message: "Updated collection");
340 UpdateTimer.startTimer();
341 return Error::success();
342}
343
344Expected<bool> DebuginfodCollection::updateIfStale() {
345 if (!UpdateTimer.isRunning())
346 return false;
347 UpdateTimer.stopTimer();
348 double Time = UpdateTimer.getTotalTime().getWallTime();
349 UpdateTimer.startTimer();
350 if (Time < MinInterval)
351 return false;
352 if (Error Err = update())
353 return std::move(Err);
354 return true;
355}
356
357Error DebuginfodCollection::updateForever(std::chrono::milliseconds Interval) {
358 while (true) {
359 if (Error Err = update())
360 return Err;
361 std::this_thread::sleep_for(rtime: Interval);
362 }
363 llvm_unreachable("updateForever loop should never end");
364}
365
366static bool hasELFMagic(StringRef FilePath) {
367 file_magic Type;
368 std::error_code EC = identify_magic(path: FilePath, result&: Type);
369 if (EC)
370 return false;
371 switch (Type) {
372 case file_magic::elf:
373 case file_magic::elf_relocatable:
374 case file_magic::elf_executable:
375 case file_magic::elf_shared_object:
376 case file_magic::elf_core:
377 return true;
378 default:
379 return false;
380 }
381}
382
383Error DebuginfodCollection::findBinaries(StringRef Path) {
384 std::error_code EC;
385 sys::fs::recursive_directory_iterator I(Twine(Path), EC), E;
386 std::mutex IteratorMutex;
387 ThreadPoolTaskGroup IteratorGroup(Pool);
388 for (unsigned WorkerIndex = 0; WorkerIndex < Pool.getMaxConcurrency();
389 WorkerIndex++) {
390 IteratorGroup.async(F: [&, this]() -> void {
391 std::string FilePath;
392 while (true) {
393 {
394 // Check if iteration is over or there is an error during iteration
395 std::lock_guard<std::mutex> Guard(IteratorMutex);
396 if (I == E || EC)
397 return;
398 // Grab a file path from the directory iterator and advance the
399 // iterator.
400 FilePath = I->path();
401 I.increment(ec&: EC);
402 }
403
404 // Inspect the file at this path to determine if it is debuginfo.
405 if (!hasELFMagic(FilePath))
406 continue;
407
408 Expected<object::OwningBinary<object::Binary>> BinOrErr =
409 object::createBinary(Path: FilePath);
410
411 if (!BinOrErr) {
412 consumeError(Err: BinOrErr.takeError());
413 continue;
414 }
415 object::Binary *Bin = std::move(BinOrErr.get().getBinary());
416 if (!Bin->isObject())
417 continue;
418
419 // TODO: Support non-ELF binaries
420 object::ELFObjectFileBase *Object =
421 dyn_cast<object::ELFObjectFileBase>(Val: Bin);
422 if (!Object)
423 continue;
424
425 BuildIDRef ID = getBuildID(Obj: Object);
426 if (ID.empty())
427 continue;
428
429 std::string IDString = buildIDToString(ID);
430 if (Object->hasDebugInfo()) {
431 std::lock_guard<sys::RWMutex> DebugBinariesGuard(DebugBinariesMutex);
432 (void)DebugBinaries.try_emplace(Key: IDString, Args: std::move(FilePath));
433 } else {
434 std::lock_guard<sys::RWMutex> BinariesGuard(BinariesMutex);
435 (void)Binaries.try_emplace(Key: IDString, Args: std::move(FilePath));
436 }
437 }
438 });
439 }
440 IteratorGroup.wait();
441 std::unique_lock<std::mutex> Guard(IteratorMutex);
442 if (EC)
443 return errorCodeToError(EC);
444 return Error::success();
445}
446
447Expected<std::optional<std::string>>
448DebuginfodCollection::getBinaryPath(BuildIDRef ID) {
449 Log.push(Message: "getting binary path of ID " + buildIDToString(ID));
450 std::shared_lock<sys::RWMutex> Guard(BinariesMutex);
451 auto Loc = Binaries.find(Key: buildIDToString(ID));
452 if (Loc != Binaries.end()) {
453 std::string Path = Loc->getValue();
454 return Path;
455 }
456 return std::nullopt;
457}
458
459Expected<std::optional<std::string>>
460DebuginfodCollection::getDebugBinaryPath(BuildIDRef ID) {
461 Log.push(Message: "getting debug binary path of ID " + buildIDToString(ID));
462 std::shared_lock<sys::RWMutex> Guard(DebugBinariesMutex);
463 auto Loc = DebugBinaries.find(Key: buildIDToString(ID));
464 if (Loc != DebugBinaries.end()) {
465 std::string Path = Loc->getValue();
466 return Path;
467 }
468 return std::nullopt;
469}
470
471Expected<std::string> DebuginfodCollection::findBinaryPath(BuildIDRef ID) {
472 {
473 // Check collection; perform on-demand update if stale.
474 Expected<std::optional<std::string>> PathOrErr = getBinaryPath(ID);
475 if (!PathOrErr)
476 return PathOrErr.takeError();
477 std::optional<std::string> Path = *PathOrErr;
478 if (!Path) {
479 Expected<bool> UpdatedOrErr = updateIfStale();
480 if (!UpdatedOrErr)
481 return UpdatedOrErr.takeError();
482 if (*UpdatedOrErr) {
483 // Try once more.
484 PathOrErr = getBinaryPath(ID);
485 if (!PathOrErr)
486 return PathOrErr.takeError();
487 Path = *PathOrErr;
488 }
489 }
490 if (Path)
491 return *Path;
492 }
493
494 // Try federation.
495 Expected<std::string> PathOrErr = getCachedOrDownloadExecutable(ID);
496 if (!PathOrErr)
497 consumeError(Err: PathOrErr.takeError());
498
499 // Fall back to debug binary.
500 return findDebugBinaryPath(ID);
501}
502
503Expected<std::string> DebuginfodCollection::findDebugBinaryPath(BuildIDRef ID) {
504 // Check collection; perform on-demand update if stale.
505 Expected<std::optional<std::string>> PathOrErr = getDebugBinaryPath(ID);
506 if (!PathOrErr)
507 return PathOrErr.takeError();
508 std::optional<std::string> Path = *PathOrErr;
509 if (!Path) {
510 Expected<bool> UpdatedOrErr = updateIfStale();
511 if (!UpdatedOrErr)
512 return UpdatedOrErr.takeError();
513 if (*UpdatedOrErr) {
514 // Try once more.
515 PathOrErr = getBinaryPath(ID);
516 if (!PathOrErr)
517 return PathOrErr.takeError();
518 Path = *PathOrErr;
519 }
520 }
521 if (Path)
522 return *Path;
523
524 // Try federation.
525 return getCachedOrDownloadDebuginfo(ID);
526}
527
528DebuginfodServer::DebuginfodServer(DebuginfodLog &Log,
529 DebuginfodCollection &Collection)
530 : Log(Log), Collection(Collection) {
531 cantFail(
532 Err: Server.get(UrlPathPattern: R"(/buildid/(.*)/debuginfo)", Handler: [&](HTTPServerRequest Request) {
533 Log.push(Message: "GET " + Request.UrlPath);
534 std::string IDString;
535 if (!tryGetFromHex(Input: Request.UrlPathMatches[0], Output&: IDString)) {
536 Request.setResponse(
537 {.Code: 404, .ContentType: "text/plain", .Body: "Build ID is not a hex string\n"});
538 return;
539 }
540 object::BuildID ID(IDString.begin(), IDString.end());
541 Expected<std::string> PathOrErr = Collection.findDebugBinaryPath(ID);
542 if (Error Err = PathOrErr.takeError()) {
543 consumeError(Err: std::move(Err));
544 Request.setResponse({.Code: 404, .ContentType: "text/plain", .Body: "Build ID not found\n"});
545 return;
546 }
547 streamFile(Request, FilePath: *PathOrErr);
548 }));
549 cantFail(
550 Err: Server.get(UrlPathPattern: R"(/buildid/(.*)/executable)", Handler: [&](HTTPServerRequest Request) {
551 Log.push(Message: "GET " + Request.UrlPath);
552 std::string IDString;
553 if (!tryGetFromHex(Input: Request.UrlPathMatches[0], Output&: IDString)) {
554 Request.setResponse(
555 {.Code: 404, .ContentType: "text/plain", .Body: "Build ID is not a hex string\n"});
556 return;
557 }
558 object::BuildID ID(IDString.begin(), IDString.end());
559 Expected<std::string> PathOrErr = Collection.findBinaryPath(ID);
560 if (Error Err = PathOrErr.takeError()) {
561 consumeError(Err: std::move(Err));
562 Request.setResponse({.Code: 404, .ContentType: "text/plain", .Body: "Build ID not found\n"});
563 return;
564 }
565 streamFile(Request, FilePath: *PathOrErr);
566 }));
567}
568
569} // namespace llvm
570