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