xref: /freebsd/contrib/llvm-project/llvm/lib/Debuginfod/Debuginfod.cpp (revision ebacd8013fe5f7fdf9f6a5b286f6680dd2891036)
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 {
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.
49 static std::string buildIDToString(BuildIDRef ID) {
50   return llvm::toHex(ID, /*LowerCase=*/true);
51 }
52 
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.
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 
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 
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 
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 
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.
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:
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 
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 
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 
228 DebuginfodLogEntry::DebuginfodLogEntry(const Twine &Message)
229     : Message(Message.str()) {}
230 
231 void DebuginfodLog::push(const Twine &Message) {
232   push(DebuginfodLogEntry(Message));
233 }
234 
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 
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 
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 
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 
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 
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 
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 
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 
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>>
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>>
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 
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 
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 
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