blob: bc042af4ca3e24258defacc8c5d97b11d34c4ba3 [file]
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "SymbolLocatorSymStore.h"
#include "lldb/Core/ModuleList.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Interpreter/OptionValueString.h"
#include "lldb/Utility/Args.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/UUID.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/HTTP/HTTPClient.h"
#include "llvm/Support/Path.h"
using namespace lldb;
using namespace lldb_private;
LLDB_PLUGIN_DEFINE(SymbolLocatorSymStore)
namespace {
#define LLDB_PROPERTIES_symbollocatorsymstore
#include "SymbolLocatorSymStoreProperties.inc"
enum {
#define LLDB_PROPERTIES_symbollocatorsymstore
#include "SymbolLocatorSymStorePropertiesEnum.inc"
};
class PluginProperties : public Properties {
public:
static llvm::StringRef GetSettingName() {
return SymbolLocatorSymStore::GetPluginNameStatic();
}
PluginProperties() {
m_collection_sp = std::make_shared<OptionValueProperties>(GetSettingName());
m_collection_sp->Initialize(g_symbollocatorsymstore_properties_def);
}
Args GetURLs() const {
Args urls;
m_collection_sp->GetPropertyAtIndexAsArgs(ePropertySymStoreURLs, urls);
return urls;
}
};
} // namespace
static PluginProperties &GetGlobalPluginProperties() {
static PluginProperties g_settings;
return g_settings;
}
SymbolLocatorSymStore::SymbolLocatorSymStore() : SymbolLocator() {}
void SymbolLocatorSymStore::Initialize() {
PluginManager::RegisterPlugin(
GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance,
nullptr, LocateExecutableSymbolFile, nullptr, nullptr,
SymbolLocatorSymStore::DebuggerInitialize);
llvm::HTTPClient::initialize();
}
void SymbolLocatorSymStore::DebuggerInitialize(Debugger &debugger) {
if (!PluginManager::GetSettingForSymbolLocatorPlugin(
debugger, PluginProperties::GetSettingName())) {
constexpr bool is_global_setting = true;
PluginManager::CreateSettingForSymbolLocatorPlugin(
debugger, GetGlobalPluginProperties().GetValueProperties(),
"Properties for the SymStore Symbol Locator plug-in.",
is_global_setting);
}
}
void SymbolLocatorSymStore::Terminate() {
PluginManager::UnregisterPlugin(CreateInstance);
llvm::HTTPClient::cleanup();
}
llvm::StringRef SymbolLocatorSymStore::GetPluginDescriptionStatic() {
return "Symbol locator for PDB in SymStore";
}
SymbolLocator *SymbolLocatorSymStore::CreateInstance() {
return new SymbolLocatorSymStore();
}
namespace {
// RSDS entries store identity as a 20-byte UUID composed of 16-byte GUID and
// 4-byte age:
// 12345678-1234-5678-9ABC-DEF012345678-00000001
//
// SymStore key is a string with no separators and age as decimal:
// 12345678123456789ABCDEF0123456781
//
std::string formatSymStoreKey(const UUID &uuid) {
llvm::ArrayRef<uint8_t> bytes = uuid.GetBytes();
uint32_t age = llvm::support::endian::read32be(bytes.data() + 16);
constexpr bool LowerCase = false;
return llvm::toHex(bytes.slice(0, 16), LowerCase) + std::to_string(age);
}
// This is a simple version of Debuginfod's StreamedHTTPResponseHandler. We
// should consider reusing that once we introduce caching.
class FileDownloadHandler : public llvm::HTTPResponseHandler {
private:
std::error_code m_ec;
llvm::raw_fd_ostream m_stream;
public:
FileDownloadHandler(llvm::StringRef file) : m_stream(file.str(), m_ec) {}
virtual ~FileDownloadHandler() = default;
llvm::Error handleBodyChunk(llvm::StringRef data) override {
// Propagate error from ctor.
if (m_ec)
return llvm::createStringError(m_ec, "Failed to open file for writing");
m_stream.write(data.data(), data.size());
if (std::error_code ec = m_stream.error())
return llvm::createStringError(ec, "Error writing to file");
return llvm::Error::success();
}
};
llvm::Error downloadFileHTTP(llvm::StringRef url, FileDownloadHandler dest) {
if (!llvm::HTTPClient::isAvailable())
return llvm::createStringError(
std::make_error_code(std::errc::not_supported),
"HTTP client is not available");
llvm::HTTPRequest Request(url);
Request.FollowRedirects = true;
llvm::HTTPClient Client;
// TODO: Since PDBs can be huge, we should distinguish between resolve,
// connect, send and receive.
Client.setTimeout(std::chrono::seconds(60));
if (llvm::Error Err = Client.perform(Request, dest))
return Err;
unsigned ResponseCode = Client.responseCode();
if (ResponseCode != 200) {
return llvm::createStringError(std::make_error_code(std::errc::io_error),
"HTTP request failed with status code " +
std::to_string(ResponseCode));
}
return llvm::Error::success();
}
bool has_unsafe_characters(llvm::StringRef s) {
for (unsigned char c : s) {
// RFC 3986 unreserved characters are safe for file names and URLs.
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' ||
c == '~') {
continue;
}
return true;
}
// Avoid path semantics issues.
return s == "." || s == "..";
}
// TODO: This is a dump initial implementation: It always downloads the file, it
// doesn't validate the result, it doesn't employ proper buffering for large
// files.
std::optional<FileSpec>
requestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key,
llvm::StringRef pdb_name) {
using namespace llvm::sys;
Log *log = GetLog(LLDBLog::Symbols);
// Make sure URL will be valid, portable, and compatible with symbol servers.
if (has_unsafe_characters(pdb_name)) {
Debugger::ReportWarning(llvm::formatv(
"rejecting HTTP lookup for PDB file due to unsafe characters in "
"name: {0}",
pdb_name));
return {};
}
// Download into a temporary file. Cache coming soon.
llvm::SmallString<128> tmp_file;
std::string tmp_file_name =
llvm::formatv("lldb_symstore_{0}_{1}", key, pdb_name);
constexpr bool erase_on_reboot = true;
path::system_temp_directory(erase_on_reboot, tmp_file);
path::append(tmp_file, tmp_file_name);
// Server has SymStore directory structure with forward slashes as separators.
std::string source_url =
llvm::formatv("{0}/{1}/{2}/{1}", base_url, pdb_name, key);
if (llvm::Error err = downloadFileHTTP(source_url, tmp_file.str())) {
LLDB_LOG_ERROR(log, std::move(err),
"Failed to download from SymStore '{1}': {0}", source_url);
return {};
}
return FileSpec(tmp_file.str());
}
std::optional<FileSpec> findFileInLocalSymStore(llvm::StringRef root_dir,
llvm::StringRef key,
llvm::StringRef pdb_name) {
llvm::SmallString<256> path;
llvm::sys::path::append(path, root_dir, pdb_name, key, pdb_name);
FileSpec spec(path);
if (!FileSystem::Instance().Exists(spec))
return {};
return spec;
}
std::optional<FileSpec> locateSymStoreEntry(llvm::StringRef base_url,
llvm::StringRef key,
llvm::StringRef pdb_name) {
if (base_url.starts_with("http://") || base_url.starts_with("https://"))
return requestFileFromSymStoreServerHTTP(base_url, key, pdb_name);
if (base_url.starts_with("file://"))
base_url = base_url.drop_front(7);
return findFileInLocalSymStore(base_url, key, pdb_name);
}
} // namespace
std::optional<FileSpec> SymbolLocatorSymStore::LocateExecutableSymbolFile(
const ModuleSpec &module_spec, const FileSpecList &default_search_paths) {
const UUID &uuid = module_spec.GetUUID();
if (!uuid.IsValid() ||
!ModuleList::GetGlobalModuleListProperties().GetEnableExternalLookup())
return {};
Log *log = GetLog(LLDBLog::Symbols);
std::string pdb_name =
module_spec.GetSymbolFileSpec().GetFilename().GetStringRef().str();
if (pdb_name.empty()) {
LLDB_LOG_VERBOSE(log,
"Failed to resolve symbol PDB module: PDB name empty");
return {};
}
LLDB_LOG_VERBOSE(log, "LocateExecutableSymbolFile {0} with UUID {1}",
pdb_name, uuid.GetAsString());
if (uuid.GetBytes().size() != 20) {
LLDB_LOG_VERBOSE(log, "Failed to resolve symbol PDB module: UUID invalid");
return {};
}
std::string key = formatSymStoreKey(uuid);
Args sym_store_urls = GetGlobalPluginProperties().GetURLs();
for (const Args::ArgEntry &url : sym_store_urls) {
if (auto spec = locateSymStoreEntry(url.ref(), key, pdb_name)) {
LLDB_LOG_VERBOSE(log, "Found {0} in SymStore {1}", pdb_name, url.ref());
return *spec;
}
}
return {};
}