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/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"
46 static std::string
uniqueKey(llvm::StringRef S
) { return utostr(xxHash64(S
)); }
48 // Returns a binary BuildID as a normalized hex string.
49 // Uses lowercase for compatibility with common debuginfod servers.
50 static std::string
buildIDToString(BuildIDRef ID
) {
51 return llvm::toHex(ID
, /*LowerCase=*/true);
54 Expected
<SmallVector
<StringRef
>> getDefaultDebuginfodUrls() {
55 const char *DebuginfodUrlsEnv
= std::getenv("DEBUGINFOD_URLS");
56 if (DebuginfodUrlsEnv
== nullptr)
57 return SmallVector
<StringRef
>();
59 SmallVector
<StringRef
> DebuginfodUrls
;
60 StringRef(DebuginfodUrlsEnv
).split(DebuginfodUrls
, " ");
61 return DebuginfodUrls
;
64 /// Finds a default local file caching directory for the debuginfod client,
65 /// first checking DEBUGINFOD_CACHE_PATH.
66 Expected
<std::string
> getDefaultDebuginfodCacheDirectory() {
67 if (const char *CacheDirectoryEnv
= std::getenv("DEBUGINFOD_CACHE_PATH"))
68 return CacheDirectoryEnv
;
70 SmallString
<64> CacheDirectory
;
71 if (!sys::path::cache_directory(CacheDirectory
))
72 return createStringError(
73 errc::io_error
, "Unable to determine appropriate cache directory.");
74 sys::path::append(CacheDirectory
, "llvm-debuginfod", "client");
75 return std::string(CacheDirectory
);
78 std::chrono::milliseconds
getDefaultDebuginfodTimeout() {
80 const char *DebuginfodTimeoutEnv
= std::getenv("DEBUGINFOD_TIMEOUT");
81 if (DebuginfodTimeoutEnv
&&
82 to_integer(StringRef(DebuginfodTimeoutEnv
).trim(), Timeout
, 10))
83 return std::chrono::milliseconds(Timeout
* 1000);
85 return std::chrono::milliseconds(90 * 1000);
88 /// The following functions fetch a debuginfod artifact to a file in a local
89 /// cache and return the cached file path. They first search the local cache,
90 /// followed by the debuginfod servers.
92 Expected
<std::string
> getCachedOrDownloadSource(BuildIDRef ID
,
93 StringRef SourceFilePath
) {
94 SmallString
<64> UrlPath
;
95 sys::path::append(UrlPath
, sys::path::Style::posix
, "buildid",
96 buildIDToString(ID
), "source",
97 sys::path::convert_to_slash(SourceFilePath
));
98 return getCachedOrDownloadArtifact(uniqueKey(UrlPath
), UrlPath
);
101 Expected
<std::string
> getCachedOrDownloadExecutable(BuildIDRef ID
) {
102 SmallString
<64> UrlPath
;
103 sys::path::append(UrlPath
, sys::path::Style::posix
, "buildid",
104 buildIDToString(ID
), "executable");
105 return getCachedOrDownloadArtifact(uniqueKey(UrlPath
), UrlPath
);
108 Expected
<std::string
> getCachedOrDownloadDebuginfo(BuildIDRef ID
) {
109 SmallString
<64> UrlPath
;
110 sys::path::append(UrlPath
, sys::path::Style::posix
, "buildid",
111 buildIDToString(ID
), "debuginfo");
112 return getCachedOrDownloadArtifact(uniqueKey(UrlPath
), UrlPath
);
115 // General fetching function.
116 Expected
<std::string
> getCachedOrDownloadArtifact(StringRef UniqueKey
,
118 SmallString
<10> CacheDir
;
120 Expected
<std::string
> CacheDirOrErr
= getDefaultDebuginfodCacheDirectory();
122 return CacheDirOrErr
.takeError();
123 CacheDir
= *CacheDirOrErr
;
125 Expected
<SmallVector
<StringRef
>> DebuginfodUrlsOrErr
=
126 getDefaultDebuginfodUrls();
127 if (!DebuginfodUrlsOrErr
)
128 return DebuginfodUrlsOrErr
.takeError();
129 SmallVector
<StringRef
> &DebuginfodUrls
= *DebuginfodUrlsOrErr
;
130 return getCachedOrDownloadArtifact(UniqueKey
, UrlPath
, CacheDir
,
132 getDefaultDebuginfodTimeout());
137 /// A simple handler which streams the returned data to a cache file. The cache
138 /// file is only created if a 200 OK status is observed.
139 class StreamedHTTPResponseHandler
: public HTTPResponseHandler
{
140 using CreateStreamFn
=
141 std::function
<Expected
<std::unique_ptr
<CachedFileStream
>>()>;
142 CreateStreamFn CreateStream
;
144 std::unique_ptr
<CachedFileStream
> FileStream
;
147 StreamedHTTPResponseHandler(CreateStreamFn CreateStream
, HTTPClient
&Client
)
148 : CreateStream(CreateStream
), Client(Client
) {}
149 virtual ~StreamedHTTPResponseHandler() = default;
151 Error
handleBodyChunk(StringRef BodyChunk
) override
;
156 Error
StreamedHTTPResponseHandler::handleBodyChunk(StringRef BodyChunk
) {
158 if (Client
.responseCode() != 200)
159 return Error::success();
160 Expected
<std::unique_ptr
<CachedFileStream
>> FileStreamOrError
=
162 if (!FileStreamOrError
)
163 return FileStreamOrError
.takeError();
164 FileStream
= std::move(*FileStreamOrError
);
166 *FileStream
->OS
<< BodyChunk
;
167 return Error::success();
170 Expected
<std::string
> getCachedOrDownloadArtifact(
171 StringRef UniqueKey
, StringRef UrlPath
, StringRef CacheDirectoryPath
,
172 ArrayRef
<StringRef
> DebuginfodUrls
, std::chrono::milliseconds Timeout
) {
173 SmallString
<64> AbsCachedArtifactPath
;
174 sys::path::append(AbsCachedArtifactPath
, CacheDirectoryPath
,
175 "llvmcache-" + UniqueKey
);
177 Expected
<FileCache
> CacheOrErr
=
178 localCache("Debuginfod-client", ".debuginfod-client", CacheDirectoryPath
);
180 return CacheOrErr
.takeError();
182 FileCache Cache
= *CacheOrErr
;
183 // We choose an arbitrary Task parameter as we do not make use of it.
185 Expected
<AddStreamFn
> CacheAddStreamOrErr
= Cache(Task
, UniqueKey
);
186 if (!CacheAddStreamOrErr
)
187 return CacheAddStreamOrErr
.takeError();
188 AddStreamFn
&CacheAddStream
= *CacheAddStreamOrErr
;
190 return std::string(AbsCachedArtifactPath
);
191 // The artifact was not found in the local cache, query the debuginfod
193 if (!HTTPClient::isAvailable())
194 return createStringError(errc::io_error
,
195 "No working HTTP client is available.");
197 if (!HTTPClient::IsInitialized
)
198 return createStringError(
200 "A working HTTP client is available, but it is not initialized. To "
201 "allow Debuginfod to make HTTP requests, call HTTPClient::initialize() "
202 "at the beginning of main.");
205 Client
.setTimeout(Timeout
);
206 for (StringRef ServerUrl
: DebuginfodUrls
) {
207 SmallString
<64> ArtifactUrl
;
208 sys::path::append(ArtifactUrl
, sys::path::Style::posix
, ServerUrl
, UrlPath
);
210 // Perform the HTTP request and if successful, write the response body to
212 StreamedHTTPResponseHandler
Handler([&]() { return CacheAddStream(Task
); },
214 HTTPRequest
Request(ArtifactUrl
);
215 Error Err
= Client
.perform(Request
, Handler
);
217 return std::move(Err
);
219 if (Client
.responseCode() != 200)
222 // Return the path to the artifact on disk.
223 return std::string(AbsCachedArtifactPath
);
226 return createStringError(errc::argument_out_of_domain
, "build id not found");
229 DebuginfodLogEntry::DebuginfodLogEntry(const Twine
&Message
)
230 : Message(Message
.str()) {}
232 void DebuginfodLog::push(const Twine
&Message
) {
233 push(DebuginfodLogEntry(Message
));
236 void DebuginfodLog::push(DebuginfodLogEntry Entry
) {
238 std::lock_guard
<std::mutex
> Guard(QueueMutex
);
239 LogEntryQueue
.push(Entry
);
241 QueueCondition
.notify_one();
244 DebuginfodLogEntry
DebuginfodLog::pop() {
246 std::unique_lock
<std::mutex
> Guard(QueueMutex
);
247 // Wait for messages to be pushed into the queue.
248 QueueCondition
.wait(Guard
, [&] { return !LogEntryQueue
.empty(); });
250 std::lock_guard
<std::mutex
> Guard(QueueMutex
);
251 if (!LogEntryQueue
.size())
252 llvm_unreachable("Expected message in the queue.");
254 DebuginfodLogEntry Entry
= LogEntryQueue
.front();
259 DebuginfodCollection::DebuginfodCollection(ArrayRef
<StringRef
> PathsRef
,
260 DebuginfodLog
&Log
, ThreadPool
&Pool
,
262 : Log(Log
), Pool(Pool
), MinInterval(MinInterval
) {
263 for (StringRef Path
: PathsRef
)
264 Paths
.push_back(Path
.str());
267 Error
DebuginfodCollection::update() {
268 std::lock_guard
<sys::Mutex
> Guard(UpdateMutex
);
269 if (UpdateTimer
.isRunning())
270 UpdateTimer
.stopTimer();
272 for (const std::string
&Path
: Paths
) {
273 Log
.push("Updating binaries at path " + Path
);
274 if (Error Err
= findBinaries(Path
))
277 Log
.push("Updated collection");
278 UpdateTimer
.startTimer();
279 return Error::success();
282 Expected
<bool> DebuginfodCollection::updateIfStale() {
283 if (!UpdateTimer
.isRunning())
285 UpdateTimer
.stopTimer();
286 double Time
= UpdateTimer
.getTotalTime().getWallTime();
287 UpdateTimer
.startTimer();
288 if (Time
< MinInterval
)
290 if (Error Err
= update())
291 return std::move(Err
);
295 Error
DebuginfodCollection::updateForever(std::chrono::milliseconds Interval
) {
297 if (Error Err
= update())
299 std::this_thread::sleep_for(Interval
);
301 llvm_unreachable("updateForever loop should never end");
304 static bool isDebugBinary(object::ObjectFile
*Object
) {
305 // TODO: handle PDB debuginfo
306 std::unique_ptr
<DWARFContext
> Context
= DWARFContext::create(
307 *Object
, DWARFContext::ProcessDebugRelocations::Process
);
308 const DWARFObject
&DObj
= Context
->getDWARFObj();
309 unsigned NumSections
= 0;
310 DObj
.forEachInfoSections([&](const DWARFSection
&S
) { NumSections
++; });
314 static bool hasELFMagic(StringRef FilePath
) {
316 std::error_code EC
= identify_magic(FilePath
, Type
);
320 case file_magic::elf
:
321 case file_magic::elf_relocatable
:
322 case file_magic::elf_executable
:
323 case file_magic::elf_shared_object
:
324 case file_magic::elf_core
:
331 Error
DebuginfodCollection::findBinaries(StringRef Path
) {
333 sys::fs::recursive_directory_iterator
I(Twine(Path
), EC
), E
;
334 std::mutex IteratorMutex
;
335 ThreadPoolTaskGroup
IteratorGroup(Pool
);
336 for (unsigned WorkerIndex
= 0; WorkerIndex
< Pool
.getThreadCount();
338 IteratorGroup
.async([&, this]() -> void {
339 std::string FilePath
;
342 // Check if iteration is over or there is an error during iteration
343 std::lock_guard
<std::mutex
> Guard(IteratorMutex
);
346 // Grab a file path from the directory iterator and advance the
348 FilePath
= I
->path();
352 // Inspect the file at this path to determine if it is debuginfo.
353 if (!hasELFMagic(FilePath
))
356 Expected
<object::OwningBinary
<object::Binary
>> BinOrErr
=
357 object::createBinary(FilePath
);
360 consumeError(BinOrErr
.takeError());
363 object::Binary
*Bin
= std::move(BinOrErr
.get().getBinary());
364 if (!Bin
->isObject())
367 // TODO: Support non-ELF binaries
368 object::ELFObjectFileBase
*Object
=
369 dyn_cast
<object::ELFObjectFileBase
>(Bin
);
373 Optional
<BuildIDRef
> ID
= symbolize::getBuildID(Object
);
377 std::string IDString
= buildIDToString(ID
.value());
378 if (isDebugBinary(Object
)) {
379 std::lock_guard
<sys::RWMutex
> DebugBinariesGuard(DebugBinariesMutex
);
380 DebugBinaries
[IDString
] = FilePath
;
382 std::lock_guard
<sys::RWMutex
> BinariesGuard(BinariesMutex
);
383 Binaries
[IDString
] = FilePath
;
388 IteratorGroup
.wait();
389 std::unique_lock
<std::mutex
> Guard(IteratorMutex
);
391 return errorCodeToError(EC
);
392 return Error::success();
395 Expected
<Optional
<std::string
>>
396 DebuginfodCollection::getBinaryPath(BuildIDRef ID
) {
397 Log
.push("getting binary path of ID " + buildIDToString(ID
));
398 std::shared_lock
<sys::RWMutex
> Guard(BinariesMutex
);
399 auto Loc
= Binaries
.find(buildIDToString(ID
));
400 if (Loc
!= Binaries
.end()) {
401 std::string Path
= Loc
->getValue();
407 Expected
<Optional
<std::string
>>
408 DebuginfodCollection::getDebugBinaryPath(BuildIDRef ID
) {
409 Log
.push("getting debug binary path of ID " + buildIDToString(ID
));
410 std::shared_lock
<sys::RWMutex
> Guard(DebugBinariesMutex
);
411 auto Loc
= DebugBinaries
.find(buildIDToString(ID
));
412 if (Loc
!= DebugBinaries
.end()) {
413 std::string Path
= Loc
->getValue();
419 Expected
<std::string
> DebuginfodCollection::findBinaryPath(BuildIDRef ID
) {
421 // Check collection; perform on-demand update if stale.
422 Expected
<Optional
<std::string
>> PathOrErr
= getBinaryPath(ID
);
424 return PathOrErr
.takeError();
425 Optional
<std::string
> Path
= *PathOrErr
;
427 Expected
<bool> UpdatedOrErr
= updateIfStale();
429 return UpdatedOrErr
.takeError();
432 PathOrErr
= getBinaryPath(ID
);
434 return PathOrErr
.takeError();
443 Expected
<std::string
> PathOrErr
= getCachedOrDownloadExecutable(ID
);
445 consumeError(PathOrErr
.takeError());
447 // Fall back to debug binary.
448 return findDebugBinaryPath(ID
);
451 Expected
<std::string
> DebuginfodCollection::findDebugBinaryPath(BuildIDRef ID
) {
452 // Check collection; perform on-demand update if stale.
453 Expected
<Optional
<std::string
>> PathOrErr
= getDebugBinaryPath(ID
);
455 return PathOrErr
.takeError();
456 Optional
<std::string
> Path
= *PathOrErr
;
458 Expected
<bool> UpdatedOrErr
= updateIfStale();
460 return UpdatedOrErr
.takeError();
463 PathOrErr
= getBinaryPath(ID
);
465 return PathOrErr
.takeError();
473 return getCachedOrDownloadDebuginfo(ID
);
476 DebuginfodServer::DebuginfodServer(DebuginfodLog
&Log
,
477 DebuginfodCollection
&Collection
)
478 : Log(Log
), Collection(Collection
) {
480 Server
.get(R
"(/buildid/(.*)/debuginfo)", [&](HTTPServerRequest Request
) {
481 Log
.push("GET " + Request
.UrlPath
);
482 std::string IDString
;
483 if (!tryGetFromHex(Request
.UrlPathMatches
[0], IDString
)) {
485 {404, "text/plain", "Build ID is not a hex string\n"});
488 BuildID
ID(IDString
.begin(), IDString
.end());
489 Expected
<std::string
> PathOrErr
= Collection
.findDebugBinaryPath(ID
);
490 if (Error Err
= PathOrErr
.takeError()) {
491 consumeError(std::move(Err
));
492 Request
.setResponse({404, "text/plain", "Build ID not found\n"});
495 streamFile(Request
, *PathOrErr
);
498 Server
.get(R
"(/buildid/(.*)/executable)", [&](HTTPServerRequest Request
) {
499 Log
.push("GET " + Request
.UrlPath
);
500 std::string IDString
;
501 if (!tryGetFromHex(Request
.UrlPathMatches
[0], IDString
)) {
503 {404, "text/plain", "Build ID is not a hex string\n"});
506 BuildID
ID(IDString
.begin(), IDString
.end());
507 Expected
<std::string
> PathOrErr
= Collection
.findBinaryPath(ID
);
508 if (Error Err
= PathOrErr
.takeError()) {
509 consumeError(std::move(Err
));
510 Request
.setResponse({404, "text/plain", "Build ID not found\n"});
513 streamFile(Request
, *PathOrErr
);