LLDB mainline
SymbolLocatorSymStore.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
10
15#include "lldb/Utility/Args.h"
17#include "lldb/Utility/Log.h"
18#include "lldb/Utility/UUID.h"
19
20#include "llvm/ADT/ScopeExit.h"
21#include "llvm/ADT/StringExtras.h"
22#include "llvm/ADT/StringMap.h"
23#include "llvm/HTTP/HTTPClient.h"
24#include "llvm/HTTP/StreamedHTTPResponseHandler.h"
25#include "llvm/Support/Caching.h"
26#include "llvm/Support/Endian.h"
27#include "llvm/Support/FileSystem.h"
28#include "llvm/Support/FormatVariadic.h"
29#include "llvm/Support/Path.h"
30#include "llvm/Support/raw_ostream.h"
31
32#include <memory>
33#include <mutex>
34
35using namespace lldb;
36using namespace lldb_private;
37
39
40namespace {
41
42#define LLDB_PROPERTIES_symbollocatorsymstore
43#include "SymbolLocatorSymStoreProperties.inc"
44
45enum {
46#define LLDB_PROPERTIES_symbollocatorsymstore
47#include "SymbolLocatorSymStorePropertiesEnum.inc"
48};
49
50class PluginProperties : public Properties {
51public:
52 static llvm::StringRef GetSettingName() {
54 }
55
56 PluginProperties() {
57 m_collection_sp = std::make_shared<OptionValueProperties>(GetSettingName());
58 m_collection_sp->Initialize(g_symbollocatorsymstore_properties_def);
59 }
60
61 Args GetURLs() const {
62 Args urls;
63 m_collection_sp->GetPropertyAtIndexAsArgs(ePropertySymStoreURLs, urls);
64 return urls;
65 }
66
67 std::string GetCachePath() const {
68 OptionValueString *s =
69 m_collection_sp->GetPropertyAtIndexAsOptionValueString(
70 ePropertyCachePath);
71 if (s && !s->GetCurrentValueAsRef().empty())
72 return s->GetCurrentValue();
74 }
75
76 uint64_t GetTimeout() const {
77 const uint32_t idx = ePropertyTimeout;
78 return GetPropertyAtIndexAs<uint64_t>(
79 idx, g_symbollocatorsymstore_properties[idx].default_uint_value);
80 }
81
82 std::optional<std::string> GetTLSCertFingerprint() const {
83 OptionValueString *s =
84 m_collection_sp->GetPropertyAtIndexAsOptionValueString(
85 ePropertyTLSCertFingerprint);
86 if (!s)
87 return {};
88 llvm::StringRef val = s->GetCurrentValueAsRef();
89 if (val.empty())
90 return {};
91 if (val.size() != 64 || !llvm::all_of(val, llvm::isHexDigit)) {
92 Debugger::ReportWarning(llvm::formatv(
93 "plugin.symbol-locator.symstore.tls-cert-fingerprint: expected a "
94 "64-character hex string (SHA-256), but got '{0}', ignoring",
95 val));
96 return {};
97 }
98 return val.lower();
99 }
100};
101
102} // namespace
103
104static PluginProperties &GetGlobalPluginProperties() {
105 static PluginProperties g_settings;
106 return g_settings;
107}
108
110
114 nullptr, LocateExecutableSymbolFile, nullptr, nullptr,
116 llvm::HTTPClient::initialize();
117
118 std::string default_cache = GetSystemDefaultCachePath();
119 if (std::error_code ec = llvm::sys::fs::create_directories(default_cache)) {
120 Debugger::ReportWarning(llvm::formatv(
121 "default SymStore cache directory '{0}' is not accessible: {1}",
122 default_cache, ec.message()));
123 }
124}
125
128 debugger, PluginProperties::GetSettingName())) {
129 constexpr bool is_global_setting = true;
131 debugger, GetGlobalPluginProperties().GetValueProperties(),
132 "Properties for the SymStore Symbol Locator plug-in.",
133 is_global_setting);
134 }
135}
136
139 llvm::HTTPClient::cleanup();
140}
141
143 return "Symbol locator for PDB in SymStore";
144}
145
149
150namespace {
151
152SymbolLocatorSymStore::LookupEntry MakeLookupEntry(llvm::StringRef source) {
154 entry.source = source.str();
155 entry.cache = std::nullopt;
156 return entry;
157}
158
159SymbolLocatorSymStore::LookupEntry MakeLookupEntry(llvm::StringRef source,
160 llvm::StringRef cache) {
162 entry.source = source.str();
163 entry.cache = cache.str();
164 return entry;
165}
166
167std::vector<SymbolLocatorSymStore::LookupEntry> GetGlobalLookupOrder() {
168 std::vector<SymbolLocatorSymStore::LookupEntry> result;
169
170 const char *sym_path = std::getenv("_NT_SYMBOL_PATH");
171 for (auto entry : SymbolLocatorSymStore::ParseEnvSymbolPaths(sym_path))
172 result.push_back(std::move(entry));
173
174 const char *alt_path = std::getenv("_NT_ALT_SYMBOL_PATH");
175 for (auto entry : SymbolLocatorSymStore::ParseEnvSymbolPaths(alt_path))
176 result.push_back(std::move(entry));
177
178 for (const auto &url : GetGlobalPluginProperties().GetURLs())
179 result.push_back(MakeLookupEntry(url.ref()));
180
181 return result;
182}
183
184std::optional<SymbolLocatorSymStore::LookupEntry>
185ParseSrvEntry(llvm::StringRef entry) {
186 llvm::SmallVector<llvm::StringRef, 4> parts;
187 entry.trim().split(parts, '*');
188
189 // Format is: srv*[LocalCache*]SymbolStore
190 switch (parts.size()) {
191 case 2:
192 return MakeLookupEntry(parts[1]);
193 case 3: {
194 // Fall back to the configured default cache for empty values.
195 if (parts[1].empty())
196 return MakeLookupEntry(parts[2],
197 GetGlobalPluginProperties().GetCachePath());
198 return MakeLookupEntry(parts[2], parts[1]);
199 }
200 default:
201 return {}; // Ignore entries with invalid number of parts.
202 }
203}
204
205std::optional<std::string> ParseCacheEntry(llvm::StringRef entry) {
206 llvm::SmallVector<llvm::StringRef, 2> parts;
207 entry.trim().split(parts, '*');
208
209 // Ignore entries with invalid number of parts.
210 if (parts.size() > 2)
211 return {};
212
213 // Empty cache* deliberatly specifies the default cache path.
214 llvm::StringRef value;
215 if (parts.size() == 2)
216 value = parts.back();
217
218 // Fall back to LLDB's default cache for empty values.
219 if (value.empty())
220 return GetGlobalPluginProperties().GetCachePath();
221
222 return value.str();
223}
224
225// RSDS entries store identity as a 20-byte UUID composed of 16-byte GUID and
226// 4-byte age:
227// 12345678-1234-5678-9ABC-DEF012345678-00000001
228//
229// SymStore key is a string with no separators and age as decimal:
230// 12345678123456789ABCDEF0123456781
231//
232std::string FormatSymStoreKey(const UUID &uuid) {
233 llvm::ArrayRef<uint8_t> bytes = uuid.GetBytes();
234 uint32_t age = llvm::support::endian::read32be(bytes.data() + 16);
235 constexpr bool lower_case = false;
236 return llvm::toHex(bytes.slice(0, 16), lower_case) + std::to_string(age);
237}
238
239bool HasUnsafeCharacters(llvm::StringRef s) {
240 for (unsigned char c : s) {
241 // RFC 3986 unreserved characters are safe for file names and URLs.
242 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
243 (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' ||
244 c == '~') {
245 continue;
246 }
247
248 return true;
249 }
250
251 // Avoid path semantics issues.
252 return s == "." || s == "..";
253}
254
255std::optional<FileSpec>
256RequestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key,
257 llvm::StringRef pdb_name) {
258 using namespace llvm::sys;
259
260 // Make sure URL will be valid, portable, and compatible with symbol servers.
261 if (HasUnsafeCharacters(pdb_name)) {
262 Debugger::ReportWarning(llvm::formatv(
263 "rejecting HTTP lookup for PDB file due to unsafe characters in "
264 "name: {0}",
265 pdb_name));
266 return {};
267 }
268
269 if (!llvm::HTTPClient::isAvailable()) {
271 "HTTP client is not available for SymStore download");
272 return {};
273 }
274
275 // Download into a temporary file. The name must be unique: lookups for the
276 // same file can be in flight concurrently.
277 llvm::SmallString<128> tmp_model;
278 constexpr bool erase_on_reboot = true;
279 path::system_temp_directory(erase_on_reboot, tmp_model);
280 path::append(tmp_model,
281 llvm::formatv("lldb_symstore_{0}_{1}.%%%%%%", key, pdb_name));
282
283 llvm::SmallString<128> tmp_file;
284 if (std::error_code ec = fs::createUniqueFile(tmp_model, tmp_file)) {
285 Debugger::ReportWarning(llvm::formatv(
286 "failed to create a temporary file to download '{0}' into: {1}",
287 pdb_name, ec.message()));
288 return {};
289 }
290
291 // Clean up the temporary file unless we hand it off to the caller.
292 llvm::scope_exit remove_tmp_file([&] { fs::remove(tmp_file.str()); });
293
294 // Server has SymStore directory structure with forward slashes as separators.
295 std::string source_url =
296 llvm::formatv("{0}/{1}/{2}/{1}", base_url, pdb_name, key);
297
298 llvm::HTTPClient client;
299 client.setTimeout(
300 std::chrono::seconds(GetGlobalPluginProperties().GetTimeout()));
301
302 llvm::StreamedHTTPResponseHandler Handler(
303 [dest = tmp_file.str().str()]()
304 -> llvm::Expected<std::unique_ptr<llvm::CachedFileStream>> {
305 std::error_code ec;
306 auto os = std::make_unique<llvm::raw_fd_ostream>(dest, ec);
307 if (ec)
308 return llvm::createStringError(ec, "Failed to open file for writing");
309 return std::make_unique<llvm::CachedFileStream>(std::move(os), dest);
310 },
311 client);
312
313 llvm::HTTPRequest request(source_url);
314 request.PinnedCertFingerprint =
315 GetGlobalPluginProperties().GetTLSCertFingerprint();
316 if (llvm::Error Err = client.perform(request, Handler)) {
317 Err = llvm::joinErrors(std::move(Err), Handler.abort());
319 llvm::formatv("failed to download from SymStore '{0}': {1}", source_url,
320 llvm::toString(std::move(Err))));
321 return {};
322 }
323 if (llvm::Error Err = Handler.commit()) {
325 llvm::formatv("failed to download from SymStore '{0}': {1}", source_url,
326 llvm::toString(std::move(Err))));
327 return {};
328 }
329
330 unsigned responseCode = client.responseCode();
331 switch (responseCode) {
332 case 200:
333 remove_tmp_file.release();
334 return FileSpec(tmp_file.str()); // success
335 case 404:
336 return {}; // file not found
337 default:
338 Debugger::ReportWarning(llvm::formatv(
339 "failed to download from SymStore '{0}': response code {1}", source_url,
340 responseCode));
341 return {};
342 }
343}
344
345std::optional<FileSpec> FindFileInLocalSymStore(llvm::StringRef root_dir,
346 llvm::StringRef key,
347 llvm::StringRef pdb_name) {
348 llvm::SmallString<256> path;
349 llvm::sys::path::append(path, root_dir, pdb_name, key, pdb_name);
350 FileSpec spec(path);
351 if (!FileSystem::Instance().Exists(spec))
352 return {};
353
354 return spec;
355}
356
357/// Synchronizes lookups that resolve to the same cache entry. Lookups for
358/// different entries never contend. The map is proportional to the number of
359/// lookups in flight, not the number of symbols resolved.
360class DownloadLock {
361public:
362 DownloadLock(llvm::StringRef cache, llvm::StringRef key,
363 llvm::StringRef pdb_name)
364 : m_key(llvm::formatv("{0}/{1}/{2}", cache, key, pdb_name)) {
365 {
366 std::lock_guard<std::mutex> guard(GetMapMutex());
367 std::shared_ptr<Entry> &entry = GetMap()[m_key];
368 if (!entry)
369 entry = std::make_shared<Entry>();
370 m_entry = entry;
371 }
372 m_entry->mutex.lock();
373 }
374
375 ~DownloadLock() {
376 m_entry->mutex.unlock();
377 std::lock_guard<std::mutex> guard(GetMapMutex());
378 m_entry.reset();
379 auto it = GetMap().find(m_key);
380 if (it != GetMap().end() && it->second.use_count() == 1)
381 GetMap().erase(it);
382 }
383
384 DownloadLock(const DownloadLock &) = delete;
385 DownloadLock &operator=(const DownloadLock &) = delete;
386
387private:
388 struct Entry {
389 std::mutex mutex;
390 };
391
392 static std::mutex &GetMapMutex() {
393 static std::mutex g_mutex;
394 return g_mutex;
395 }
396
397 static llvm::StringMap<std::shared_ptr<Entry>> &GetMap() {
398 static llvm::StringMap<std::shared_ptr<Entry>> g_map;
399 return g_map;
400 }
401
402 std::string m_key;
403 std::shared_ptr<Entry> m_entry;
404};
405
406std::optional<FileSpec> MoveToLocalSymStore(llvm::StringRef cache,
407 llvm::StringRef key,
408 llvm::StringRef pdb_name,
409 FileSpec tmp_file) {
410 // Caches have SymStore directory structure: cache/pdb_name/key/pdb_name
411 llvm::SmallString<256> dest_dir;
412 llvm::sys::path::append(dest_dir, cache, pdb_name, key);
413 if (std::error_code ec = llvm::sys::fs::create_directories(dest_dir)) {
415 llvm::formatv("failed to create SymStore cache directory '{0}': {1}",
416 dest_dir, ec.message()));
417 return {};
418 }
419
420 llvm::SmallString<256> dest;
421 llvm::sys::path::append(dest, dest_dir, pdb_name);
422 std::error_code ec = llvm::sys::fs::rename(tmp_file.GetPath(), dest);
423
424 // Fall back to copy+delete if we move to a different volume. Copy next to
425 // the destination and rename, so a concurrent lookup never observes a
426 // partially written file at the cache location.
427 if (ec == std::errc::cross_device_link) {
428 llvm::SmallString<256> staged;
429 if ((ec = llvm::sys::fs::createUniqueFile(dest + ".%%%%%%", staged))) {
430 Debugger::ReportWarning(llvm::formatv(
431 "failed to create a temporary file in SymStore cache '{0}': {1}",
432 dest_dir, ec.message()));
433 return {};
434 }
435 llvm::scope_exit remove_staged([&] { llvm::sys::fs::remove(staged); });
436 if (!(ec = llvm::sys::fs::copy_file(tmp_file.GetPath(), staged)) &&
437 !(ec = llvm::sys::fs::rename(staged, dest))) {
438 remove_staged.release();
439 llvm::sys::fs::remove(tmp_file.GetPath());
440 }
441 }
442 if (ec) {
444 llvm::formatv("failed to move '{0}' to SymStore cache '{1}': {2}",
445 tmp_file.GetPath(), dest, ec.message()));
446 return {};
447 }
448
449 return FileSpec(dest.str());
450}
451
452std::string SelectSymStoreCache(std::optional<std::string> sympath_cache) {
453 llvm::SmallVector<std::string, 2> candidates;
454
455 // Prefer user cache from symbol path.
456 if (sympath_cache) {
457 assert(!sympath_cache->empty() && "Empty entries resolve to default cache");
458 candidates.push_back(*sympath_cache);
459 }
460
461 // Fallback to configured cache from settings.
462 candidates.push_back(GetGlobalPluginProperties().GetCachePath());
463
465 for (const auto &path : candidates) {
466 if (llvm::sys::fs::is_directory(path))
467 return path;
468 if (std::error_code ec = llvm::sys::fs::create_directories(path)) {
469 LLDB_LOG(log, "Ignoring invalid SymStore cache directory '{0}': {1}",
470 path, ec.message());
471 continue;
472 }
473 return path;
474 }
475
476 // Last resort is the system default location.
478}
479
480std::optional<FileSpec>
481LocateSymStoreEntry(const SymbolLocatorSymStore::LookupEntry &entry,
482 llvm::StringRef key, llvm::StringRef pdb_name) {
484 llvm::StringRef url = entry.source;
485 if (url.starts_with("http://") || url.starts_with("https://")) {
486 std::string cache_path = SelectSymStoreCache(entry.cache);
487
488 // Held across the cache check as well as the download, so that a thread
489 // that loses the race observes the winner's result instead of fetching
490 // the same symbol a second time.
491 DownloadLock lock(cache_path, key, pdb_name);
492
493 // Check cache first.
494 if (auto spec = FindFileInLocalSymStore(cache_path, key, pdb_name)) {
495 LLDB_LOG(log, "Found {0} in SymStore cache {1}", pdb_name, cache_path);
496 return *spec;
497 }
498
499 // Download and move to cache.
500 if (auto tmp_file = RequestFileFromSymStoreServerHTTP(url, key, pdb_name)) {
501 LLDB_LOG(log, "Downloaded {0} from SymStore {1}", pdb_name, url);
502 auto spec = MoveToLocalSymStore(cache_path, key, pdb_name, *tmp_file);
503 if (!spec) {
504 // Try the fallback and eventually rather cancel than loading the tmp
505 // file, since it might disappear or get overwritten.
507 spec = MoveToLocalSymStore(cache_path, key, pdb_name, *tmp_file);
508 if (!spec)
509 return {};
510 }
511 LLDB_LOG(log, "Added {0} to SymStore cache {1}", pdb_name, cache_path);
512 return *spec;
513 }
514
515 return {};
516 }
517
518 llvm::StringRef file = entry.source;
519 if (file.starts_with("file://"))
520 file = file.drop_front(7);
521 if (auto spec = FindFileInLocalSymStore(file, key, pdb_name)) {
522 LLDB_LOG(log, "Found {0} in local SymStore {1}", pdb_name, file);
523 return *spec;
524 }
525
526 return {};
527}
528
529} // namespace
530
532 const ModuleSpec &module_spec, const FileSpecList &default_search_paths) {
533 const UUID &uuid = module_spec.GetUUID();
534 if (!uuid.IsValid() ||
535 !ModuleList::GetGlobalModuleListProperties().GetEnableExternalLookup())
536 return {};
537
539 std::string pdb_name = module_spec.GetSymbolFileSpec().GetFilename().str();
540 if (pdb_name.empty()) {
541 LLDB_LOG(log, "Failed to resolve symbol PDB module: PDB name empty");
542 return {};
543 }
544
545 LLDB_LOG(log, "LocateExecutableSymbolFile {0} with UUID {1}", pdb_name,
546 uuid.GetAsString());
547 if (uuid.GetBytes().size() != 20) {
548 LLDB_LOG(log, "Failed to resolve symbol PDB module: UUID invalid");
549 return {};
550 }
551
552 std::string key = FormatSymStoreKey(uuid);
553 for (const LookupEntry &entry : GetGlobalLookupOrder()) {
554 if (auto spec = LocateSymStoreEntry(entry, key, pdb_name))
555 return *spec;
556 }
557
558 return {};
559}
560
561std::vector<SymbolLocatorSymStore::LookupEntry>
563 if (val.empty())
564 return {};
565
566 std::vector<LookupEntry> result;
567 std::optional<std::string> implicit_cache;
568 llvm::SmallVector<llvm::StringRef, 2> entries;
569 val.split(entries, ';');
570
571 for (llvm::StringRef raw : entries) {
572 llvm::StringRef entry = raw.trim();
573 if (entry.empty())
574 continue;
575
576 // Explicit cache directives apply to all subsequent srv* entries that don't
577 // set their own explicit cache.
578 if (entry.starts_with_insensitive("cache*")) {
579 if (auto cache = ParseCacheEntry(entry))
580 implicit_cache = *cache;
581 continue;
582 }
583
584 // SymStore directives with explicit interpreters are unsupported
585 // explicitly.
586 if (entry.starts_with_insensitive("symsrv*")) {
588 llvm::formatv("ignoring unsupported entry in env: {0}", entry));
589 continue;
590 }
591
592 // SymStore server directives may include an explicit cache.
593 // Format is: srv*[LocalCache*]SymbolStore
594 if (entry.starts_with_insensitive("srv*")) {
595 if (auto lookup_entry = ParseSrvEntry(entry)) {
596 if (!lookup_entry->cache && implicit_cache)
597 lookup_entry->cache = implicit_cache;
598 result.push_back(*lookup_entry);
599 }
600 continue;
601 }
602
603 // Plain local paths aren't cached.
604 result.push_back(MakeLookupEntry(entry));
605 }
606
607 return result;
608}
609
611 // Fall back to the platform cache directory.
612 llvm::SmallString<128> cache_dir;
613 if (llvm::sys::path::cache_directory(cache_dir)) {
614 llvm::sys::path::append(cache_dir, "lldb", "symstore");
615 return cache_dir.str().str();
616 }
617 // Last resort: use a subdirectory of the system temp directory.
618 constexpr bool erase_on_reboot = false;
619 llvm::sys::path::system_temp_directory(erase_on_reboot, cache_dir);
620 llvm::sys::path::append(cache_dir, "lldb", "symstore");
621 return cache_dir.str().str();
622}
FormatEntity::Entry Entry
static PluginProperties & GetGlobalPluginProperties()
#define LLDB_LOG(log,...)
The LLDB_LOG* macros defined below are the way to emit log messages.
Definition Log.h:375
#define LLDB_PLUGIN_DEFINE(PluginName)
static PluginProperties & GetGlobalPluginProperties()
A class to manage flag bits.
Definition Debugger.h:103
static void ReportWarning(std::string message, std::optional< lldb::user_id_t > debugger_id=std::nullopt, std::once_flag *once=nullptr)
Report warning events.
A file collection class.
A file utility class.
Definition FileSpec.h:56
llvm::StringRef GetFilename() const
Filename string const get accessor.
Definition FileSpec.h:248
size_t GetPath(char *path, size_t max_path_length, bool denormalize=true) const
Extract the full path to the file.
Definition FileSpec.cpp:380
static FileSystem & Instance()
static ModuleListProperties & GetGlobalModuleListProperties()
FileSpec & GetSymbolFileSpec()
Definition ModuleSpec.h:81
llvm::StringRef GetCurrentValueAsRef() const
const char * GetCurrentValue() const
static bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description, ABICreateInstance create_callback)
static lldb::OptionValuePropertiesSP GetSettingForSymbolLocatorPlugin(Debugger &debugger, llvm::StringRef setting_name)
static bool UnregisterPlugin(ABICreateInstance create_callback)
static bool CreateSettingForSymbolLocatorPlugin(Debugger &debugger, const lldb::OptionValuePropertiesSP &properties_sp, llvm::StringRef description, bool is_global_property)
This plugin implements lookup in Microsoft SymStore instances.
static void DebuggerInitialize(Debugger &debugger)
static lldb_private::SymbolLocator * CreateInstance()
static llvm::StringRef GetPluginNameStatic()
static llvm::StringRef GetPluginDescriptionStatic()
static std::optional< FileSpec > LocateExecutableSymbolFile(const ModuleSpec &module_spec, const FileSpecList &default_search_paths)
static std::vector< LookupEntry > ParseEnvSymbolPaths(llvm::StringRef val)
Represents UUID's of various sizes.
Definition UUID.h:27
llvm::ArrayRef< uint8_t > GetBytes() const
Definition UUID.h:66
std::string GetAsString(llvm::StringRef separator="-") const
Definition UUID.cpp:54
bool IsValid() const
Definition UUID.h:69
A class that represents a running process on the host machine.
Log * GetLog(Cat mask)
Retrieve the Log object for the channel associated with the given log enum.
Definition Log.h:338