[Lldb-commits] [lldb] [llvm] [lldb] Add HTTP support in SymbolLocatorSymStore (PR #186986)
Stefan Gränitz via lldb-commits
lldb-commits at lists.llvm.org
Wed Mar 18 08:52:42 PDT 2026
https://github.com/weliveindetail updated https://github.com/llvm/llvm-project/pull/186986
>From 7c9d0094de26760c790b857d6c916953f96df6f4 Mon Sep 17 00:00:00 2001
From: Alexandre Ganea <aganea at havenstudios.com>
Date: Wed, 4 Mar 2026 10:10:59 +0100
Subject: [PATCH 1/7] Add WinHTTP-based client on Windows
---
llvm/include/llvm/Support/HTTP/HTTPClient.h | 4 +-
llvm/lib/Support/HTTP/CMakeLists.txt | 5 +
llvm/lib/Support/HTTP/HTTPClient.cpp | 206 ++++++++++++++++++++
3 files changed, 212 insertions(+), 3 deletions(-)
diff --git a/llvm/include/llvm/Support/HTTP/HTTPClient.h b/llvm/include/llvm/Support/HTTP/HTTPClient.h
index aa4727ad33024..594995505a40e 100644
--- a/llvm/include/llvm/Support/HTTP/HTTPClient.h
+++ b/llvm/include/llvm/Support/HTTP/HTTPClient.h
@@ -51,9 +51,7 @@ class HTTPResponseHandler {
/// A reusable client that can perform HTTPRequests through a network socket.
class HTTPClient {
-#ifdef LLVM_ENABLE_CURL
- void *Curl = nullptr;
-#endif
+ void *Handle = nullptr;
public:
HTTPClient();
diff --git a/llvm/lib/Support/HTTP/CMakeLists.txt b/llvm/lib/Support/HTTP/CMakeLists.txt
index 9bf1da8c60c88..e7a0e6fe34110 100644
--- a/llvm/lib/Support/HTTP/CMakeLists.txt
+++ b/llvm/lib/Support/HTTP/CMakeLists.txt
@@ -8,6 +8,11 @@ if (LLVM_ENABLE_HTTPLIB)
set(imported_libs ${imported_libs} httplib::httplib)
endif()
+# Use WinHTTP on Windows
+if (WIN32)
+ set(imported_libs ${imported_libs} winhttp.lib)
+endif()
+
add_llvm_component_library(LLVMSupportHTTP
HTTPClient.cpp
HTTPServer.cpp
diff --git a/llvm/lib/Support/HTTP/HTTPClient.cpp b/llvm/lib/Support/HTTP/HTTPClient.cpp
index 6301f86da4086..ec1961cb26658 100644
--- a/llvm/lib/Support/HTTP/HTTPClient.cpp
+++ b/llvm/lib/Support/HTTP/HTTPClient.cpp
@@ -140,6 +140,210 @@ unsigned HTTPClient::responseCode() {
#else
+#ifdef _WIN32
+#include <windows.h>
+#include <winhttp.h>
+#pragma comment(lib, "winhttp.lib")
+
+namespace {
+
+struct WinHTTPSession {
+ HINTERNET SessionHandle = nullptr;
+ HINTERNET ConnectHandle = nullptr;
+ HINTERNET RequestHandle = nullptr;
+ DWORD ResponseCode = 0;
+
+ ~WinHTTPSession() {
+ if (RequestHandle)
+ WinHttpCloseHandle(RequestHandle);
+ if (ConnectHandle)
+ WinHttpCloseHandle(ConnectHandle);
+ if (SessionHandle)
+ WinHttpCloseHandle(SessionHandle);
+ }
+};
+
+bool convertUTF8ToWide(StringRef Utf8, std::wstring &Wide) {
+ int WideLen =
+ MultiByteToWideChar(CP_UTF8, 0, Utf8.data(), Utf8.size(), nullptr, 0);
+ if (WideLen <= 0)
+ return false;
+ Wide.resize(WideLen);
+ MultiByteToWideChar(CP_UTF8, 0, Utf8.data(), Utf8.size(), &Wide[0], WideLen);
+ return true;
+}
+
+bool parseURL(StringRef Url, std::wstring &Host, std::wstring &Path,
+ INTERNET_PORT &Port, bool &Secure) {
+ // Parse URL: http://host:port/path
+ if (Url.starts_with("https://")) {
+ Secure = true;
+ Url = Url.drop_front(8);
+ } else if (Url.starts_with("http://")) {
+ Secure = false;
+ Url = Url.drop_front(7);
+ } else {
+ return false;
+ }
+
+ size_t SlashPos = Url.find('/');
+ StringRef HostPort =
+ (SlashPos != StringRef::npos) ? Url.substr(0, SlashPos) : Url;
+ StringRef PathPart =
+ (SlashPos != StringRef::npos) ? Url.substr(SlashPos) : StringRef("/");
+
+ size_t ColonPos = HostPort.find(':');
+ StringRef HostStr =
+ (ColonPos != StringRef::npos) ? HostPort.substr(0, ColonPos) : HostPort;
+
+ if (!convertUTF8ToWide(HostStr, Host))
+ return false;
+ if (!convertUTF8ToWide(PathPart, Path))
+ return false;
+
+ if (ColonPos != StringRef::npos) {
+ StringRef PortStr = HostPort.substr(ColonPos + 1);
+ Port = static_cast<INTERNET_PORT>(std::stoi(PortStr.str()));
+ } else {
+ Port = Secure ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT;
+ }
+
+ return true;
+}
+
+} // namespace
+
+HTTPClient::HTTPClient() : Handle(new WinHTTPSession()) {}
+
+HTTPClient::~HTTPClient() { delete static_cast<WinHTTPSession *>(Handle); }
+
+bool HTTPClient::isAvailable() { return true; }
+
+void HTTPClient::initialize() {
+ if (!IsInitialized) {
+ IsInitialized = true;
+ }
+}
+
+void HTTPClient::cleanup() {
+ if (IsInitialized) {
+ IsInitialized = false;
+ }
+}
+
+void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {
+ WinHTTPSession *Session = static_cast<WinHTTPSession *>(Handle);
+ if (Session && Session->SessionHandle) {
+ DWORD TimeoutMs = static_cast<DWORD>(Timeout.count());
+ WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_CONNECT_TIMEOUT,
+ &TimeoutMs, sizeof(TimeoutMs));
+ WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_RECEIVE_TIMEOUT,
+ &TimeoutMs, sizeof(TimeoutMs));
+ WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_SEND_TIMEOUT,
+ &TimeoutMs, sizeof(TimeoutMs));
+ }
+}
+
+Error HTTPClient::perform(const HTTPRequest &Request,
+ HTTPResponseHandler &Handler) {
+ if (Request.Method != HTTPMethod::GET)
+ return createStringError(errc::invalid_argument,
+ "Only GET requests are supported.");
+
+ WinHTTPSession *Session = static_cast<WinHTTPSession *>(Handle);
+
+ // Parse URL
+ std::wstring Host, Path;
+ INTERNET_PORT Port = 0;
+ bool Secure = false;
+ if (!parseURL(Request.Url, Host, Path, Port, Secure))
+ return createStringError(errc::invalid_argument,
+ "Invalid URL: " + Request.Url);
+
+ // Create session
+ Session->SessionHandle =
+ WinHttpOpen(L"LLVM-HTTPClient/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
+ WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
+ if (!Session->SessionHandle)
+ return createStringError(errc::io_error, "Failed to open WinHTTP session");
+
+ // Create connection
+ Session->ConnectHandle =
+ WinHttpConnect(Session->SessionHandle, Host.c_str(), Port, 0);
+ if (!Session->ConnectHandle) {
+ return createStringError(errc::io_error,
+ "Failed to connect to host: " + Request.Url);
+ }
+
+ // Open request
+ DWORD Flags = WINHTTP_FLAG_REFRESH;
+ if (Secure)
+ Flags |= WINHTTP_FLAG_SECURE;
+
+ Session->RequestHandle = WinHttpOpenRequest(
+ Session->ConnectHandle, L"GET", Path.c_str(), nullptr, WINHTTP_NO_REFERER,
+ WINHTTP_DEFAULT_ACCEPT_TYPES, Flags);
+ if (!Session->RequestHandle)
+ return createStringError(errc::io_error, "Failed to open HTTP request");
+
+ // Add headers
+ for (const std::string &Header : Request.Headers) {
+ std::wstring WideHeader;
+ if (!convertUTF8ToWide(Header, WideHeader))
+ continue;
+ WinHttpAddRequestHeaders(Session->RequestHandle, WideHeader.c_str(),
+ static_cast<DWORD>(WideHeader.length()),
+ WINHTTP_ADDREQ_FLAG_ADD);
+ }
+
+ // Send request
+ if (!WinHttpSendRequest(Session->RequestHandle, WINHTTP_NO_ADDITIONAL_HEADERS,
+ 0, nullptr, 0, 0, 0))
+ return createStringError(errc::io_error, "Failed to send HTTP request");
+
+ // Receive response
+ if (!WinHttpReceiveResponse(Session->RequestHandle, nullptr))
+ return createStringError(errc::io_error, "Failed to receive HTTP response");
+
+ // Get response code
+ DWORD CodeSize = sizeof(Session->ResponseCode);
+ if (!WinHttpQueryHeaders(Session->RequestHandle,
+ WINHTTP_QUERY_STATUS_CODE |
+ WINHTTP_QUERY_FLAG_NUMBER,
+ WINHTTP_HEADER_NAME_BY_INDEX, &Session->ResponseCode,
+ &CodeSize, nullptr))
+ Session->ResponseCode = 0;
+
+ // Read response body
+ DWORD BytesAvailable = 0;
+ while (WinHttpQueryDataAvailable(Session->RequestHandle, &BytesAvailable)) {
+ if (BytesAvailable == 0)
+ break;
+
+ std::vector<char> Buffer(BytesAvailable);
+ DWORD BytesRead = 0;
+ if (!WinHttpReadData(Session->RequestHandle, Buffer.data(), BytesAvailable,
+ &BytesRead))
+ return createStringError(errc::io_error, "Failed to read HTTP response");
+
+ if (BytesRead > 0) {
+ if (Error Err =
+ Handler.handleBodyChunk(StringRef(Buffer.data(), BytesRead)))
+ return Err;
+ }
+ }
+
+ return Error::success();
+}
+
+unsigned HTTPClient::responseCode() {
+ WinHTTPSession *Session = static_cast<WinHTTPSession *>(Handle);
+ return Session ? Session->ResponseCode : 0;
+}
+
+#else // _WIN32
+
+// Non-Windows, non-libcurl stub implementations
HTTPClient::HTTPClient() = default;
HTTPClient::~HTTPClient() = default;
@@ -161,4 +365,6 @@ unsigned HTTPClient::responseCode() {
llvm_unreachable("No HTTP Client implementation available.");
}
+#endif // _WIN32
+
#endif
>From 6d7d6f1710afb8d1a614f8e2ce08bcb3457d7250 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Wed, 4 Mar 2026 13:27:52 +0100
Subject: [PATCH 2/7] Apply variable renaming in CURL based implementation
---
llvm/lib/Support/HTTP/HTTPClient.cpp | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/llvm/lib/Support/HTTP/HTTPClient.cpp b/llvm/lib/Support/HTTP/HTTPClient.cpp
index ec1961cb26658..43d688cddecec 100644
--- a/llvm/lib/Support/HTTP/HTTPClient.cpp
+++ b/llvm/lib/Support/HTTP/HTTPClient.cpp
@@ -64,7 +64,7 @@ void HTTPClient::cleanup() {
void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {
if (Timeout < std::chrono::milliseconds(0))
Timeout = std::chrono::milliseconds(0);
- curl_easy_setopt(Curl, CURLOPT_TIMEOUT_MS, Timeout.count());
+ curl_easy_setopt(Handle, CURLOPT_TIMEOUT_MS, Timeout.count());
}
/// CurlHTTPRequest and the curl{Header,Write}Function are implementation
@@ -93,17 +93,17 @@ static size_t curlWriteFunction(char *Contents, size_t Size, size_t NMemb,
HTTPClient::HTTPClient() {
assert(IsInitialized &&
"Must call HTTPClient::initialize() at the beginning of main().");
- if (Curl)
+ if (Handle)
return;
- Curl = curl_easy_init();
- assert(Curl && "Curl could not be initialized");
+ Handle = curl_easy_init();
+ assert(Handle && "Curl could not be initialized");
// Set the callback hooks.
- curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, curlWriteFunction);
+ curl_easy_setopt(Handle, CURLOPT_WRITEFUNCTION, curlWriteFunction);
// Detect supported compressed encodings and accept all.
- curl_easy_setopt(Curl, CURLOPT_ACCEPT_ENCODING, "");
+ curl_easy_setopt(Handle, CURLOPT_ACCEPT_ENCODING, "");
}
-HTTPClient::~HTTPClient() { curl_easy_cleanup(Curl); }
+HTTPClient::~HTTPClient() { curl_easy_cleanup(Handle); }
Error HTTPClient::perform(const HTTPRequest &Request,
HTTPResponseHandler &Handler) {
@@ -112,17 +112,17 @@ Error HTTPClient::perform(const HTTPRequest &Request,
"Unsupported CURL request method.");
SmallString<128> Url = Request.Url;
- curl_easy_setopt(Curl, CURLOPT_URL, Url.c_str());
- curl_easy_setopt(Curl, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects);
+ curl_easy_setopt(Handle, CURLOPT_URL, Url.c_str());
+ curl_easy_setopt(Handle, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects);
curl_slist *Headers = nullptr;
for (const std::string &Header : Request.Headers)
Headers = curl_slist_append(Headers, Header.c_str());
- curl_easy_setopt(Curl, CURLOPT_HTTPHEADER, Headers);
+ curl_easy_setopt(Handle, CURLOPT_HTTPHEADER, Headers);
CurlHTTPRequest CurlRequest(Handler);
- curl_easy_setopt(Curl, CURLOPT_WRITEDATA, &CurlRequest);
- CURLcode CurlRes = curl_easy_perform(Curl);
+ curl_easy_setopt(Handle, CURLOPT_WRITEDATA, &CurlRequest);
+ CURLcode CurlRes = curl_easy_perform(Handle);
curl_slist_free_all(Headers);
if (CurlRes != CURLE_OK)
return joinErrors(std::move(CurlRequest.ErrorState),
@@ -134,7 +134,7 @@ Error HTTPClient::perform(const HTTPRequest &Request,
unsigned HTTPClient::responseCode() {
long Code = 0;
- curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &Code);
+ curl_easy_getinfo(Handle, CURLINFO_RESPONSE_CODE, &Code);
return Code;
}
>From 595b712cd346d909e63e0154649be8864f5be625 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Wed, 4 Mar 2026 13:30:30 +0100
Subject: [PATCH 3/7] Cover WinHTTP based implementation in new test variant of
llvm-debuginfod-find
---
.../{headers.test => headers-curl.test} | 0
.../llvm-debuginfod-find/headers-winhttp.test | 31 +++++++++++++++++++
2 files changed, 31 insertions(+)
rename llvm/test/tools/llvm-debuginfod-find/{headers.test => headers-curl.test} (100%)
create mode 100644 llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test
diff --git a/llvm/test/tools/llvm-debuginfod-find/headers.test b/llvm/test/tools/llvm-debuginfod-find/headers-curl.test
similarity index 100%
rename from llvm/test/tools/llvm-debuginfod-find/headers.test
rename to llvm/test/tools/llvm-debuginfod-find/headers-curl.test
diff --git a/llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test b/llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test
new file mode 100644
index 0000000000000..96a73c6bf373a
--- /dev/null
+++ b/llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test
@@ -0,0 +1,31 @@
+REQUIRES: system-windows
+
+RUN: rm -rf %t
+RUN: mkdir -p %t/debuginfod-cache
+RUN: %python %S/Inputs/capture_req.py llvm-debuginfod-find --debuginfo 0 \
+RUN: | FileCheck --check-prefix NO-HEADERS %s
+RUN: env DEBUGINFOD_CACHE=%t/debuginfod-cache DEBUGINFOD_HEADERS_FILE=bad %python %S/Inputs/capture_req.py \
+RUN: llvm-debuginfod-find --debuginfo 0 \
+RUN: | FileCheck --check-prefix NO-HEADERS %s
+RUN: rm -rf %t/debuginfod-cache/*
+RUN: env DEBUGINFOD_CACHE=%t/debuginfod-cache DEBUGINFOD_HEADERS_FILE=%S/Inputs/headers %python %S/Inputs/capture_req.py \
+RUN: llvm-debuginfod-find --debuginfo 0 \
+RUN: | FileCheck --check-prefix HEADERS %s
+RUN: rm -rf %t/debuginfod-cache/*
+RUN: env DEBUGINFOD_CACHE=%t/debuginfod-cache DEBUGINFOD_HEADERS_FILE=%S/Inputs/headers DEBUGINFOD_URLS=fake not llvm-debuginfod-find --debuginfo 0 2>&1 \
+RUN: | FileCheck --check-prefix ERR -DHEADER_FILE=%S/Inputs/headers %s
+
+NO-HEADERS: User-Agent: LLVM-HTTPClient/1.0
+NO-HEADERS-NEXT: Host: localhost:{{[0-9]+}}
+
+HEADERS: User-Agent: LLVM-HTTPClient/1.0
+HEADERS-NEXT: A: B
+HEADERS-NEXT: C: D
+HEADERS-NEXT: E: F
+HEADERS-NEXT: hi!$: j k
+HEADERS-NEXT: Host: localhost:{{[0-9]+}}
+
+ERR: warning: could not parse debuginfod header: [[HEADER_FILE]]:3
+ERR-NEXT: warning: could not parse debuginfod header: [[HEADER_FILE]]:4
+ERR-NEXT: warning: could not parse debuginfod header: [[HEADER_FILE]]:5
+ERR-NEXT: warning: could not parse debuginfod header: [[HEADER_FILE]]:6
>From 41d6a45e72735f0d6ea6d530f063c7f87a9ca2de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Thu, 12 Mar 2026 17:16:49 +0100
Subject: [PATCH 4/7] [lldb] Add HTTP support in SymbolLocatorSymStore
---
.../SymbolLocator/SymStore/CMakeLists.txt | 1 +
.../SymStore/SymbolLocatorSymStore.cpp | 132 ++++++++++++++++--
.../{TestSymStoreLocal.py => TestSymStore.py} | 42 +++++-
3 files changed, 162 insertions(+), 13 deletions(-)
rename lldb/test/API/symstore/{TestSymStoreLocal.py => TestSymStore.py} (75%)
diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt b/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt
index b0da27f26c6a8..775e0284cd8af 100644
--- a/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt
@@ -13,6 +13,7 @@ add_lldb_library(lldbPluginSymbolLocatorSymStore PLUGIN
lldbCore
lldbHost
lldbSymbol
+ LLVMSupportHTTP
)
add_dependencies(lldbPluginSymbolLocatorSymStore
diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
index d008a7d3e8e9a..7663120984239 100644
--- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
@@ -20,6 +20,8 @@
#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;
@@ -65,11 +67,15 @@ static PluginProperties &GetGlobalPluginProperties() {
SymbolLocatorSymStore::SymbolLocatorSymStore() : SymbolLocator() {}
void SymbolLocatorSymStore::Initialize() {
- // First version can only locate PDB in local SymStore (no download yet).
- PluginManager::RegisterPlugin(
- GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance,
- nullptr, LocateExecutableSymbolFile, nullptr, nullptr,
- SymbolLocatorSymStore::DebuggerInitialize);
+ static llvm::once_flag g_once_flag;
+
+ llvm::call_once(g_once_flag, []() {
+ PluginManager::RegisterPlugin(
+ GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance,
+ nullptr, LocateExecutableSymbolFile, nullptr, nullptr,
+ SymbolLocatorSymStore::DebuggerInitialize);
+ llvm::HTTPClient::initialize();
+ });
}
void SymbolLocatorSymStore::DebuggerInitialize(Debugger &debugger) {
@@ -85,6 +91,7 @@ void SymbolLocatorSymStore::DebuggerInitialize(Debugger &debugger) {
void SymbolLocatorSymStore::Terminate() {
PluginManager::UnregisterPlugin(CreateInstance);
+ llvm::HTTPClient::cleanup();
}
llvm::StringRef SymbolLocatorSymStore::GetPluginDescriptionStatic() {
@@ -95,6 +102,8 @@ 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
@@ -102,13 +111,115 @@ SymbolLocator *SymbolLocatorSymStore::CreateInstance() {
// SymStore key is a string with no separators and age as decimal:
// 12345678123456789ABCDEF0123456781
//
-static std::string formatSymStoreKey(const UUID &uuid) {
+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;
+ 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();
+}
+
+std::optional<FileSpec>
+requestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key,
+ llvm::StringRef pdb_name) {
+ using namespace llvm::sys;
+ Log *log = GetLog(LLDBLog::Symbols);
+
+ // Construct the path for local storage. Configurable cache coming soon.
+ llvm::SmallString<128> cache_file;
+ if (!path::cache_directory(cache_file)) {
+ LLDB_LOGV(log, "Failed to determine cache directory for SymStore");
+ return {};
+ }
+ path::append(cache_file, "lldb", "SymStore", pdb_name, key);
+ if (std::error_code ec = fs::create_directories(cache_file)) {
+ LLDB_LOG(log, "Failed to create temporary folder '{0}': {1}", cache_file,
+ ec.message());
+ return {};
+ }
+ path::append(cache_file, pdb_name);
+
+ // Server has same 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, cache_file.str())) {
+ LLDB_LOG_ERROR(log, std::move(err),
+ " Failed to download from SymStore '{1}': {0}", source_url);
+ return {};
+ }
+
+ return FileSpec(cache_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);
+
+ 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();
@@ -134,12 +245,9 @@ std::optional<FileSpec> SymbolLocatorSymStore::LocateExecutableSymbolFile(
std::string key = formatSymStoreKey(uuid);
Args sym_store_urls = GetGlobalPluginProperties().GetURLs();
for (const Args::ArgEntry &url : sym_store_urls) {
- llvm::SmallString<256> path;
- llvm::sys::path::append(path, url.ref(), pdb_name, key, pdb_name);
- FileSpec spec(path);
- if (FileSystem::Instance().Exists(spec)) {
- LLDB_LOGV(log, "Found {0} in SymStore {1}", pdb_name, url.ref());
- return spec;
+ if (auto spec = locateSymStoreEntry(url.ref(), key, pdb_name)) {
+ LLDB_LOGV(log, " Found {0} in SymStore {1}", pdb_name, url.ref());
+ return *spec;
}
}
diff --git a/lldb/test/API/symstore/TestSymStoreLocal.py b/lldb/test/API/symstore/TestSymStore.py
similarity index 75%
rename from lldb/test/API/symstore/TestSymStoreLocal.py
rename to lldb/test/API/symstore/TestSymStore.py
index 98569d2b8c66f..4fcac80ee546a 100644
--- a/lldb/test/API/symstore/TestSymStoreLocal.py
+++ b/lldb/test/API/symstore/TestSymStore.py
@@ -1,5 +1,9 @@
+import http.server
import os
import shutil
+import socketserver
+import threading
+from functools import partial
import lldb
from lldbsuite.test.decorators import *
@@ -62,7 +66,31 @@ def __exit__(self, *exc_info):
self._test.runCmd("settings clear plugin.symbol-locator.symstore")
-class SymStoreLocalTests(TestBase):
+class HTTPServer:
+ """
+ Context Manager to serve a local directory tree via HTTP.
+ """
+
+ def __init__(self, dir):
+ address = ("localhost", 0) # auto-select free port
+ handler = partial(http.server.SimpleHTTPRequestHandler, directory=dir)
+ self._server = socketserver.ThreadingTCPServer(address, handler)
+ self._thread = threading.Thread(target=self._server.serve_forever, daemon=True)
+
+ def __enter__(self):
+ self._thread.start()
+ host, port = self._server.server_address
+ return f"http://{host}:{port}"
+
+ def __exit__(self, *exc_info):
+ if self._server:
+ self._server.shutdown()
+ self._server.server_close()
+ if self._thread:
+ self._thread.join()
+
+
+class SymStoreTests(TestBase):
SHARED_BUILD_TESTCASE = False
TEST_WITH_PDB_DEBUG_INFO = True
@@ -115,3 +143,15 @@ def test_local_dir(self):
f"settings set plugin.symbol-locator.symstore.urls {symstore_dir}"
)
self.try_breakpoint(exe, should_have_loc=True)
+
+ def test_http(self):
+ """
+ Check that breakpoint hits with remote SymStore.
+ """
+ exe, sym = self.build_inferior()
+ with MockedSymStore(self, exe, sym) as symstore_dir:
+ with HTTPServer(symstore_dir) as url:
+ self.runCmd(
+ f"settings set plugin.symbol-locator.symstore.urls {url}"
+ )
+ self.try_breakpoint(exe, should_have_loc=True)
>From 091e91263aeacb75852d7932858779ce1b8fea14 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Wed, 18 Mar 2026 13:18:07 +0100
Subject: [PATCH 5/7] Address feedback
---
.../SymStore/SymbolLocatorSymStore.cpp | 31 +++++++++++
llvm/include/llvm/Support/HTTP/HTTPClient.h | 2 +
llvm/lib/Support/HTTP/HTTPClient.cpp | 53 ++++++++++++++-----
3 files changed, 73 insertions(+), 13 deletions(-)
diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
index 7663120984239..7df6198c837c4 100644
--- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
@@ -150,6 +150,9 @@ llvm::Error downloadFileHTTP(llvm::StringRef url, FileDownloadHandler dest) {
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))
@@ -165,12 +168,37 @@ llvm::Error downloadFileHTTP(llvm::StringRef url, FileDownloadHandler dest) {
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 == "..";
+}
+
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)) {
+ LLDB_LOGV(log,
+ "Rejecting HTTP lookup for PDB file due to unsafe characters in "
+ "name: {0}",
+ pdb_name);
+ return {};
+ }
+
// Construct the path for local storage. Configurable cache coming soon.
llvm::SmallString<128> cache_file;
if (!path::cache_directory(cache_file)) {
@@ -215,6 +243,9 @@ std::optional<FileSpec> locateSymStoreEntry(llvm::StringRef base_url,
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);
}
diff --git a/llvm/include/llvm/Support/HTTP/HTTPClient.h b/llvm/include/llvm/Support/HTTP/HTTPClient.h
index 594995505a40e..17b706ff3e083 100644
--- a/llvm/include/llvm/Support/HTTP/HTTPClient.h
+++ b/llvm/include/llvm/Support/HTTP/HTTPClient.h
@@ -51,7 +51,9 @@ class HTTPResponseHandler {
/// A reusable client that can perform HTTPRequests through a network socket.
class HTTPClient {
+#if defined(LLVM_ENABLE_CURL) || defined(_WIN32)
void *Handle = nullptr;
+#endif
public:
HTTPClient();
diff --git a/llvm/lib/Support/HTTP/HTTPClient.cpp b/llvm/lib/Support/HTTP/HTTPClient.cpp
index 43d688cddecec..69780b32d1cf0 100644
--- a/llvm/lib/Support/HTTP/HTTPClient.cpp
+++ b/llvm/lib/Support/HTTP/HTTPClient.cpp
@@ -23,6 +23,9 @@
#ifdef LLVM_ENABLE_CURL
#include <curl/curl.h>
#endif
+#ifdef _WIN32
+#include "llvm/Support/ConvertUTF.h"
+#endif
using namespace llvm;
@@ -163,16 +166,6 @@ struct WinHTTPSession {
}
};
-bool convertUTF8ToWide(StringRef Utf8, std::wstring &Wide) {
- int WideLen =
- MultiByteToWideChar(CP_UTF8, 0, Utf8.data(), Utf8.size(), nullptr, 0);
- if (WideLen <= 0)
- return false;
- Wide.resize(WideLen);
- MultiByteToWideChar(CP_UTF8, 0, Utf8.data(), Utf8.size(), &Wide[0], WideLen);
- return true;
-}
-
bool parseURL(StringRef Url, std::wstring &Host, std::wstring &Path,
INTERNET_PORT &Port, bool &Secure) {
// Parse URL: http://host:port/path
@@ -196,9 +189,9 @@ bool parseURL(StringRef Url, std::wstring &Host, std::wstring &Path,
StringRef HostStr =
(ColonPos != StringRef::npos) ? HostPort.substr(0, ColonPos) : HostPort;
- if (!convertUTF8ToWide(HostStr, Host))
+ if (!llvm::ConvertUTF8toWide(HostStr, Host))
return false;
- if (!convertUTF8ToWide(PathPart, Path))
+ if (!llvm::ConvertUTF8toWide(PathPart, Path))
return false;
if (ColonPos != StringRef::npos) {
@@ -249,6 +242,12 @@ Error HTTPClient::perform(const HTTPRequest &Request,
if (Request.Method != HTTPMethod::GET)
return createStringError(errc::invalid_argument,
"Only GET requests are supported.");
+ for (const std::string &Header : Request.Headers)
+ if (Header.find("\r") != std::string::npos ||
+ Header.find("\n") != std::string::npos) {
+ return createStringError(errc::invalid_argument,
+ "Unsafe request can lead to header injection.");
+ }
WinHTTPSession *Session = static_cast<WinHTTPSession *>(Handle);
@@ -267,6 +266,18 @@ Error HTTPClient::perform(const HTTPRequest &Request,
if (!Session->SessionHandle)
return createStringError(errc::io_error, "Failed to open WinHTTP session");
+ // Prevent fallback to TLS 1.0/1.1
+ DWORD SecureProtocols =
+ WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3;
+ if (!WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_SECURE_PROTOCOLS,
+ &SecureProtocols, sizeof(SecureProtocols)))
+ return createStringError(errc::io_error, "Failed to set secure protocols");
+
+ // Use HTTP/2 if available
+ DWORD EnableHttp2 = WINHTTP_PROTOCOL_FLAG_HTTP2;
+ WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL,
+ &EnableHttp2, sizeof(EnableHttp2));
+
// Create connection
Session->ConnectHandle =
WinHttpConnect(Session->SessionHandle, Host.c_str(), Port, 0);
@@ -286,10 +297,26 @@ Error HTTPClient::perform(const HTTPRequest &Request,
if (!Session->RequestHandle)
return createStringError(errc::io_error, "Failed to open HTTP request");
+ // Enforce checks that certificate wasn't revoked.
+ DWORD EnableRevocationChecks = WINHTTP_ENABLE_SSL_REVOCATION;
+ if (!WinHttpSetOption(Session->RequestHandle, WINHTTP_OPTION_ENABLE_FEATURE,
+ &EnableRevocationChecks,
+ sizeof(EnableRevocationChecks)))
+ return createStringError(errc::io_error,
+ "Failed to enable certificate revocation checks");
+
+ // Explicitly enforce default validation. This protects against insecure
+ // overrides like SECURITY_FLAG_IGNORE_UNKNOWN_CA.
+ DWORD SecurityFlags = 0;
+ if (!WinHttpSetOption(Session->RequestHandle, WINHTTP_OPTION_SECURITY_FLAGS,
+ &SecurityFlags, sizeof(SecurityFlags)))
+ return createStringError(errc::io_error,
+ "Failed to enforce security flags");
+
// Add headers
for (const std::string &Header : Request.Headers) {
std::wstring WideHeader;
- if (!convertUTF8ToWide(Header, WideHeader))
+ if (!llvm::ConvertUTF8toWide(Header, WideHeader))
continue;
WinHttpAddRequestHeaders(Session->RequestHandle, WideHeader.c_str(),
static_cast<DWORD>(WideHeader.length()),
>From 56093ee2ce28d599da96e4c0c2f0c23d21d11e48 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Wed, 18 Mar 2026 13:26:23 +0100
Subject: [PATCH 6/7] Format test code (NFC)
---
lldb/test/API/symstore/TestSymStore.py | 20 +++++++-------------
1 file changed, 7 insertions(+), 13 deletions(-)
diff --git a/lldb/test/API/symstore/TestSymStore.py b/lldb/test/API/symstore/TestSymStore.py
index 4fcac80ee546a..60b29bb19f2c6 100644
--- a/lldb/test/API/symstore/TestSymStore.py
+++ b/lldb/test/API/symstore/TestSymStore.py
@@ -127,10 +127,8 @@ def test_external_lookup_off(self):
Check that breakpoint doesn't resolve with external lookup disabled.
"""
exe, sym = self.build_inferior()
- with MockedSymStore(self, exe, sym) as symstore_dir:
- self.runCmd(
- f"settings set plugin.symbol-locator.symstore.urls {symstore_dir}"
- )
+ with MockedSymStore(self, exe, sym) as dir:
+ self.runCmd(f"settings set plugin.symbol-locator.symstore.urls {dir}")
self.try_breakpoint(exe, ext_lookup=False, should_have_loc=False)
def test_local_dir(self):
@@ -138,10 +136,8 @@ def test_local_dir(self):
Check that breakpoint resolves with local SymStore.
"""
exe, sym = self.build_inferior()
- with MockedSymStore(self, exe, sym) as symstore_dir:
- self.runCmd(
- f"settings set plugin.symbol-locator.symstore.urls {symstore_dir}"
- )
+ with MockedSymStore(self, exe, sym) as dir:
+ self.runCmd(f"settings set plugin.symbol-locator.symstore.urls {dir}")
self.try_breakpoint(exe, should_have_loc=True)
def test_http(self):
@@ -149,9 +145,7 @@ def test_http(self):
Check that breakpoint hits with remote SymStore.
"""
exe, sym = self.build_inferior()
- with MockedSymStore(self, exe, sym) as symstore_dir:
- with HTTPServer(symstore_dir) as url:
- self.runCmd(
- f"settings set plugin.symbol-locator.symstore.urls {url}"
- )
+ with MockedSymStore(self, exe, sym) as dir:
+ with HTTPServer(dir) as url:
+ self.runCmd(f"settings set plugin.symbol-locator.symstore.urls {url}")
self.try_breakpoint(exe, should_have_loc=True)
>From 354668e537d4514a70024da111bd58aac56363f3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Wed, 18 Mar 2026 14:45:45 +0100
Subject: [PATCH 7/7] ReportWarning instead of logging failed FS ops
---
.../SymStore/SymbolLocatorSymStore.cpp | 21 ++++++++++---------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
index 7df6198c837c4..1975e313ccdf9 100644
--- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
@@ -190,25 +190,26 @@ requestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key,
using namespace llvm::sys;
Log *log = GetLog(LLDBLog::Symbols);
- //// Make sure URL will be valid, portable, and compatible with symbol servers
+ // Make sure URL will be valid, portable, and compatible with symbol servers
if (has_unsafe_characters(pdb_name)) {
- LLDB_LOGV(log,
- "Rejecting HTTP lookup for PDB file due to unsafe characters in "
- "name: {0}",
- pdb_name);
+ Debugger::ReportWarning(llvm::formatv(
+ "Rejecting HTTP lookup for PDB file due to unsafe characters in "
+ "name: {0}",
+ pdb_name));
return {};
}
// Construct the path for local storage. Configurable cache coming soon.
llvm::SmallString<128> cache_file;
if (!path::cache_directory(cache_file)) {
- LLDB_LOGV(log, "Failed to determine cache directory for SymStore");
+ Debugger::ReportWarning("Failed to determine cache directory for SymStore");
return {};
}
path::append(cache_file, "lldb", "SymStore", pdb_name, key);
if (std::error_code ec = fs::create_directories(cache_file)) {
- LLDB_LOG(log, "Failed to create temporary folder '{0}': {1}", cache_file,
- ec.message());
+ Debugger::ReportWarning(
+ llvm::formatv("Failed to create cache directory '{0}': {1}", cache_file,
+ ec.message()));
return {};
}
path::append(cache_file, pdb_name);
@@ -218,7 +219,7 @@ requestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key,
llvm::formatv("{0}/{1}/{2}/{1}", base_url, pdb_name, key);
if (llvm::Error err = downloadFileHTTP(source_url, cache_file.str())) {
LLDB_LOG_ERROR(log, std::move(err),
- " Failed to download from SymStore '{1}': {0}", source_url);
+ "Failed to download from SymStore '{1}': {0}", source_url);
return {};
}
@@ -277,7 +278,7 @@ std::optional<FileSpec> SymbolLocatorSymStore::LocateExecutableSymbolFile(
Args sym_store_urls = GetGlobalPluginProperties().GetURLs();
for (const Args::ArgEntry &url : sym_store_urls) {
if (auto spec = locateSymStoreEntry(url.ref(), key, pdb_name)) {
- LLDB_LOGV(log, " Found {0} in SymStore {1}", pdb_name, url.ref());
+ LLDB_LOGV(log, "Found {0} in SymStore {1}", pdb_name, url.ref());
return *spec;
}
}
More information about the lldb-commits
mailing list