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/StringRef.h"
26 #include "llvm/BinaryFormat/Magic.h"
27 #include "llvm/DebugInfo/DWARF/DWARFContext.h"
28 #include "llvm/DebugInfo/Symbolize/Symbolize.h"
29 #include "llvm/Debuginfod/HTTPClient.h"
30 #include "llvm/Object/Binary.h"
31 #include "llvm/Object/ELFObjectFile.h"
32 #include "llvm/Object/ObjectFile.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/Path.h"
39 #include "llvm/Support/ThreadPool.h"
40 #include "llvm/Support/xxhash.h"
41
42 #include <atomic>
43
44 namespace llvm {
uniqueKey(llvm::StringRef S)45 static std::string uniqueKey(llvm::StringRef S) { return utostr(xxHash64(S)); }
46
47 // Returns a binary BuildID as a normalized hex string.
48 // Uses lowercase for compatibility with common debuginfod servers.
buildIDToString(BuildIDRef ID)49 static std::string buildIDToString(BuildIDRef ID) {
50 return llvm::toHex(ID, /*LowerCase=*/true);
51 }
52
getDefaultDebuginfodUrls()53 Expected<SmallVector<StringRef>> getDefaultDebuginfodUrls() {
54 const char *DebuginfodUrlsEnv = std::getenv("DEBUGINFOD_URLS");
55 if (DebuginfodUrlsEnv == nullptr)
56 return SmallVector<StringRef>();
57
58 SmallVector<StringRef> DebuginfodUrls;
59 StringRef(DebuginfodUrlsEnv).split(DebuginfodUrls, " ");
60 return DebuginfodUrls;
61 }
62
63 /// Finds a default local file caching directory for the debuginfod client,
64 /// first checking DEBUGINFOD_CACHE_PATH.
getDefaultDebuginfodCacheDirectory()65 Expected<std::string> getDefaultDebuginfodCacheDirectory() {
66 if (const char *CacheDirectoryEnv = std::getenv("DEBUGINFOD_CACHE_PATH"))
67 return CacheDirectoryEnv;
68
69 SmallString<64> CacheDirectory;
70 if (!sys::path::cache_directory(CacheDirectory))
71 return createStringError(
72 errc::io_error, "Unable to determine appropriate cache directory.");
73 sys::path::append(CacheDirectory, "llvm-debuginfod", "client");
74 return std::string(CacheDirectory);
75 }
76
getDefaultDebuginfodTimeout()77 std::chrono::milliseconds getDefaultDebuginfodTimeout() {
78 long Timeout;
79 const char *DebuginfodTimeoutEnv = std::getenv("DEBUGINFOD_TIMEOUT");
80 if (DebuginfodTimeoutEnv &&
81 to_integer(StringRef(DebuginfodTimeoutEnv).trim(), Timeout, 10))
82 return std::chrono::milliseconds(Timeout * 1000);
83
84 return std::chrono::milliseconds(90 * 1000);
85 }
86
87 /// The following functions fetch a debuginfod artifact to a file in a local
88 /// cache and return the cached file path. They first search the local cache,
89 /// followed by the debuginfod servers.
90
getCachedOrDownloadSource(BuildIDRef ID,StringRef SourceFilePath)91 Expected<std::string> getCachedOrDownloadSource(BuildIDRef ID,
92 StringRef SourceFilePath) {
93 SmallString<64> UrlPath;
94 sys::path::append(UrlPath, sys::path::Style::posix, "buildid",
95 buildIDToString(ID), "source",
96 sys::path::convert_to_slash(SourceFilePath));
97 return getCachedOrDownloadArtifact(uniqueKey(UrlPath), UrlPath);
98 }
99
getCachedOrDownloadExecutable(BuildIDRef ID)100 Expected<std::string> getCachedOrDownloadExecutable(BuildIDRef ID) {
101 SmallString<64> UrlPath;
102 sys::path::append(UrlPath, sys::path::Style::posix, "buildid",
103 buildIDToString(ID), "executable");
104 return getCachedOrDownloadArtifact(uniqueKey(UrlPath), UrlPath);
105 }
106
getCachedOrDownloadDebuginfo(BuildIDRef ID)107 Expected<std::string> getCachedOrDownloadDebuginfo(BuildIDRef ID) {
108 SmallString<64> UrlPath;
109 sys::path::append(UrlPath, sys::path::Style::posix, "buildid",
110 buildIDToString(ID), "debuginfo");
111 return getCachedOrDownloadArtifact(uniqueKey(UrlPath), UrlPath);
112 }
113
114 // General fetching function.
getCachedOrDownloadArtifact(StringRef UniqueKey,StringRef UrlPath)115 Expected<std::string> getCachedOrDownloadArtifact(StringRef UniqueKey,
116 StringRef UrlPath) {
117 SmallString<10> CacheDir;
118
119 Expected<std::string> CacheDirOrErr = getDefaultDebuginfodCacheDirectory();
120 if (!CacheDirOrErr)
121 return CacheDirOrErr.takeError();
122 CacheDir = *CacheDirOrErr;
123
124 Expected<SmallVector<StringRef>> DebuginfodUrlsOrErr =
125 getDefaultDebuginfodUrls();
126 if (!DebuginfodUrlsOrErr)
127 return DebuginfodUrlsOrErr.takeError();
128 SmallVector<StringRef> &DebuginfodUrls = *DebuginfodUrlsOrErr;
129 return getCachedOrDownloadArtifact(UniqueKey, UrlPath, CacheDir,
130 DebuginfodUrls,
131 getDefaultDebuginfodTimeout());
132 }
133
134 namespace {
135
136 /// A simple handler which streams the returned data to a cache file. The cache
137 /// file is only created if a 200 OK status is observed.
138 class StreamedHTTPResponseHandler : public HTTPResponseHandler {
139 using CreateStreamFn =
140 std::function<Expected<std::unique_ptr<CachedFileStream>>()>;
141 CreateStreamFn CreateStream;
142 HTTPClient &Client;
143 std::unique_ptr<CachedFileStream> FileStream;
144
145 public:
StreamedHTTPResponseHandler(CreateStreamFn CreateStream,HTTPClient & Client)146 StreamedHTTPResponseHandler(CreateStreamFn CreateStream, HTTPClient &Client)
147 : CreateStream(CreateStream), Client(Client) {}
148 virtual ~StreamedHTTPResponseHandler() = default;
149
150 Error handleBodyChunk(StringRef BodyChunk) override;
151 };
152
153 } // namespace
154
handleBodyChunk(StringRef BodyChunk)155 Error StreamedHTTPResponseHandler::handleBodyChunk(StringRef BodyChunk) {
156 if (!FileStream) {
157 if (Client.responseCode() != 200)
158 return Error::success();
159 Expected<std::unique_ptr<CachedFileStream>> FileStreamOrError =
160 CreateStream();
161 if (!FileStreamOrError)
162 return FileStreamOrError.takeError();
163 FileStream = std::move(*FileStreamOrError);
164 }
165 *FileStream->OS << BodyChunk;
166 return Error::success();
167 }
168
getCachedOrDownloadArtifact(StringRef UniqueKey,StringRef UrlPath,StringRef CacheDirectoryPath,ArrayRef<StringRef> DebuginfodUrls,std::chrono::milliseconds Timeout)169 Expected<std::string> getCachedOrDownloadArtifact(
170 StringRef UniqueKey, StringRef UrlPath, StringRef CacheDirectoryPath,
171 ArrayRef<StringRef> DebuginfodUrls, std::chrono::milliseconds Timeout) {
172 SmallString<64> AbsCachedArtifactPath;
173 sys::path::append(AbsCachedArtifactPath, CacheDirectoryPath,
174 "llvmcache-" + UniqueKey);
175
176 Expected<FileCache> CacheOrErr =
177 localCache("Debuginfod-client", ".debuginfod-client", CacheDirectoryPath);
178 if (!CacheOrErr)
179 return CacheOrErr.takeError();
180
181 FileCache Cache = *CacheOrErr;
182 // We choose an arbitrary Task parameter as we do not make use of it.
183 unsigned Task = 0;
184 Expected<AddStreamFn> CacheAddStreamOrErr = Cache(Task, UniqueKey);
185 if (!CacheAddStreamOrErr)
186 return CacheAddStreamOrErr.takeError();
187 AddStreamFn &CacheAddStream = *CacheAddStreamOrErr;
188 if (!CacheAddStream)
189 return std::string(AbsCachedArtifactPath);
190 // The artifact was not found in the local cache, query the debuginfod
191 // servers.
192 if (!HTTPClient::isAvailable())
193 return createStringError(errc::io_error,
194 "No working HTTP client is available.");
195
196 if (!HTTPClient::IsInitialized)
197 return createStringError(
198 errc::io_error,
199 "A working HTTP client is available, but it is not initialized. To "
200 "allow Debuginfod to make HTTP requests, call HTTPClient::initialize() "
201 "at the beginning of main.");
202
203 HTTPClient Client;
204 Client.setTimeout(Timeout);
205 for (StringRef ServerUrl : DebuginfodUrls) {
206 SmallString<64> ArtifactUrl;
207 sys::path::append(ArtifactUrl, sys::path::Style::posix, ServerUrl, UrlPath);
208
209 // Perform the HTTP request and if successful, write the response body to
210 // the cache.
211 StreamedHTTPResponseHandler Handler([&]() { return CacheAddStream(Task); },
212 Client);
213 HTTPRequest Request(ArtifactUrl);
214 Error Err = Client.perform(Request, Handler);
215 if (Err)
216 return std::move(Err);
217
218 if (Client.responseCode() != 200)
219 continue;
220
221 // Return the path to the artifact on disk.
222 return std::string(AbsCachedArtifactPath);
223 }
224
225 return createStringError(errc::argument_out_of_domain, "build id not found");
226 }
227
DebuginfodLogEntry(const Twine & Message)228 DebuginfodLogEntry::DebuginfodLogEntry(const Twine &Message)
229 : Message(Message.str()) {}
230
push(const Twine & Message)231 void DebuginfodLog::push(const Twine &Message) {
232 push(DebuginfodLogEntry(Message));
233 }
234
push(DebuginfodLogEntry Entry)235 void DebuginfodLog::push(DebuginfodLogEntry Entry) {
236 {
237 std::lock_guard<std::mutex> Guard(QueueMutex);
238 LogEntryQueue.push(Entry);
239 }
240 QueueCondition.notify_one();
241 }
242
pop()243 DebuginfodLogEntry DebuginfodLog::pop() {
244 {
245 std::unique_lock<std::mutex> Guard(QueueMutex);
246 // Wait for messages to be pushed into the queue.
247 QueueCondition.wait(Guard, [&] { return !LogEntryQueue.empty(); });
248 }
249 std::lock_guard<std::mutex> Guard(QueueMutex);
250 if (!LogEntryQueue.size())
251 llvm_unreachable("Expected message in the queue.");
252
253 DebuginfodLogEntry Entry = LogEntryQueue.front();
254 LogEntryQueue.pop();
255 return Entry;
256 }
257
DebuginfodCollection(ArrayRef<StringRef> PathsRef,DebuginfodLog & Log,ThreadPool & Pool,double MinInterval)258 DebuginfodCollection::DebuginfodCollection(ArrayRef<StringRef> PathsRef,
259 DebuginfodLog &Log, ThreadPool &Pool,
260 double MinInterval)
261 : Log(Log), Pool(Pool), MinInterval(MinInterval) {
262 for (StringRef Path : PathsRef)
263 Paths.push_back(Path.str());
264 }
265
update()266 Error DebuginfodCollection::update() {
267 std::lock_guard<sys::Mutex> Guard(UpdateMutex);
268 if (UpdateTimer.isRunning())
269 UpdateTimer.stopTimer();
270 UpdateTimer.clear();
271 for (const std::string &Path : Paths) {
272 Log.push("Updating binaries at path " + Path);
273 if (Error Err = findBinaries(Path))
274 return Err;
275 }
276 Log.push("Updated collection");
277 UpdateTimer.startTimer();
278 return Error::success();
279 }
280
updateIfStale()281 Expected<bool> DebuginfodCollection::updateIfStale() {
282 if (!UpdateTimer.isRunning())
283 return false;
284 UpdateTimer.stopTimer();
285 double Time = UpdateTimer.getTotalTime().getWallTime();
286 UpdateTimer.startTimer();
287 if (Time < MinInterval)
288 return false;
289 if (Error Err = update())
290 return std::move(Err);
291 return true;
292 }
293
updateForever(std::chrono::milliseconds Interval)294 Error DebuginfodCollection::updateForever(std::chrono::milliseconds Interval) {
295 while (true) {
296 if (Error Err = update())
297 return Err;
298 std::this_thread::sleep_for(Interval);
299 }
300 llvm_unreachable("updateForever loop should never end");
301 }
302
isDebugBinary(object::ObjectFile * Object)303 static bool isDebugBinary(object::ObjectFile *Object) {
304 // TODO: handle PDB debuginfo
305 std::unique_ptr<DWARFContext> Context = DWARFContext::create(
306 *Object, DWARFContext::ProcessDebugRelocations::Process);
307 const DWARFObject &DObj = Context->getDWARFObj();
308 unsigned NumSections = 0;
309 DObj.forEachInfoSections([&](const DWARFSection &S) { NumSections++; });
310 return NumSections;
311 }
312
hasELFMagic(StringRef FilePath)313 static bool hasELFMagic(StringRef FilePath) {
314 file_magic Type;
315 std::error_code EC = identify_magic(FilePath, Type);
316 if (EC)
317 return false;
318 switch (Type) {
319 case file_magic::elf:
320 case file_magic::elf_relocatable:
321 case file_magic::elf_executable:
322 case file_magic::elf_shared_object:
323 case file_magic::elf_core:
324 return true;
325 default:
326 return false;
327 }
328 }
329
findBinaries(StringRef Path)330 Error DebuginfodCollection::findBinaries(StringRef Path) {
331 std::error_code EC;
332 sys::fs::recursive_directory_iterator I(Twine(Path), EC), E;
333 std::mutex IteratorMutex;
334 ThreadPoolTaskGroup IteratorGroup(Pool);
335 for (unsigned WorkerIndex = 0; WorkerIndex < Pool.getThreadCount();
336 WorkerIndex++) {
337 IteratorGroup.async([&, this]() -> void {
338 std::string FilePath;
339 while (true) {
340 {
341 // Check if iteration is over or there is an error during iteration
342 std::lock_guard<std::mutex> Guard(IteratorMutex);
343 if (I == E || EC)
344 return;
345 // Grab a file path from the directory iterator and advance the
346 // iterator.
347 FilePath = I->path();
348 I.increment(EC);
349 }
350
351 // Inspect the file at this path to determine if it is debuginfo.
352 if (!hasELFMagic(FilePath))
353 continue;
354
355 Expected<object::OwningBinary<object::Binary>> BinOrErr =
356 object::createBinary(FilePath);
357
358 if (!BinOrErr) {
359 consumeError(BinOrErr.takeError());
360 continue;
361 }
362 object::Binary *Bin = std::move(BinOrErr.get().getBinary());
363 if (!Bin->isObject())
364 continue;
365
366 // TODO: Support non-ELF binaries
367 object::ELFObjectFileBase *Object =
368 dyn_cast<object::ELFObjectFileBase>(Bin);
369 if (!Object)
370 continue;
371
372 Optional<BuildIDRef> ID = symbolize::getBuildID(Object);
373 if (!ID)
374 continue;
375
376 std::string IDString = buildIDToString(ID.value());
377 if (isDebugBinary(Object)) {
378 std::lock_guard<sys::RWMutex> DebugBinariesGuard(DebugBinariesMutex);
379 DebugBinaries[IDString] = FilePath;
380 } else {
381 std::lock_guard<sys::RWMutex> BinariesGuard(BinariesMutex);
382 Binaries[IDString] = FilePath;
383 }
384 }
385 });
386 }
387 IteratorGroup.wait();
388 std::unique_lock<std::mutex> Guard(IteratorMutex);
389 if (EC)
390 return errorCodeToError(EC);
391 return Error::success();
392 }
393
394 Expected<Optional<std::string>>
getBinaryPath(BuildIDRef ID)395 DebuginfodCollection::getBinaryPath(BuildIDRef ID) {
396 Log.push("getting binary path of ID " + buildIDToString(ID));
397 std::shared_lock<sys::RWMutex> Guard(BinariesMutex);
398 auto Loc = Binaries.find(buildIDToString(ID));
399 if (Loc != Binaries.end()) {
400 std::string Path = Loc->getValue();
401 return Path;
402 }
403 return None;
404 }
405
406 Expected<Optional<std::string>>
getDebugBinaryPath(BuildIDRef ID)407 DebuginfodCollection::getDebugBinaryPath(BuildIDRef ID) {
408 Log.push("getting debug binary path of ID " + buildIDToString(ID));
409 std::shared_lock<sys::RWMutex> Guard(DebugBinariesMutex);
410 auto Loc = DebugBinaries.find(buildIDToString(ID));
411 if (Loc != DebugBinaries.end()) {
412 std::string Path = Loc->getValue();
413 return Path;
414 }
415 return None;
416 }
417
findBinaryPath(BuildIDRef ID)418 Expected<std::string> DebuginfodCollection::findBinaryPath(BuildIDRef ID) {
419 {
420 // Check collection; perform on-demand update if stale.
421 Expected<Optional<std::string>> PathOrErr = getBinaryPath(ID);
422 if (!PathOrErr)
423 return PathOrErr.takeError();
424 Optional<std::string> Path = *PathOrErr;
425 if (!Path) {
426 Expected<bool> UpdatedOrErr = updateIfStale();
427 if (!UpdatedOrErr)
428 return UpdatedOrErr.takeError();
429 if (*UpdatedOrErr) {
430 // Try once more.
431 PathOrErr = getBinaryPath(ID);
432 if (!PathOrErr)
433 return PathOrErr.takeError();
434 Path = *PathOrErr;
435 }
436 }
437 if (Path)
438 return Path.value();
439 }
440
441 // Try federation.
442 Expected<std::string> PathOrErr = getCachedOrDownloadExecutable(ID);
443 if (!PathOrErr)
444 consumeError(PathOrErr.takeError());
445
446 // Fall back to debug binary.
447 return findDebugBinaryPath(ID);
448 }
449
findDebugBinaryPath(BuildIDRef ID)450 Expected<std::string> DebuginfodCollection::findDebugBinaryPath(BuildIDRef ID) {
451 // Check collection; perform on-demand update if stale.
452 Expected<Optional<std::string>> PathOrErr = getDebugBinaryPath(ID);
453 if (!PathOrErr)
454 return PathOrErr.takeError();
455 Optional<std::string> Path = *PathOrErr;
456 if (!Path) {
457 Expected<bool> UpdatedOrErr = updateIfStale();
458 if (!UpdatedOrErr)
459 return UpdatedOrErr.takeError();
460 if (*UpdatedOrErr) {
461 // Try once more.
462 PathOrErr = getBinaryPath(ID);
463 if (!PathOrErr)
464 return PathOrErr.takeError();
465 Path = *PathOrErr;
466 }
467 }
468 if (Path)
469 return Path.value();
470
471 // Try federation.
472 return getCachedOrDownloadDebuginfo(ID);
473 }
474
DebuginfodServer(DebuginfodLog & Log,DebuginfodCollection & Collection)475 DebuginfodServer::DebuginfodServer(DebuginfodLog &Log,
476 DebuginfodCollection &Collection)
477 : Log(Log), Collection(Collection) {
478 cantFail(
479 Server.get(R"(/buildid/(.*)/debuginfo)", [&](HTTPServerRequest Request) {
480 Log.push("GET " + Request.UrlPath);
481 std::string IDString;
482 if (!tryGetFromHex(Request.UrlPathMatches[0], IDString)) {
483 Request.setResponse(
484 {404, "text/plain", "Build ID is not a hex string\n"});
485 return;
486 }
487 BuildID ID(IDString.begin(), IDString.end());
488 Expected<std::string> PathOrErr = Collection.findDebugBinaryPath(ID);
489 if (Error Err = PathOrErr.takeError()) {
490 consumeError(std::move(Err));
491 Request.setResponse({404, "text/plain", "Build ID not found\n"});
492 return;
493 }
494 streamFile(Request, *PathOrErr);
495 }));
496 cantFail(
497 Server.get(R"(/buildid/(.*)/executable)", [&](HTTPServerRequest Request) {
498 Log.push("GET " + Request.UrlPath);
499 std::string IDString;
500 if (!tryGetFromHex(Request.UrlPathMatches[0], IDString)) {
501 Request.setResponse(
502 {404, "text/plain", "Build ID is not a hex string\n"});
503 return;
504 }
505 BuildID ID(IDString.begin(), IDString.end());
506 Expected<std::string> PathOrErr = Collection.findBinaryPath(ID);
507 if (Error Err = PathOrErr.takeError()) {
508 consumeError(std::move(Err));
509 Request.setResponse({404, "text/plain", "Build ID not found\n"});
510 return;
511 }
512 streamFile(Request, *PathOrErr);
513 }));
514 }
515
516 } // namespace llvm
517