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"
42#define LLDB_PROPERTIES_symbollocatorsymstore
43#include "SymbolLocatorSymStoreProperties.inc"
46#define LLDB_PROPERTIES_symbollocatorsymstore
47#include "SymbolLocatorSymStorePropertiesEnum.inc"
52 static llvm::StringRef GetSettingName() {
57 m_collection_sp = std::make_shared<OptionValueProperties>(GetSettingName());
58 m_collection_sp->Initialize(g_symbollocatorsymstore_properties_def);
61 Args GetURLs()
const {
63 m_collection_sp->GetPropertyAtIndexAsArgs(ePropertySymStoreURLs, urls);
67 std::string GetCachePath()
const {
68 OptionValueString *s =
69 m_collection_sp->GetPropertyAtIndexAsOptionValueString(
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);
82 std::optional<std::string> GetTLSCertFingerprint()
const {
83 OptionValueString *s =
84 m_collection_sp->GetPropertyAtIndexAsOptionValueString(
85 ePropertyTLSCertFingerprint);
91 if (val.size() != 64 || !llvm::all_of(val, llvm::isHexDigit)) {
93 "plugin.symbol-locator.symstore.tls-cert-fingerprint: expected a "
94 "64-character hex string (SHA-256), but got '{0}', ignoring",
105 static PluginProperties g_settings;
116 llvm::HTTPClient::initialize();
119 if (std::error_code ec = llvm::sys::fs::create_directories(default_cache)) {
121 "default SymStore cache directory '{0}' is not accessible: {1}",
122 default_cache, ec.message()));
128 debugger, PluginProperties::GetSettingName())) {
129 constexpr bool is_global_setting =
true;
132 "Properties for the SymStore Symbol Locator plug-in.",
139 llvm::HTTPClient::cleanup();
143 return "Symbol locator for PDB in SymStore";
154 entry.
source = source.str();
155 entry.
cache = std::nullopt;
160 llvm::StringRef cache) {
162 entry.
source = source.str();
163 entry.
cache = cache.str();
167std::vector<SymbolLocatorSymStore::LookupEntry> GetGlobalLookupOrder() {
168 std::vector<SymbolLocatorSymStore::LookupEntry> result;
170 const char *sym_path = std::getenv(
"_NT_SYMBOL_PATH");
172 result.push_back(std::move(entry));
174 const char *alt_path = std::getenv(
"_NT_ALT_SYMBOL_PATH");
176 result.push_back(std::move(entry));
179 result.push_back(MakeLookupEntry(url.ref()));
184std::optional<SymbolLocatorSymStore::LookupEntry>
185ParseSrvEntry(llvm::StringRef entry) {
186 llvm::SmallVector<llvm::StringRef, 4> parts;
187 entry.trim().split(parts,
'*');
190 switch (parts.size()) {
192 return MakeLookupEntry(parts[1]);
195 if (parts[1].empty())
196 return MakeLookupEntry(parts[2],
198 return MakeLookupEntry(parts[2], parts[1]);
205std::optional<std::string> ParseCacheEntry(llvm::StringRef entry) {
206 llvm::SmallVector<llvm::StringRef, 2> parts;
207 entry.trim().split(parts,
'*');
210 if (parts.size() > 2)
214 llvm::StringRef value;
215 if (parts.size() == 2)
216 value = parts.back();
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);
239bool HasUnsafeCharacters(llvm::StringRef s) {
240 for (
unsigned char c : s) {
242 if ((c >=
'A' && c <=
'Z') || (c >=
'a' && c <=
'z') ||
243 (c >=
'0' && c <=
'9') || c ==
'-' || c ==
'.' || c ==
'_' ||
252 return s ==
"." || s ==
"..";
255std::optional<FileSpec>
256RequestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key,
257 llvm::StringRef pdb_name) {
258 using namespace llvm::sys;
261 if (HasUnsafeCharacters(pdb_name)) {
263 "rejecting HTTP lookup for PDB file due to unsafe characters in "
269 if (!llvm::HTTPClient::isAvailable()) {
271 "HTTP client is not available for SymStore download");
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));
283 llvm::SmallString<128> tmp_file;
284 if (std::error_code ec = fs::createUniqueFile(tmp_model, tmp_file)) {
286 "failed to create a temporary file to download '{0}' into: {1}",
287 pdb_name, ec.message()));
292 llvm::scope_exit remove_tmp_file([&] { fs::remove(tmp_file.str()); });
295 std::string source_url =
296 llvm::formatv(
"{0}/{1}/{2}/{1}", base_url, pdb_name, key);
298 llvm::HTTPClient client;
302 llvm::StreamedHTTPResponseHandler Handler(
303 [dest = tmp_file.str().str()]()
304 -> llvm::Expected<std::unique_ptr<llvm::CachedFileStream>> {
306 auto os = std::make_unique<llvm::raw_fd_ostream>(dest, ec);
308 return llvm::createStringError(ec,
"Failed to open file for writing");
309 return std::make_unique<llvm::CachedFileStream>(std::move(os), dest);
313 llvm::HTTPRequest request(source_url);
314 request.PinnedCertFingerprint =
316 if (llvm::Error Err = client.perform(request, Handler)) {
318 llvm::formatv(
"failed to download from SymStore '{0}': {1}", source_url,
319 llvm::toString(std::move(Err))));
322 if (llvm::Error Err = Handler.commit()) {
324 llvm::formatv(
"failed to download from SymStore '{0}': {1}", source_url,
325 llvm::toString(std::move(Err))));
329 unsigned responseCode = client.responseCode();
330 switch (responseCode) {
332 remove_tmp_file.release();
338 "failed to download from SymStore '{0}': response code {1}", source_url,
344std::optional<FileSpec> FindFileInLocalSymStore(llvm::StringRef root_dir,
346 llvm::StringRef pdb_name) {
347 llvm::SmallString<256> path;
348 llvm::sys::path::append(path, root_dir, pdb_name, key, pdb_name);
361 DownloadLock(llvm::StringRef cache, llvm::StringRef key,
362 llvm::StringRef pdb_name)
363 : m_key(llvm::formatv(
"{0}/{1}/{2}", cache, key, pdb_name)) {
365 std::lock_guard<std::mutex> guard(GetMapMutex());
366 std::shared_ptr<Entry> &entry = GetMap()[m_key];
368 entry = std::make_shared<Entry>();
371 m_entry->mutex.lock();
375 m_entry->mutex.unlock();
376 std::lock_guard<std::mutex> guard(GetMapMutex());
378 auto it = GetMap().find(m_key);
379 if (it != GetMap().end() && it->second.use_count() == 1)
383 DownloadLock(
const DownloadLock &) =
delete;
384 DownloadLock &operator=(
const DownloadLock &) =
delete;
391 static std::mutex &GetMapMutex() {
392 static std::mutex g_mutex;
396 static llvm::StringMap<std::shared_ptr<Entry>> &GetMap() {
397 static llvm::StringMap<std::shared_ptr<Entry>> g_map;
402 std::shared_ptr<Entry> m_entry;
405std::optional<FileSpec> MoveToLocalSymStore(llvm::StringRef cache,
407 llvm::StringRef pdb_name,
410 llvm::SmallString<256> dest_dir;
411 llvm::sys::path::append(dest_dir, cache, pdb_name, key);
412 if (std::error_code ec = llvm::sys::fs::create_directories(dest_dir)) {
414 llvm::formatv(
"failed to create SymStore cache directory '{0}': {1}",
415 dest_dir, ec.message()));
419 llvm::SmallString<256> dest;
420 llvm::sys::path::append(dest, dest_dir, pdb_name);
421 std::error_code ec = llvm::sys::fs::rename(tmp_file.
GetPath(), dest);
426 if (ec == std::errc::cross_device_link) {
427 llvm::SmallString<256> staged;
428 if ((ec = llvm::sys::fs::createUniqueFile(dest +
".%%%%%%", staged))) {
430 "failed to create a temporary file in SymStore cache '{0}': {1}",
431 dest_dir, ec.message()));
434 llvm::scope_exit remove_staged([&] { llvm::sys::fs::remove(staged); });
435 if (!(ec = llvm::sys::fs::copy_file(tmp_file.
GetPath(), staged)) &&
436 !(ec = llvm::sys::fs::rename(staged, dest))) {
437 remove_staged.release();
438 llvm::sys::fs::remove(tmp_file.
GetPath());
443 llvm::formatv(
"failed to move '{0}' to SymStore cache '{1}': {2}",
444 tmp_file.
GetPath(), dest, ec.message()));
451std::string SelectSymStoreCache(std::optional<std::string> sympath_cache) {
452 llvm::SmallVector<std::string, 2> candidates;
456 assert(!sympath_cache->empty() &&
"Empty entries resolve to default cache");
457 candidates.push_back(*sympath_cache);
464 for (
const auto &path : candidates) {
465 if (llvm::sys::fs::is_directory(path))
467 if (std::error_code ec = llvm::sys::fs::create_directories(path)) {
468 LLDB_LOG(log,
"Ignoring invalid SymStore cache directory '{0}': {1}",
479std::optional<FileSpec>
481 llvm::StringRef key, llvm::StringRef pdb_name) {
483 llvm::StringRef url = entry.
source;
484 if (url.starts_with(
"http://") || url.starts_with(
"https://")) {
485 std::string cache_path = SelectSymStoreCache(entry.
cache);
490 DownloadLock lock(cache_path, key, pdb_name);
493 if (
auto spec = FindFileInLocalSymStore(cache_path, key, pdb_name)) {
494 LLDB_LOG(log,
"Found {0} in SymStore cache {1}", pdb_name, cache_path);
499 if (
auto tmp_file = RequestFileFromSymStoreServerHTTP(url, key, pdb_name)) {
500 LLDB_LOG(log,
"Downloaded {0} from SymStore {1}", pdb_name, url);
501 auto spec = MoveToLocalSymStore(cache_path, key, pdb_name, *tmp_file);
506 spec = MoveToLocalSymStore(cache_path, key, pdb_name, *tmp_file);
510 LLDB_LOG(log,
"Added {0} to SymStore cache {1}", pdb_name, cache_path);
517 llvm::StringRef file = entry.
source;
518 if (file.starts_with(
"file://"))
519 file = file.drop_front(7);
520 if (
auto spec = FindFileInLocalSymStore(file, key, pdb_name)) {
521 LLDB_LOG(log,
"Found {0} in local SymStore {1}", pdb_name, file);
539 if (pdb_name.empty()) {
540 LLDB_LOG(log,
"Failed to resolve symbol PDB module: PDB name empty");
544 LLDB_LOG(log,
"LocateExecutableSymbolFile {0} with UUID {1}", pdb_name,
547 LLDB_LOG(log,
"Failed to resolve symbol PDB module: UUID invalid");
551 std::string key = FormatSymStoreKey(uuid);
552 for (
const LookupEntry &entry : GetGlobalLookupOrder()) {
553 if (
auto spec = LocateSymStoreEntry(entry, key, pdb_name))
560std::vector<SymbolLocatorSymStore::LookupEntry>
565 std::vector<LookupEntry> result;
566 std::optional<std::string> implicit_cache;
567 llvm::SmallVector<llvm::StringRef, 2> entries;
568 val.split(entries,
';');
570 for (llvm::StringRef raw : entries) {
571 llvm::StringRef entry = raw.trim();
577 if (entry.starts_with_insensitive(
"cache*")) {
578 if (
auto cache = ParseCacheEntry(entry))
579 implicit_cache = *cache;
585 if (entry.starts_with_insensitive(
"symsrv*")) {
587 llvm::formatv(
"ignoring unsupported entry in env: {0}", entry));
593 if (entry.starts_with_insensitive(
"srv*")) {
594 if (
auto lookup_entry = ParseSrvEntry(entry)) {
595 if (!lookup_entry->cache && implicit_cache)
596 lookup_entry->cache = implicit_cache;
597 result.push_back(*lookup_entry);
603 result.push_back(MakeLookupEntry(entry));
611 llvm::SmallString<128> cache_dir;
612 if (llvm::sys::path::cache_directory(cache_dir)) {
613 llvm::sys::path::append(cache_dir,
"lldb",
"symstore");
614 return cache_dir.str().str();
617 constexpr bool erase_on_reboot =
false;
618 llvm::sys::path::system_temp_directory(erase_on_reboot, cache_dir);
619 llvm::sys::path::append(cache_dir,
"lldb",
"symstore");
620 return cache_dir.str().str();
static PluginProperties & GetGlobalPluginProperties()
#define LLDB_LOG(log,...)
The LLDB_LOG* macros defined below are the way to emit log messages.
#define LLDB_PLUGIN_DEFINE(PluginName)
static PluginProperties & GetGlobalPluginProperties()
A class to manage flag bits.
static void ReportWarning(std::string message, std::optional< lldb::user_id_t > debugger_id=std::nullopt, std::once_flag *>
Report warning events.
llvm::StringRef GetFilename() const
Filename string const get accessor.
size_t GetPath(char *path, size_t max_path_length, bool denormalize=true) const
Extract the full path to the file.
static FileSystem & Instance()
static ModuleListProperties & GetGlobalModuleListProperties()
FileSpec & GetSymbolFileSpec()
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::string GetSystemDefaultCachePath()
static std::vector< LookupEntry > ParseEnvSymbolPaths(llvm::StringRef val)
Represents UUID's of various sizes.
llvm::ArrayRef< uint8_t > GetBytes() const
std::string GetAsString(llvm::StringRef separator="-") const
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.
std::optional< std::string > cache