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/StringExtras.h"
21#include "llvm/HTTP/HTTPClient.h"
22#include "llvm/HTTP/StreamedHTTPResponseHandler.h"
23#include "llvm/Support/Caching.h"
24#include "llvm/Support/Endian.h"
25#include "llvm/Support/FileSystem.h"
26#include "llvm/Support/FormatVariadic.h"
27#include "llvm/Support/Path.h"
28#include "llvm/Support/raw_ostream.h"
29
30using namespace lldb;
31using namespace lldb_private;
32
34
35namespace {
36
37#define LLDB_PROPERTIES_symbollocatorsymstore
38#include "SymbolLocatorSymStoreProperties.inc"
39
40enum {
41#define LLDB_PROPERTIES_symbollocatorsymstore
42#include "SymbolLocatorSymStorePropertiesEnum.inc"
43};
44
45class PluginProperties : public Properties {
46public:
47 static llvm::StringRef GetSettingName() {
49 }
50
51 PluginProperties() {
52 m_collection_sp = std::make_shared<OptionValueProperties>(GetSettingName());
53 m_collection_sp->Initialize(g_symbollocatorsymstore_properties_def);
54 }
55
56 Args GetURLs() const {
57 Args urls;
58 m_collection_sp->GetPropertyAtIndexAsArgs(ePropertySymStoreURLs, urls);
59 return urls;
60 }
61
62 std::string GetCachePath() const {
63 OptionValueString *s =
64 m_collection_sp->GetPropertyAtIndexAsOptionValueString(
65 ePropertyCachePath);
66 if (s && !s->GetCurrentValueAsRef().empty())
67 return s->GetCurrentValue();
69 }
70
71 std::optional<std::string> GetTLSCertFingerprint() const {
72 OptionValueString *s =
73 m_collection_sp->GetPropertyAtIndexAsOptionValueString(
74 ePropertyTLSCertFingerprint);
75 if (!s)
76 return {};
77 llvm::StringRef val = s->GetCurrentValueAsRef();
78 if (val.empty())
79 return {};
80 if (val.size() != 64 || !llvm::all_of(val, llvm::isHexDigit)) {
81 Debugger::ReportWarning(llvm::formatv(
82 "plugin.symbol-locator.symstore.tls-cert-fingerprint: expected a "
83 "64-character hex string (SHA-256), but got '{0}', ignoring",
84 val));
85 return {};
86 }
87 return val.lower();
88 }
89};
90
91} // namespace
92
93static PluginProperties &GetGlobalPluginProperties() {
94 static PluginProperties g_settings;
95 return g_settings;
96}
97
99
103 nullptr, LocateExecutableSymbolFile, nullptr, nullptr,
105 llvm::HTTPClient::initialize();
106
107 std::string default_cache = GetSystemDefaultCachePath();
108 if (std::error_code ec = llvm::sys::fs::create_directories(default_cache)) {
109 Debugger::ReportWarning(llvm::formatv(
110 "default SymStore cache directory '{0}' is not accessible: {1}",
111 default_cache, ec.message()));
112 }
113}
114
117 debugger, PluginProperties::GetSettingName())) {
118 constexpr bool is_global_setting = true;
120 debugger, GetGlobalPluginProperties().GetValueProperties(),
121 "Properties for the SymStore Symbol Locator plug-in.",
122 is_global_setting);
123 }
124}
125
128 llvm::HTTPClient::cleanup();
129}
130
132 return "Symbol locator for PDB in SymStore";
133}
134
138
139namespace {
140
141SymbolLocatorSymStore::LookupEntry MakeLookupEntry(llvm::StringRef source) {
143 entry.source = source.str();
144 entry.cache = std::nullopt;
145 return entry;
146}
147
148SymbolLocatorSymStore::LookupEntry MakeLookupEntry(llvm::StringRef source,
149 llvm::StringRef cache) {
151 entry.source = source.str();
152 entry.cache = cache.str();
153 return entry;
154}
155
156std::vector<SymbolLocatorSymStore::LookupEntry> GetGlobalLookupOrder() {
157 std::vector<SymbolLocatorSymStore::LookupEntry> result;
158
159 const char *sym_path = std::getenv("_NT_SYMBOL_PATH");
160 for (auto entry : SymbolLocatorSymStore::ParseEnvSymbolPaths(sym_path))
161 result.push_back(std::move(entry));
162
163 const char *alt_path = std::getenv("_NT_ALT_SYMBOL_PATH");
164 for (auto entry : SymbolLocatorSymStore::ParseEnvSymbolPaths(alt_path))
165 result.push_back(std::move(entry));
166
167 for (const auto &url : GetGlobalPluginProperties().GetURLs())
168 result.push_back(MakeLookupEntry(url.ref()));
169
170 return result;
171}
172
173std::optional<SymbolLocatorSymStore::LookupEntry>
174ParseSrvEntry(llvm::StringRef entry) {
175 llvm::SmallVector<llvm::StringRef, 4> parts;
176 entry.trim().split(parts, '*');
177
178 // Format is: srv*[LocalCache*]SymbolStore
179 switch (parts.size()) {
180 case 2:
181 return MakeLookupEntry(parts[1]);
182 case 3: {
183 // Fall back to the configured default cache for empty values.
184 if (parts[1].empty())
185 return MakeLookupEntry(parts[2],
186 GetGlobalPluginProperties().GetCachePath());
187 return MakeLookupEntry(parts[2], parts[1]);
188 }
189 default:
190 return {}; // Ignore entries with invalid number of parts.
191 }
192}
193
194std::optional<std::string> ParseCacheEntry(llvm::StringRef entry) {
195 llvm::SmallVector<llvm::StringRef, 2> parts;
196 entry.trim().split(parts, '*');
197
198 // Ignore entries with invalid number of parts.
199 if (parts.size() > 2)
200 return {};
201
202 // Empty cache* deliberatly specifies the default cache path.
203 llvm::StringRef value;
204 if (parts.size() == 2)
205 value = parts.back();
206
207 // Fall back to LLDB's default cache for empty values.
208 if (value.empty())
209 return GetGlobalPluginProperties().GetCachePath();
210
211 return value.str();
212}
213
214// RSDS entries store identity as a 20-byte UUID composed of 16-byte GUID and
215// 4-byte age:
216// 12345678-1234-5678-9ABC-DEF012345678-00000001
217//
218// SymStore key is a string with no separators and age as decimal:
219// 12345678123456789ABCDEF0123456781
220//
221std::string FormatSymStoreKey(const UUID &uuid) {
222 llvm::ArrayRef<uint8_t> bytes = uuid.GetBytes();
223 uint32_t age = llvm::support::endian::read32be(bytes.data() + 16);
224 constexpr bool lower_case = false;
225 return llvm::toHex(bytes.slice(0, 16), lower_case) + std::to_string(age);
226}
227
228bool HasUnsafeCharacters(llvm::StringRef s) {
229 for (unsigned char c : s) {
230 // RFC 3986 unreserved characters are safe for file names and URLs.
231 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
232 (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' ||
233 c == '~') {
234 continue;
235 }
236
237 return true;
238 }
239
240 // Avoid path semantics issues.
241 return s == "." || s == "..";
242}
243
244std::optional<FileSpec>
245RequestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key,
246 llvm::StringRef pdb_name) {
247 using namespace llvm::sys;
248
249 // Make sure URL will be valid, portable, and compatible with symbol servers.
250 if (HasUnsafeCharacters(pdb_name)) {
251 Debugger::ReportWarning(llvm::formatv(
252 "rejecting HTTP lookup for PDB file due to unsafe characters in "
253 "name: {0}",
254 pdb_name));
255 return {};
256 }
257
258 // Download into a temporary file.
259 llvm::SmallString<128> tmp_file;
260 constexpr bool erase_on_reboot = true;
261 path::system_temp_directory(erase_on_reboot, tmp_file);
262 path::append(tmp_file, llvm::formatv("lldb_symstore_{0}_{1}", key, pdb_name));
263
264 // Server has SymStore directory structure with forward slashes as separators.
265 std::string source_url =
266 llvm::formatv("{0}/{1}/{2}/{1}", base_url, pdb_name, key);
267
268 if (!llvm::HTTPClient::isAvailable()) {
270 "HTTP client is not available for SymStore download");
271 return {};
272 }
273
274 llvm::HTTPClient client;
275 // TODO: Since PDBs can be huge, we should distinguish between resolve,
276 // connect, send and receive.
277 client.setTimeout(std::chrono::seconds(60));
278
279 llvm::StreamedHTTPResponseHandler Handler(
280 [dest = tmp_file.str().str()]()
281 -> llvm::Expected<std::unique_ptr<llvm::CachedFileStream>> {
282 std::error_code ec;
283 auto os = std::make_unique<llvm::raw_fd_ostream>(dest, ec);
284 if (ec)
285 return llvm::createStringError(ec, "Failed to open file for writing");
286 return std::make_unique<llvm::CachedFileStream>(std::move(os), dest);
287 },
288 client);
289
290 llvm::HTTPRequest request(source_url);
291 request.PinnedCertFingerprint =
292 GetGlobalPluginProperties().GetTLSCertFingerprint();
293 if (llvm::Error Err = client.perform(request, Handler)) {
295 llvm::formatv("failed to download from SymStore '{0}': {1}", source_url,
296 llvm::toString(std::move(Err))));
297 return {};
298 }
299 if (llvm::Error Err = Handler.commit()) {
301 llvm::formatv("failed to download from SymStore '{0}': {1}", source_url,
302 llvm::toString(std::move(Err))));
303 return {};
304 }
305
306 unsigned responseCode = client.responseCode();
307 switch (responseCode) {
308 case 404:
309 return {}; // file not found
310 case 200:
311 return FileSpec(tmp_file.str()); // success
312 default:
313 Debugger::ReportWarning(llvm::formatv(
314 "failed to download from SymStore '{0}': response code {1}", source_url,
315 responseCode));
316 return {};
317 }
318}
319
320std::optional<FileSpec> FindFileInLocalSymStore(llvm::StringRef root_dir,
321 llvm::StringRef key,
322 llvm::StringRef pdb_name) {
323 llvm::SmallString<256> path;
324 llvm::sys::path::append(path, root_dir, pdb_name, key, pdb_name);
325 FileSpec spec(path);
326 if (!FileSystem::Instance().Exists(spec))
327 return {};
328
329 return spec;
330}
331
332std::optional<FileSpec> MoveToLocalSymStore(llvm::StringRef cache,
333 llvm::StringRef key,
334 llvm::StringRef pdb_name,
335 FileSpec tmp_file) {
336 // Caches have SymStore directory structure: cache/pdb_name/key/pdb_name
337 llvm::SmallString<256> dest_dir;
338 llvm::sys::path::append(dest_dir, cache, pdb_name, key);
339 if (std::error_code ec = llvm::sys::fs::create_directories(dest_dir)) {
341 llvm::formatv("failed to create SymStore cache directory '{0}': {1}",
342 dest_dir, ec.message()));
343 return {};
344 }
345
346 llvm::SmallString<256> dest;
347 llvm::sys::path::append(dest, dest_dir, pdb_name);
348 std::error_code ec = llvm::sys::fs::rename(tmp_file.GetPath(), dest);
349
350 // Fall back to copy+delete if we move to a different volume.
351 if (ec == std::errc::cross_device_link) {
352 ec = llvm::sys::fs::copy_file(tmp_file.GetPath(), dest);
353 if (!ec)
354 llvm::sys::fs::remove(tmp_file.GetPath());
355 }
356 if (ec) {
358 llvm::formatv("failed to move '{0}' to SymStore cache '{1}': {2}",
359 tmp_file.GetPath(), dest, ec.message()));
360 return {};
361 }
362
363 return FileSpec(dest.str());
364}
365
366std::string SelectSymStoreCache(std::optional<std::string> sympath_cache) {
367 llvm::SmallVector<std::string, 2> candidates;
368
369 // Prefer user cache from symbol path.
370 if (sympath_cache) {
371 assert(!sympath_cache->empty() && "Empty entries resolve to default cache");
372 candidates.push_back(*sympath_cache);
373 }
374
375 // Fallback to configured cache from settings.
376 candidates.push_back(GetGlobalPluginProperties().GetCachePath());
377
379 for (const auto &path : candidates) {
380 if (llvm::sys::fs::is_directory(path))
381 return path;
382 if (std::error_code ec = llvm::sys::fs::create_directories(path)) {
383 LLDB_LOG(log, "Ignoring invalid SymStore cache directory '{0}': {1}",
384 path, ec.message());
385 continue;
386 }
387 return path;
388 }
389
390 // Last resort is the system default location.
392}
393
394std::optional<FileSpec>
395LocateSymStoreEntry(const SymbolLocatorSymStore::LookupEntry &entry,
396 llvm::StringRef key, llvm::StringRef pdb_name) {
398 llvm::StringRef url = entry.source;
399 if (url.starts_with("http://") || url.starts_with("https://")) {
400 // Check cache first.
401 std::string cache_path = SelectSymStoreCache(entry.cache);
402 if (auto spec = FindFileInLocalSymStore(cache_path, key, pdb_name)) {
403 LLDB_LOG(log, "Found {0} in SymStore cache {1}", pdb_name, cache_path);
404 return *spec;
405 }
406
407 // Download and move to cache.
408 if (auto tmp_file = RequestFileFromSymStoreServerHTTP(url, key, pdb_name)) {
409 LLDB_LOG(log, "Downloaded {0} from SymStore {1}", pdb_name, url);
410 auto spec = MoveToLocalSymStore(cache_path, key, pdb_name, *tmp_file);
411 if (!spec) {
412 // Try the fallback and eventually rather cancel than loading the tmp
413 // file, since it might disappear or get overwritten.
415 spec = MoveToLocalSymStore(cache_path, key, pdb_name, *tmp_file);
416 if (!spec)
417 return {};
418 }
419 LLDB_LOG(log, "Added {0} to SymStore cache {1}", pdb_name, cache_path);
420 return *spec;
421 }
422
423 return {};
424 }
425
426 llvm::StringRef file = entry.source;
427 if (file.starts_with("file://"))
428 file = file.drop_front(7);
429 if (auto spec = FindFileInLocalSymStore(file, key, pdb_name)) {
430 LLDB_LOG_VERBOSE(log, "Found {0} in local SymStore {1}", pdb_name, file);
431 return *spec;
432 }
433
434 return {};
435}
436
437} // namespace
438
440 const ModuleSpec &module_spec, const FileSpecList &default_search_paths) {
441 const UUID &uuid = module_spec.GetUUID();
442 if (!uuid.IsValid() ||
443 !ModuleList::GetGlobalModuleListProperties().GetEnableExternalLookup())
444 return {};
445
447 std::string pdb_name =
448 module_spec.GetSymbolFileSpec().GetFilename().GetStringRef().str();
449 if (pdb_name.empty()) {
451 "Failed to resolve symbol PDB module: PDB name empty");
452 return {};
453 }
454
455 LLDB_LOG_VERBOSE(log, "LocateExecutableSymbolFile {0} with UUID {1}",
456 pdb_name, uuid.GetAsString());
457 if (uuid.GetBytes().size() != 20) {
458 LLDB_LOG_VERBOSE(log, "Failed to resolve symbol PDB module: UUID invalid");
459 return {};
460 }
461
462 std::string key = FormatSymStoreKey(uuid);
463 for (const LookupEntry &entry : GetGlobalLookupOrder()) {
464 if (auto spec = LocateSymStoreEntry(entry, key, pdb_name))
465 return *spec;
466 }
467
468 return {};
469}
470
471std::vector<SymbolLocatorSymStore::LookupEntry>
473 if (val.empty())
474 return {};
475
476 std::vector<LookupEntry> result;
477 std::optional<std::string> implicit_cache;
478 llvm::SmallVector<llvm::StringRef, 2> entries;
479 val.split(entries, ';');
480
481 for (llvm::StringRef raw : entries) {
482 llvm::StringRef entry = raw.trim();
483 if (entry.empty())
484 continue;
485
486 // Explicit cache directives apply to all subsequent srv* entries that don't
487 // set their own explicit cache.
488 if (entry.starts_with_insensitive("cache*")) {
489 if (auto cache = ParseCacheEntry(entry))
490 implicit_cache = *cache;
491 continue;
492 }
493
494 // SymStore directives with explicit interpreters are unsupported
495 // explicitly.
496 if (entry.starts_with_insensitive("symsrv*")) {
498 llvm::formatv("ignoring unsupported entry in env: {0}", entry));
499 continue;
500 }
501
502 // SymStore server directives may include an explicit cache.
503 // Format is: srv*[LocalCache*]SymbolStore
504 if (entry.starts_with_insensitive("srv*")) {
505 if (auto lookup_entry = ParseSrvEntry(entry)) {
506 if (!lookup_entry->cache && implicit_cache)
507 lookup_entry->cache = implicit_cache;
508 result.push_back(*lookup_entry);
509 }
510 continue;
511 }
512
513 // Plain local paths aren't cached.
514 result.push_back(MakeLookupEntry(entry));
515 }
516
517 return result;
518}
519
521 // Fall back to the platform cache directory.
522 llvm::SmallString<128> cache_dir;
523 if (llvm::sys::path::cache_directory(cache_dir)) {
524 llvm::sys::path::append(cache_dir, "lldb", "symstore");
525 return cache_dir.str().str();
526 }
527 // Last resort: use a subdirectory of the system temp directory.
528 constexpr bool erase_on_reboot = false;
529 llvm::sys::path::system_temp_directory(erase_on_reboot, cache_dir);
530 llvm::sys::path::append(cache_dir, "lldb", "symstore");
531 return cache_dir.str().str();
532}
static PluginProperties & GetGlobalPluginProperties()
#define LLDB_LOG(log,...)
The LLDB_LOG* macros defined below are the way to emit log messages.
Definition Log.h:364
#define LLDB_LOG_VERBOSE(log,...)
Definition Log.h:371
#define LLDB_PLUGIN_DEFINE(PluginName)
static PluginProperties & GetGlobalPluginProperties()
llvm::StringRef GetStringRef() const
Get the string value as a llvm::StringRef.
A class to manage flag bits.
Definition Debugger.h:100
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:57
const ConstString & GetFilename() const
Filename string const get accessor.
Definition FileSpec.h:250
size_t GetPath(char *path, size_t max_path_length, bool denormalize=true) const
Extract the full path to the file.
Definition FileSpec.cpp:374
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:327