1 //===-- llvm/Debuginfod/Debuginfod.cpp - Debuginfod client library --------===//
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
7 //===----------------------------------------------------------------------===//
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.
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"
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() {
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
,
128 SmallString
<10> CacheDir
;
130 Expected
<std::string
> CacheDirOrErr
= getDefaultDebuginfodCacheDirectory();
132 return CacheDirOrErr
.takeError();
133 CacheDir
= *CacheDirOrErr
;
135 return getCachedOrDownloadArtifact(UniqueKey
, UrlPath
, CacheDir
,
136 getDefaultDebuginfodUrls(),
137 getDefaultDebuginfodTimeout());
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
;
149 std::unique_ptr
<CachedFileStream
> FileStream
;
152 StreamedHTTPResponseHandler(CreateStreamFn CreateStream
, HTTPClient
&Client
)
153 : CreateStream(CreateStream
), Client(Client
) {}
154 virtual ~StreamedHTTPResponseHandler() = default;
156 Error
handleBodyChunk(StringRef BodyChunk
) override
;
161 Error
StreamedHTTPResponseHandler::handleBodyChunk(StringRef BodyChunk
) {
163 unsigned Code
= Client
.responseCode();
164 if (Code
&& Code
!= 200)
165 return Error::success();
166 Expected
<std::unique_ptr
<CachedFileStream
>> FileStreamOrError
=
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
) {
180 std::tie(Name
, Value
) = S
.split(':');
181 if (Name
.empty() || Value
.empty())
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");
191 ErrorOr
<std::unique_ptr
<MemoryBuffer
>> HeadersFile
=
192 MemoryBuffer::getFile(Filename
, /*IsText=*/true);
196 SmallVector
<std::string
, 0> Headers
;
197 uint64_t LineNumber
= 0;
198 for (StringRef Line
: llvm::split((*HeadersFile
)->getBuffer(), '\n')) {
200 if (!Line
.empty() && Line
.back() == '\r')
201 Line
= Line
.drop_back();
202 if (!isHeader(Line
)) {
203 if (!all_of(Line
, llvm::isSpace
))
205 << "could not parse debuginfod header: " << Filename
<< ':'
206 << LineNumber
<< '\n';
209 Headers
.emplace_back(Line
);
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
);
224 return CacheOrErr
.takeError();
226 FileCache Cache
= *CacheOrErr
;
227 // We choose an arbitrary Task parameter as we do not make use of it.
229 Expected
<AddStreamFn
> CacheAddStreamOrErr
= Cache(Task
, UniqueKey
, "");
230 if (!CacheAddStreamOrErr
)
231 return CacheAddStreamOrErr
.takeError();
232 AddStreamFn
&CacheAddStream
= *CacheAddStreamOrErr
;
234 return std::string(AbsCachedArtifactPath
);
235 // The artifact was not found in the local cache, query the debuginfod
237 if (!HTTPClient::isAvailable())
238 return createStringError(errc::io_error
,
239 "No working HTTP client is available.");
241 if (!HTTPClient::IsInitialized
)
242 return createStringError(
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.");
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
257 StreamedHTTPResponseHandler
Handler(
258 [&]() { return CacheAddStream(Task
, ""); }, Client
);
259 HTTPRequest
Request(ArtifactUrl
);
260 Request
.Headers
= getHeaders();
261 Error Err
= Client
.perform(Request
, Handler
);
263 return std::move(Err
);
265 unsigned Code
= Client
.responseCode();
266 if (Code
&& Code
!= 200)
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();
313 DebuginfodCollection::DebuginfodCollection(ArrayRef
<StringRef
> PathsRef
,
314 DebuginfodLog
&Log
, ThreadPool
&Pool
,
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();
326 for (const std::string
&Path
: Paths
) {
327 Log
.push("Updating binaries at path " + Path
);
328 if (Error Err
= findBinaries(Path
))
331 Log
.push("Updated collection");
332 UpdateTimer
.startTimer();
333 return Error::success();
336 Expected
<bool> DebuginfodCollection::updateIfStale() {
337 if (!UpdateTimer
.isRunning())
339 UpdateTimer
.stopTimer();
340 double Time
= UpdateTimer
.getTotalTime().getWallTime();
341 UpdateTimer
.startTimer();
342 if (Time
< MinInterval
)
344 if (Error Err
= update())
345 return std::move(Err
);
349 Error
DebuginfodCollection::updateForever(std::chrono::milliseconds Interval
) {
351 if (Error Err
= update())
353 std::this_thread::sleep_for(Interval
);
355 llvm_unreachable("updateForever loop should never end");
358 static bool hasELFMagic(StringRef FilePath
) {
360 std::error_code EC
= identify_magic(FilePath
, 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
:
375 Error
DebuginfodCollection::findBinaries(StringRef Path
) {
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();
382 IteratorGroup
.async([&, this]() -> void {
383 std::string FilePath
;
386 // Check if iteration is over or there is an error during iteration
387 std::lock_guard
<std::mutex
> Guard(IteratorMutex
);
390 // Grab a file path from the directory iterator and advance the
392 FilePath
= I
->path();
396 // Inspect the file at this path to determine if it is debuginfo.
397 if (!hasELFMagic(FilePath
))
400 Expected
<object::OwningBinary
<object::Binary
>> BinOrErr
=
401 object::createBinary(FilePath
);
404 consumeError(BinOrErr
.takeError());
407 object::Binary
*Bin
= std::move(BinOrErr
.get().getBinary());
408 if (!Bin
->isObject())
411 // TODO: Support non-ELF binaries
412 object::ELFObjectFileBase
*Object
=
413 dyn_cast
<object::ELFObjectFileBase
>(Bin
);
417 BuildIDRef ID
= getBuildID(Object
);
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
));
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
);
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();
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();
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
);
468 return PathOrErr
.takeError();
469 std::optional
<std::string
> Path
= *PathOrErr
;
471 Expected
<bool> UpdatedOrErr
= updateIfStale();
473 return UpdatedOrErr
.takeError();
476 PathOrErr
= getBinaryPath(ID
);
478 return PathOrErr
.takeError();
487 Expected
<std::string
> PathOrErr
= getCachedOrDownloadExecutable(ID
);
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
);
499 return PathOrErr
.takeError();
500 std::optional
<std::string
> Path
= *PathOrErr
;
502 Expected
<bool> UpdatedOrErr
= updateIfStale();
504 return UpdatedOrErr
.takeError();
507 PathOrErr
= getBinaryPath(ID
);
509 return PathOrErr
.takeError();
517 return getCachedOrDownloadDebuginfo(ID
);
520 DebuginfodServer::DebuginfodServer(DebuginfodLog
&Log
,
521 DebuginfodCollection
&Collection
)
522 : Log(Log
), Collection(Collection
) {
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
)) {
529 {404, "text/plain", "Build ID is not a hex string\n"});
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"});
539 streamFile(Request
, *PathOrErr
);
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
)) {
547 {404, "text/plain", "Build ID is not a hex string\n"});
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"});
557 streamFile(Request
, *PathOrErr
);