[Lldb-commits] [lldb] [lldb] Add caching and _NT_SYMBOL_PATH parsing in SymbolLocatorSymStore (PR #191782)

Stefan Gränitz via lldb-commits lldb-commits at lists.llvm.org
Tue Apr 14 04:10:17 PDT 2026


https://github.com/weliveindetail updated https://github.com/llvm/llvm-project/pull/191782

>From b6c01d870a5fdea8a78e074a4b5906b525547641 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Mon, 13 Apr 2026 10:53:55 +0200
Subject: [PATCH 01/12] [lldb] Add caching and _NT_SYMBOL_PATH parsing in
 SymbolLocatorSymStore

---
 .../SymStore/SymbolLocatorSymStore.cpp        | 220 ++++++++++++++++--
 .../SymStore/SymbolLocatorSymStore.h          |   7 +
 .../SymbolLocatorSymStoreProperties.td        |   3 +
 lldb/test/API/symstore/TestSymStore.py        |  56 ++++-
 lldb/unittests/Symbol/CMakeLists.txt          |   2 +
 lldb/unittests/Symbol/SymStoreTest.cpp        | 108 +++++++++
 6 files changed, 378 insertions(+), 18 deletions(-)
 create mode 100644 lldb/unittests/Symbol/SymStoreTest.cpp

diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
index 41b15c6725a26..3d0e6243f4d6d 100644
--- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
@@ -58,6 +58,25 @@ class PluginProperties : public Properties {
     m_collection_sp->GetPropertyAtIndexAsArgs(ePropertySymStoreURLs, urls);
     return urls;
   }
+
+  std::string GetCachePath() const {
+    OptionValueString *s =
+        m_collection_sp->GetPropertyAtIndexAsOptionValueString(
+            ePropertyCachePath);
+    if (s && !s->GetCurrentValueAsRef().empty())
+      return s->GetCurrentValue();
+    // Fall back to the platform cache directory.
+    llvm::SmallString<128> cache_dir;
+    if (llvm::sys::path::cache_directory(cache_dir)) {
+      llvm::sys::path::append(cache_dir, "lldb", "symstore");
+      return cache_dir.str().str();
+    }
+    // Last resort: use a subdirectory of the system temp directory.
+    constexpr bool erase_on_reboot = false;
+    llvm::sys::path::system_temp_directory(erase_on_reboot, cache_dir);
+    llvm::sys::path::append(cache_dir, "lldb", "symstore");
+    return cache_dir.str().str();
+  }
 };
 
 } // namespace
@@ -103,6 +122,61 @@ SymbolLocator *SymbolLocatorSymStore::CreateInstance() {
 
 namespace {
 
+SymbolLocatorSymStore::LookupEntry make_lookup_entry(llvm::StringRef source) {
+  SymbolLocatorSymStore::LookupEntry entry;
+  entry.source = source.str();
+  entry.cache = std::nullopt;
+  return entry;
+}
+
+SymbolLocatorSymStore::LookupEntry make_lookup_entry(llvm::StringRef source,
+                                                     llvm::StringRef cache) {
+  SymbolLocatorSymStore::LookupEntry entry;
+  entry.source = source.str();
+  entry.cache = cache.str();
+  return entry;
+}
+
+std::vector<SymbolLocatorSymStore::LookupEntry> getGlobalLookupOrder() {
+  std::vector<SymbolLocatorSymStore::LookupEntry> result;
+
+  for (const auto &url : GetGlobalPluginProperties().GetURLs())
+    result.push_back(make_lookup_entry(url.ref()));
+
+  const char *sym_path = std::getenv("_NT_SYMBOL_PATH");
+  for (auto entry : SymbolLocatorSymStore::ParseEnvSymbolPaths(sym_path))
+    result.push_back(std::move(entry));
+
+  const char *alt_path = std::getenv("_NT_ALT_SYMBOL_PATH");
+  for (auto entry : SymbolLocatorSymStore::ParseEnvSymbolPaths(alt_path))
+    result.push_back(std::move(entry));
+
+  return result;
+}
+
+std::optional<SymbolLocatorSymStore::LookupEntry>
+ParseSrvEntry(llvm::StringRef entry) {
+  llvm::SmallVector<llvm::StringRef, 4> parts;
+  entry.trim().split(parts, '*');
+  switch (parts.size()) {
+  case 2:
+    return make_lookup_entry(parts[1]);
+  case 3:
+    return make_lookup_entry(parts[2], parts[1]);
+  default:
+    return {}; // Ignore entries with invalid number of parts.
+  }
+}
+
+std::optional<std::string> ParseCacheEntry(llvm::StringRef entry) {
+  llvm::SmallVector<llvm::StringRef, 2> parts;
+  entry.trim().split(parts, '*');
+  // Ignore entries with invalid number of parts.
+  if (parts.size() != 2)
+    return {};
+  return parts.back().str();
+}
+
 // RSDS entries store identity as a 20-byte UUID composed of 16-byte GUID and
 // 4-byte age:
 //   12345678-1234-5678-9ABC-DEF012345678-00000001
@@ -133,8 +207,6 @@ bool HasUnsafeCharacters(llvm::StringRef s) {
   return s == "." || s == "..";
 }
 
-// TODO: This is a dumb initial implementation: It always downloads the file and
-// doesn't validate the result.
 std::optional<FileSpec>
 RequestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key,
                                   llvm::StringRef pdb_name) {
@@ -151,11 +223,9 @@ RequestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key,
 
   // 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);
+  path::append(tmp_file, llvm::formatv("lldb_symstore_{0}_{1}", key, pdb_name));
 
   // Server has SymStore directory structure with forward slashes as separators.
   std::string source_url =
@@ -223,16 +293,86 @@ std::optional<FileSpec> FindFileInLocalSymStore(llvm::StringRef root_dir,
   return spec;
 }
 
-std::optional<FileSpec> LocateSymStoreEntry(llvm::StringRef base_url,
+std::optional<FileSpec> MoveToLocalSymStore(llvm::StringRef cache,
                                             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);
+                                            llvm::StringRef pdb_name,
+                                            FileSpec tmp_file) {
+  // Caches have SymStore directory structure: cache/pdb_name/key/pdb_name
+  llvm::SmallString<256> dest_dir;
+  llvm::sys::path::append(dest_dir, cache, pdb_name, key);
+  if (std::error_code ec = llvm::sys::fs::create_directories(dest_dir)) {
+    Debugger::ReportWarning(
+        llvm::formatv("failed to create SymStore cache directory '{0}': {1}",
+                      dest_dir, ec.message()));
+    return tmp_file;
+  }
+
+  llvm::SmallString<256> dest;
+  llvm::sys::path::append(dest, dest_dir, pdb_name);
+  std::error_code ec = llvm::sys::fs::rename(tmp_file.GetPath(), dest);
+
+  // Fall back to copy+delete if we move to a different volume.
+  if (ec == std::errc::cross_device_link) {
+    ec = llvm::sys::fs::copy_file(tmp_file.GetPath(), dest);
+    if (!ec)
+      llvm::sys::fs::remove(tmp_file.GetPath());
+  }
+  if (ec) {
+    Debugger::ReportWarning(
+        llvm::formatv("failed to move '{0}' to SymStore cache '{1}': {2}",
+                      tmp_file.GetPath(), dest, ec.message()));
+    return tmp_file;
+  }
+
+  return FileSpec(dest.str());
+}
+
+std::optional<FileSpec>
+LocateSymStoreEntry(const SymbolLocatorSymStore::LookupEntry &entry,
+                    llvm::StringRef key, llvm::StringRef pdb_name) {
+  Log *log = GetLog(LLDBLog::Symbols);
+  std::string default_cache = GetGlobalPluginProperties().GetCachePath();
+
+  llvm::StringRef url = entry.source;
+  if (url.starts_with("http://") || url.starts_with("https://")) {
+    // Check cache first.
+    if (entry.cache) {
+      if (auto spec = FindFileInLocalSymStore(*entry.cache, key, pdb_name)) {
+        LLDB_LOG_VERBOSE(log, "Found {0} in SymStore cache {1}", pdb_name,
+                         *entry.cache);
+        return *spec;
+      }
+    } else {
+      // Check LLDB default cache to avoid duplicate downloads in the same
+      // session.
+      if (auto spec = FindFileInLocalSymStore(default_cache, key, pdb_name)) {
+        LLDB_LOG_VERBOSE(log, "Found {0} in SymStore cache {1}", pdb_name,
+                         default_cache);
+        return *spec;
+      }
+    }
+
+    // Download and move to cache.
+    if (auto spec = RequestFileFromSymStoreServerHTTP(url, key, pdb_name)) {
+      LLDB_LOG_VERBOSE(log, "Downloaded {0} from SymStore {1}", pdb_name, url);
+      std::string cache = entry.cache.value_or(default_cache);
+      spec = MoveToLocalSymStore(cache, key, pdb_name, *spec);
+      LLDB_LOG_VERBOSE(log, "Added {0} to SymStore cache {1}", pdb_name, cache);
+      return *spec;
+    }
 
-  if (base_url.starts_with("file://"))
-    base_url = base_url.drop_front(7);
+    return {};
+  }
 
-  return FindFileInLocalSymStore(base_url, key, pdb_name);
+  llvm::StringRef file = entry.source;
+  if (file.starts_with("file://"))
+    file = file.drop_front(7);
+  if (auto spec = FindFileInLocalSymStore(file, key, pdb_name)) {
+    LLDB_LOG_VERBOSE(log, "Found {0} in local SymStore {1}", pdb_name, file);
+    return *spec;
+  }
+
+  return {};
 }
 
 } // namespace
@@ -261,13 +401,59 @@ std::optional<FileSpec> SymbolLocatorSymStore::LocateExecutableSymbolFile(
   }
 
   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());
+  for (const LookupEntry &entry : getGlobalLookupOrder()) {
+    if (auto spec = LocateSymStoreEntry(entry, key, pdb_name))
       return *spec;
-    }
   }
 
   return {};
 }
+
+std::vector<SymbolLocatorSymStore::LookupEntry>
+SymbolLocatorSymStore::ParseEnvSymbolPaths(llvm::StringRef val) {
+  if (val.empty())
+    return {};
+
+  std::vector<LookupEntry> result;
+  std::optional<std::string> implicit_cache;
+  llvm::SmallVector<llvm::StringRef, 2> entries;
+  val.split(entries, ';');
+
+  for (llvm::StringRef raw : entries) {
+    llvm::StringRef entry = raw.trim();
+    if (entry.empty())
+      continue;
+
+    // Explicit cache directives apply to all subsequent srv* entries that don't
+    // set their own explicit cache.
+    if (entry.starts_with_insensitive("cache*")) {
+      if (auto cache = ParseCacheEntry(entry))
+        implicit_cache = *cache;
+      continue;
+    }
+
+    // SymStore directives with explicit interpreters are unsupported
+    // explicitly.
+    if (entry.starts_with_insensitive("symsrv*")) {
+      Debugger::ReportWarning(
+          llvm::formatv("ignoring unsupported entry in env: {0}", entry));
+      continue;
+    }
+
+    // SymStore server directives may include an explicit cache.
+    // Format is: srv*[LocalCache*]SymbolStore
+    if (entry.starts_with_insensitive("srv*")) {
+      if (auto lookup_entry = ParseSrvEntry(entry)) {
+        if (!lookup_entry->cache && implicit_cache)
+          lookup_entry->cache = implicit_cache;
+        result.push_back(*lookup_entry);
+      }
+      continue;
+    }
+
+    // Plain local paths aren't cached.
+    result.push_back(make_lookup_entry(entry));
+  }
+
+  return result;
+}
diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h
index 52ec04cae387b..de2a1c6b9bdd5 100644
--- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h
@@ -43,6 +43,13 @@ class SymbolLocatorSymStore : public SymbolLocator {
   static std::optional<FileSpec>
   LocateExecutableSymbolFile(const ModuleSpec &module_spec,
                              const FileSpecList &default_search_paths);
+
+  struct LookupEntry {
+    std::string source;
+    std::optional<std::string> cache;
+  };
+
+  static std::vector<LookupEntry> ParseEnvSymbolPaths(llvm::StringRef val);
 };
 
 } // namespace lldb_private
diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td
index 0cd631a80b90b..337f570b2ec7e 100644
--- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td
@@ -4,4 +4,7 @@ let Definition = "symbollocatorsymstore", Path = "plugin.symbol-locator.symstore
   def SymStoreURLs : Property<"urls", "Array">,
     ElementType<"String">,
     Desc<"List of local symstore directories to query for symbols">;
+  def CachePath : Property<"cache", "String">,
+    DefaultStringValue<"">,
+    Desc<"Default cache directory for downloaded symbol files. Used when no cache is specified in _NT_SYMBOL_PATH.">;
 }
diff --git a/lldb/test/API/symstore/TestSymStore.py b/lldb/test/API/symstore/TestSymStore.py
index 5baa5fa4d0da8..6af45dbe7e56c 100644
--- a/lldb/test/API/symstore/TestSymStore.py
+++ b/lldb/test/API/symstore/TestSymStore.py
@@ -66,6 +66,26 @@ def __exit__(self, *exc_info):
         self._test.runCmd("settings clear plugin.symbol-locator.symstore")
 
 
+class NtSymbolPath:
+    """
+    Context Manager to temporarily set the _NT_SYMBOL_PATH environment variable.
+    """
+
+    def __init__(self, value):
+        self._value = value
+        self._saved = None
+
+    def __enter__(self):
+        self._saved = os.environ.get("_NT_SYMBOL_PATH")
+        os.environ["_NT_SYMBOL_PATH"] = self._value
+
+    def __exit__(self, *exc_info):
+        if self._saved is None:
+            os.environ.pop("_NT_SYMBOL_PATH", None)
+        else:
+            os.environ["_NT_SYMBOL_PATH"] = self._saved
+
+
 class HTTPServer:
     """
     Context Manager to serve a local directory tree via HTTP.
@@ -162,10 +182,44 @@ def test_http_not_found(self):
     # certs, non-HTTPS redirects, etc.
     def test_http(self):
         """
-        Check that breakpoint hits with remote SymStore.
+        Check that breakpoint resolves with remote SymStore.
         """
         exe, sym = self.build_inferior()
         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)
+
+    def test_nt_symbol_path_local(self):
+        """
+        Check that breakpoint resolves with a local SymStore path in
+        _NT_SYMBOL_PATH, and that the PDB is not copied to the cache.
+        """
+        exe, sym = self.build_inferior()
+        cache_dir = self.getBuildArtifact("cache")
+        symstore = MockedSymStore(self, exe, sym)
+        with symstore as dir:
+            self.runCmd(
+                f"settings set plugin.symbol-locator.symstore.cache {cache_dir}"
+            )
+            with NtSymbolPath(dir):
+                self.try_breakpoint(exe, should_have_loc=True)
+            self.assertFalse(any(files for _, _, files in os.walk(cache_dir)))
+
+    def test_nt_symbol_path_srv(self):
+        """
+        Check that breakpoint resolves with an HTTP symbol server in
+        _NT_SYMBOL_PATH using the srv* syntax, and that the PDB is cached.
+        """
+        exe, sym = self.build_inferior()
+        cache_dir = self.getBuildArtifact("cache")
+        symstore = MockedSymStore(self, exe, sym)
+        with symstore as dir:
+            self.runCmd(
+                f"settings set plugin.symbol-locator.symstore.cache {cache_dir}"
+            )
+            with HTTPServer(dir) as url:
+                with NtSymbolPath(f"srv*{url}"):
+                    self.try_breakpoint(exe, should_have_loc=True)
+            key = symstore.get_key_pdb(exe)
+            self.assertTrue(os.path.isfile(os.path.join(cache_dir, sym, key, sym)))
diff --git a/lldb/unittests/Symbol/CMakeLists.txt b/lldb/unittests/Symbol/CMakeLists.txt
index 5664c21adbe3f..f9794369a89f4 100644
--- a/lldb/unittests/Symbol/CMakeLists.txt
+++ b/lldb/unittests/Symbol/CMakeLists.txt
@@ -6,6 +6,7 @@ add_lldb_unittest(SymbolTests
   PostfixExpressionTest.cpp
   SymbolTest.cpp
   SymtabTest.cpp
+  SymStoreTest.cpp
   TestTypeSystem.cpp
   TestTypeSystemClang.cpp
   TestClangASTImporter.cpp
@@ -24,6 +25,7 @@ add_lldb_unittest(SymbolTests
     lldbPluginSymbolFileDWARF
     lldbPluginSymbolFileSymtab
     lldbPluginTypeSystemClang
+    lldbPluginSymbolLocatorSymStore
     LLVMTestingSupport
   )
 
diff --git a/lldb/unittests/Symbol/SymStoreTest.cpp b/lldb/unittests/Symbol/SymStoreTest.cpp
new file mode 100644
index 0000000000000..b08cdadf74400
--- /dev/null
+++ b/lldb/unittests/Symbol/SymStoreTest.cpp
@@ -0,0 +1,108 @@
+//===-- SymbolsTest.cpp ---------------------------------------------------===//
+//
+// 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 "gtest/gtest.h"
+
+#include "Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h"
+
+using namespace lldb_private;
+using LookupEntry = SymbolLocatorSymStore::LookupEntry;
+
+TEST(SymStoreTest, ParseEnvSymbolPaths_Srv) {
+  auto check = [](const char *str) {
+    std::vector<std::string> sources;
+    for (LookupEntry entry : SymbolLocatorSymStore::ParseEnvSymbolPaths(str))
+      sources.push_back(std::move(entry.source));
+    return sources;
+  };
+  auto returns = [](auto... strs) { return std::vector<std::string>{strs...}; };
+
+  // Local paths
+  EXPECT_EQ(check(""), returns());
+  EXPECT_EQ(check("C:\\ProgramData\\Symbols"),
+            returns("C:\\ProgramData\\Symbols"));
+  EXPECT_EQ(check("C:\\symbols;\\\\buildserver\\syms;file://D:/pdb"),
+            returns("C:\\symbols", "\\\\buildserver\\syms", "file://D:/pdb"));
+
+  // Symbol servers
+  EXPECT_EQ(check("srv*https://msdl.microsoft.com/download/symbols"),
+            returns("https://msdl.microsoft.com/download/symbols"));
+  EXPECT_EQ(check("Srv*https://msdl.microsoft.com/download/symbols"),
+            returns("https://msdl.microsoft.com/download/symbols"));
+  EXPECT_EQ(check("SRV*http://localhost"), returns("http://localhost"));
+
+  // Symbol servers and local paths with caches
+  EXPECT_EQ(check("SRV*C:\\symcache*\\\\corp\\symbols"),
+            returns("\\\\corp\\symbols"));
+  EXPECT_EQ(check("D:\\sym;srv*C:\\symcache*D:\\sym"),
+            returns("D:\\sym", "D:\\sym"));
+  EXPECT_EQ(check("srv**https://symbols.mozilla.org"),
+            returns("https://symbols.mozilla.org"));
+
+  // Symbol server with custom implementation (unsupported)
+  EXPECT_EQ(check("symsrv*symsrv.dll*https://symbols.mozilla.org"), returns());
+  EXPECT_EQ(check("symsrv*symsrv.dll*C:\\symbols*https://symbols.mozilla.org"),
+            returns());
+  EXPECT_EQ(check("symsrv*https://symbols.mozilla.org;D:\\sym"),
+            returns("D:\\sym"));
+
+  // Partially invalid specs
+  EXPECT_EQ(check("srv*;;D:\\sym;SRV*"), returns("", "D:\\sym", ""));
+  EXPECT_EQ(check("srv*D:\\1*D:\\2*D:\\3;D:\\sym"), returns("D:\\sym"));
+  EXPECT_EQ(check("symsrv*D:\\1;D:\\sym"), returns("D:\\sym"));
+}
+
+TEST(SymStoreTest, ParseEnvSymbolPaths_Cache) {
+  auto check = [](const char *str) {
+    std::vector<std::string> caches;
+    for (LookupEntry entry : SymbolLocatorSymStore::ParseEnvSymbolPaths(str))
+      if (entry.cache)
+        caches.push_back(std::move(*entry.cache));
+    return caches;
+  };
+  auto returns = [](auto... strs) { return std::vector<std::string>{strs...}; };
+
+  // No caches
+  EXPECT_EQ(check(""), returns());
+  EXPECT_EQ(check("C:\\ProgramData\\Symbols"), returns());
+  EXPECT_EQ(check("C:\\symbols;\\\\buildserver\\syms;file://D:/pdb"),
+            returns());
+  EXPECT_EQ(check("SRV*http://localhost"), returns());
+
+  // No cache without a server
+  EXPECT_EQ(check("cache*C:\\symcache"), returns());
+  EXPECT_EQ(check("cache*C:\\symcache;D:\\sym"), returns());
+
+  // Explicit caches for symbol servers
+  EXPECT_EQ(check("SRV*C:\\symcache*\\\\corp\\symbols"),
+            returns("C:\\symcache"));
+  EXPECT_EQ(check("D:\\sym;srv*C:\\symcache*D:\\sym"), returns("C:\\symcache"));
+  EXPECT_EQ(check("srv**https://symbols.mozilla.org"), returns(""));
+
+  // Implicit caches for following symbol servers
+  EXPECT_EQ(check("cache*D:\\s;srv*\\\\corp"), returns("D:\\s"));
+  EXPECT_EQ(check("CACHE*D:\\s;srv*\\\\corp;SRV*http://localhost"),
+            returns("D:\\s", "D:\\s"));
+  EXPECT_EQ(check("Cache*D:\\s;srv*\\\\corp;SRV*C:\\X*http://localhost"),
+            returns("D:\\s", "C:\\X"));
+  EXPECT_EQ(check("srv*\\\\corp;cache*D:\\s;SRV*C:\\X*http://localhost"),
+            returns("C:\\X"));
+  EXPECT_EQ(check("srv*\\\\corp;SRV*C:\\X*http://localhost;cache*D:\\s"),
+            returns("C:\\X"));
+
+  // Symbol server with custom implementation (unsupported)
+  EXPECT_EQ(check("symsrv*symsrv.dll*https://symbols.mozilla.org"), returns());
+  EXPECT_EQ(check("symsrv*symsrv.dll*C:\\symbols*https://symbols.mozilla.org"),
+            returns());
+
+  // Partially invalid specs
+  EXPECT_EQ(check("cache*C:\\1;;D:\\sym;SRV*"), returns("C:\\1"));
+  EXPECT_EQ(check("cache*C:\\1;srv*D:\\1*D:\\2*D:\\3;srv*D:\\sym"),
+            returns("C:\\1"));
+  EXPECT_EQ(check("cache*C:\\1;symsrv*D:\\1;srv*D:\\sym"), returns("C:\\1"));
+}

>From a82d232e480687a3b3195428b1a34b972092cc99 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Mon, 13 Apr 2026 18:08:26 +0200
Subject: [PATCH 02/12] Rename make_lookup_entry() -> MakeLookupEntry()

---
 .../SymStore/SymbolLocatorSymStore.cpp             | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
index 3d0e6243f4d6d..b3b572bcfa21e 100644
--- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
@@ -122,15 +122,15 @@ SymbolLocator *SymbolLocatorSymStore::CreateInstance() {
 
 namespace {
 
-SymbolLocatorSymStore::LookupEntry make_lookup_entry(llvm::StringRef source) {
+SymbolLocatorSymStore::LookupEntry MakeLookupEntry(llvm::StringRef source) {
   SymbolLocatorSymStore::LookupEntry entry;
   entry.source = source.str();
   entry.cache = std::nullopt;
   return entry;
 }
 
-SymbolLocatorSymStore::LookupEntry make_lookup_entry(llvm::StringRef source,
-                                                     llvm::StringRef cache) {
+SymbolLocatorSymStore::LookupEntry MakeLookupEntry(llvm::StringRef source,
+                                                   llvm::StringRef cache) {
   SymbolLocatorSymStore::LookupEntry entry;
   entry.source = source.str();
   entry.cache = cache.str();
@@ -141,7 +141,7 @@ std::vector<SymbolLocatorSymStore::LookupEntry> getGlobalLookupOrder() {
   std::vector<SymbolLocatorSymStore::LookupEntry> result;
 
   for (const auto &url : GetGlobalPluginProperties().GetURLs())
-    result.push_back(make_lookup_entry(url.ref()));
+    result.push_back(MakeLookupEntry(url.ref()));
 
   const char *sym_path = std::getenv("_NT_SYMBOL_PATH");
   for (auto entry : SymbolLocatorSymStore::ParseEnvSymbolPaths(sym_path))
@@ -160,9 +160,9 @@ ParseSrvEntry(llvm::StringRef entry) {
   entry.trim().split(parts, '*');
   switch (parts.size()) {
   case 2:
-    return make_lookup_entry(parts[1]);
+    return MakeLookupEntry(parts[1]);
   case 3:
-    return make_lookup_entry(parts[2], parts[1]);
+    return MakeLookupEntry(parts[2], parts[1]);
   default:
     return {}; // Ignore entries with invalid number of parts.
   }
@@ -452,7 +452,7 @@ SymbolLocatorSymStore::ParseEnvSymbolPaths(llvm::StringRef val) {
     }
 
     // Plain local paths aren't cached.
-    result.push_back(make_lookup_entry(entry));
+    result.push_back(MakeLookupEntry(entry));
   }
 
   return result;

>From c732f6537332c21d2d0d9deb25de34ae522189bd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Mon, 13 Apr 2026 18:09:30 +0200
Subject: [PATCH 03/12] Rename getGlobalLookupOrder() -> GetGlobalLookupOrder()

---
 .../Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
index b3b572bcfa21e..ca9a75694dcc4 100644
--- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
@@ -137,7 +137,7 @@ SymbolLocatorSymStore::LookupEntry MakeLookupEntry(llvm::StringRef source,
   return entry;
 }
 
-std::vector<SymbolLocatorSymStore::LookupEntry> getGlobalLookupOrder() {
+std::vector<SymbolLocatorSymStore::LookupEntry> GetGlobalLookupOrder() {
   std::vector<SymbolLocatorSymStore::LookupEntry> result;
 
   for (const auto &url : GetGlobalPluginProperties().GetURLs())
@@ -401,7 +401,7 @@ std::optional<FileSpec> SymbolLocatorSymStore::LocateExecutableSymbolFile(
   }
 
   std::string key = FormatSymStoreKey(uuid);
-  for (const LookupEntry &entry : getGlobalLookupOrder()) {
+  for (const LookupEntry &entry : GetGlobalLookupOrder()) {
     if (auto spec = LocateSymStoreEntry(entry, key, pdb_name))
       return *spec;
   }

>From 3cb8481c00c5dedddd57c8e52736a29c942837b5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Mon, 13 Apr 2026 18:19:51 +0200
Subject: [PATCH 04/12] Unify lookup order for server and cache

---
 .../SymbolLocator/SymStore/SymbolLocatorSymStore.cpp        | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
index ca9a75694dcc4..c3389416fd7ec 100644
--- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
@@ -140,9 +140,6 @@ SymbolLocatorSymStore::LookupEntry MakeLookupEntry(llvm::StringRef source,
 std::vector<SymbolLocatorSymStore::LookupEntry> GetGlobalLookupOrder() {
   std::vector<SymbolLocatorSymStore::LookupEntry> result;
 
-  for (const auto &url : GetGlobalPluginProperties().GetURLs())
-    result.push_back(MakeLookupEntry(url.ref()));
-
   const char *sym_path = std::getenv("_NT_SYMBOL_PATH");
   for (auto entry : SymbolLocatorSymStore::ParseEnvSymbolPaths(sym_path))
     result.push_back(std::move(entry));
@@ -151,6 +148,9 @@ std::vector<SymbolLocatorSymStore::LookupEntry> GetGlobalLookupOrder() {
   for (auto entry : SymbolLocatorSymStore::ParseEnvSymbolPaths(alt_path))
     result.push_back(std::move(entry));
 
+  for (const auto &url : GetGlobalPluginProperties().GetURLs())
+    result.push_back(MakeLookupEntry(url.ref()));
+
   return result;
 }
 

>From 299c37307c95a7a33c137bf6b1bd43e946a180d6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Mon, 13 Apr 2026 18:45:41 +0200
Subject: [PATCH 05/12] Set local default cache in all API tests to avoid
 writing outside the test dir

---
 lldb/test/API/symstore/TestSymStore.py | 13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)

diff --git a/lldb/test/API/symstore/TestSymStore.py b/lldb/test/API/symstore/TestSymStore.py
index 6af45dbe7e56c..37ed762497942 100644
--- a/lldb/test/API/symstore/TestSymStore.py
+++ b/lldb/test/API/symstore/TestSymStore.py
@@ -26,6 +26,7 @@ def __init__(self, test, exe, pdb):
         self._test = test
         self._exe = exe
         self._pdb = pdb
+        self.cache_dir = test.getBuildArtifact("cache")
 
     def get_key_pdb(self, exe):
         """
@@ -57,6 +58,9 @@ def __enter__(self):
             self._test.getBuildArtifact(self._pdb),
             os.path.join(pdb_dir, self._pdb),
         )
+        self._test.runCmd(
+            f"settings set plugin.symbol-locator.symstore.cache {self.cache_dir}"
+        )
         return symstore_dir
 
     def __exit__(self, *exc_info):
@@ -196,12 +200,9 @@ def test_nt_symbol_path_local(self):
         _NT_SYMBOL_PATH, and that the PDB is not copied to the cache.
         """
         exe, sym = self.build_inferior()
-        cache_dir = self.getBuildArtifact("cache")
         symstore = MockedSymStore(self, exe, sym)
         with symstore as dir:
-            self.runCmd(
-                f"settings set plugin.symbol-locator.symstore.cache {cache_dir}"
-            )
+            # Symbol path with plain directory
             with NtSymbolPath(dir):
                 self.try_breakpoint(exe, should_have_loc=True)
             self.assertFalse(any(files for _, _, files in os.walk(cache_dir)))
@@ -212,12 +213,8 @@ def test_nt_symbol_path_srv(self):
         _NT_SYMBOL_PATH using the srv* syntax, and that the PDB is cached.
         """
         exe, sym = self.build_inferior()
-        cache_dir = self.getBuildArtifact("cache")
         symstore = MockedSymStore(self, exe, sym)
         with symstore as dir:
-            self.runCmd(
-                f"settings set plugin.symbol-locator.symstore.cache {cache_dir}"
-            )
             with HTTPServer(dir) as url:
                 with NtSymbolPath(f"srv*{url}"):
                     self.try_breakpoint(exe, should_have_loc=True)

>From 9295f19d0aca0bd4e4cd3e67d6da7cd728e9c2e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Mon, 13 Apr 2026 18:46:31 +0200
Subject: [PATCH 06/12] Polish cache checks

---
 lldb/test/API/symstore/TestSymStore.py | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/lldb/test/API/symstore/TestSymStore.py b/lldb/test/API/symstore/TestSymStore.py
index 37ed762497942..2b94a1af455b1 100644
--- a/lldb/test/API/symstore/TestSymStore.py
+++ b/lldb/test/API/symstore/TestSymStore.py
@@ -205,7 +205,11 @@ def test_nt_symbol_path_local(self):
             # Symbol path with plain directory
             with NtSymbolPath(dir):
                 self.try_breakpoint(exe, should_have_loc=True)
-            self.assertFalse(any(files for _, _, files in os.walk(cache_dir)))
+            # Symbol path with local directory in server notation
+            with NtSymbolPath(f"srv*{dir}"):
+                self.try_breakpoint(exe, should_have_loc=True)
+            cached_files = sum(len(f) for _, _, f in os.walk(symstore.cache_dir))
+            self.assertEqual(cached_files, 0)
 
     def test_nt_symbol_path_srv(self):
         """
@@ -219,4 +223,7 @@ def test_nt_symbol_path_srv(self):
                 with NtSymbolPath(f"srv*{url}"):
                     self.try_breakpoint(exe, should_have_loc=True)
             key = symstore.get_key_pdb(exe)
-            self.assertTrue(os.path.isfile(os.path.join(cache_dir, sym, key, sym)))
+            cache_file = os.path.join(symstore.cache_dir, sym, key, sym)
+            self.assertTrue(os.path.isfile(cache_file))
+            cached_files = sum(len(f) for _, _, f in os.walk(symstore.cache_dir))
+            self.assertEqual(cached_files, 1)

>From caec5ef791e0010d9d87a8050836722989dad1e1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Mon, 13 Apr 2026 19:13:22 +0200
Subject: [PATCH 07/12] Add test_lookup_order()

---
 lldb/test/API/symstore/TestSymStore.py | 30 ++++++++++++++++++++++++--
 1 file changed, 28 insertions(+), 2 deletions(-)

diff --git a/lldb/test/API/symstore/TestSymStore.py b/lldb/test/API/symstore/TestSymStore.py
index 2b94a1af455b1..de16220521239 100644
--- a/lldb/test/API/symstore/TestSymStore.py
+++ b/lldb/test/API/symstore/TestSymStore.py
@@ -95,9 +95,10 @@ class HTTPServer:
     Context Manager to serve a local directory tree via HTTP.
     """
 
-    def __init__(self, dir):
+    def __init__(self, dir, handler=None):
         address = ("localhost", 0)  # auto-select free port
-        handler = partial(http.server.SimpleHTTPRequestHandler, directory=dir)
+        if handler is None:
+            handler = partial(http.server.SimpleHTTPRequestHandler, directory=dir)
         self._server = socketserver.ThreadingTCPServer(address, handler)
         self._thread = threading.Thread(target=self._server.serve_forever, daemon=True)
 
@@ -114,6 +115,17 @@ def __exit__(self, *exc_info):
             self._thread.join()
 
 
+class RequestCounter(http.server.SimpleHTTPRequestHandler):
+    requests = 0  # class-level so all instances share one counter
+
+    def __init__(self, *args, directory=None, **kwargs):
+        super().__init__(*args, directory=directory, **kwargs)
+
+    def do_GET(self):
+        RequestCounter.requests += 1
+        super().do_GET()
+
+
 class SymStoreTests(TestBase):
     SHARED_BUILD_TESTCASE = False
     TEST_WITH_PDB_DEBUG_INFO = True
@@ -227,3 +239,17 @@ def test_nt_symbol_path_srv(self):
             self.assertTrue(os.path.isfile(cache_file))
             cached_files = sum(len(f) for _, _, f in os.walk(symstore.cache_dir))
             self.assertEqual(cached_files, 1)
+
+    def test_lookup_order(self):
+        """
+        Check that _NT_SYMBOL_PATH takes precedence over symstore.urls setting.
+        """
+        exe, sym = self.build_inferior()
+        symstore = MockedSymStore(self, exe, sym)
+        RequestCounter.requests = 0
+        with symstore as dir:
+            with HTTPServer(dir, RequestCounter) as url:
+                self.runCmd(f"settings set plugin.symbol-locator.symstore.urls {url}")
+                with NtSymbolPath(dir):
+                    self.try_breakpoint(exe, should_have_loc=True)
+            self.assertEqual(RequestCounter.requests, 0)

>From 2f6b6ceee0041bc1dd23cae57350e2c301f4945a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Mon, 13 Apr 2026 23:05:28 +0200
Subject: [PATCH 08/12] Fall back to default system path for empty and invalid
 cache strings

---
 .../SymStore/SymbolLocatorSymStore.cpp        | 53 +++++++++++++------
 .../SymStore/SymbolLocatorSymStore.h          |  1 +
 lldb/unittests/Symbol/SymStoreTest.cpp        | 11 ++--
 3 files changed, 47 insertions(+), 18 deletions(-)

diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
index c3389416fd7ec..a5a3e3e79bde9 100644
--- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
@@ -65,17 +65,7 @@ class PluginProperties : public Properties {
             ePropertyCachePath);
     if (s && !s->GetCurrentValueAsRef().empty())
       return s->GetCurrentValue();
-    // Fall back to the platform cache directory.
-    llvm::SmallString<128> cache_dir;
-    if (llvm::sys::path::cache_directory(cache_dir)) {
-      llvm::sys::path::append(cache_dir, "lldb", "symstore");
-      return cache_dir.str().str();
-    }
-    // Last resort: use a subdirectory of the system temp directory.
-    constexpr bool erase_on_reboot = false;
-    llvm::sys::path::system_temp_directory(erase_on_reboot, cache_dir);
-    llvm::sys::path::append(cache_dir, "lldb", "symstore");
-    return cache_dir.str().str();
+    return SymbolLocatorSymStore::GetDefaultCachePath();
   }
 };
 
@@ -158,11 +148,18 @@ std::optional<SymbolLocatorSymStore::LookupEntry>
 ParseSrvEntry(llvm::StringRef entry) {
   llvm::SmallVector<llvm::StringRef, 4> parts;
   entry.trim().split(parts, '*');
+
+  // Format is: srv*[LocalCache*]SymbolStore
   switch (parts.size()) {
   case 2:
     return MakeLookupEntry(parts[1]);
-  case 3:
-    return MakeLookupEntry(parts[2], parts[1]);
+  case 3: {
+    if (llvm::sys::path::is_absolute(parts[1]))
+      return MakeLookupEntry(parts[2], parts[1]);
+    // Fall back to LLDB's default cache for empty and invalid cache values.
+    return MakeLookupEntry(parts[2],
+                           SymbolLocatorSymStore::GetDefaultCachePath());
+  }
   default:
     return {}; // Ignore entries with invalid number of parts.
   }
@@ -171,10 +168,21 @@ ParseSrvEntry(llvm::StringRef entry) {
 std::optional<std::string> ParseCacheEntry(llvm::StringRef entry) {
   llvm::SmallVector<llvm::StringRef, 2> parts;
   entry.trim().split(parts, '*');
+
   // Ignore entries with invalid number of parts.
-  if (parts.size() != 2)
+  if (parts.size() > 2)
     return {};
-  return parts.back().str();
+
+  // Empty cache* deliberatly specifies the default cache path.
+  llvm::StringRef value;
+  if (parts.size() == 2)
+    value = parts.back();
+
+  // Fall back to LLDB's default cache for empty and invalid values.
+  if (!llvm::sys::path::is_absolute(value))
+    return SymbolLocatorSymStore::GetDefaultCachePath();
+
+  return value.str();
 }
 
 // RSDS entries store identity as a 20-byte UUID composed of 16-byte GUID and
@@ -457,3 +465,18 @@ SymbolLocatorSymStore::ParseEnvSymbolPaths(llvm::StringRef val) {
 
   return result;
 }
+
+std::string SymbolLocatorSymStore::GetDefaultCachePath() {
+  // Fall back to the platform cache directory.
+  llvm::SmallString<128> cache_dir;
+  if (llvm::sys::path::cache_directory(cache_dir)) {
+    llvm::sys::path::append(cache_dir, "lldb", "symstore");
+    return cache_dir.str().str();
+  }
+  // Last resort: use a subdirectory of the system temp directory.
+  constexpr bool erase_on_reboot = false;
+  llvm::sys::path::system_temp_directory(erase_on_reboot, cache_dir);
+  llvm::sys::path::append(cache_dir, "lldb", "symstore");
+  return cache_dir.str().str();
+}
+
diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h
index de2a1c6b9bdd5..deb2419a67d9a 100644
--- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h
@@ -50,6 +50,7 @@ class SymbolLocatorSymStore : public SymbolLocator {
   };
 
   static std::vector<LookupEntry> ParseEnvSymbolPaths(llvm::StringRef val);
+  static std::string GetDefaultCachePath();
 };
 
 } // namespace lldb_private
diff --git a/lldb/unittests/Symbol/SymStoreTest.cpp b/lldb/unittests/Symbol/SymStoreTest.cpp
index b08cdadf74400..cf9c509443228 100644
--- a/lldb/unittests/Symbol/SymStoreTest.cpp
+++ b/lldb/unittests/Symbol/SymStoreTest.cpp
@@ -74,7 +74,8 @@ TEST(SymStoreTest, ParseEnvSymbolPaths_Cache) {
             returns());
   EXPECT_EQ(check("SRV*http://localhost"), returns());
 
-  // No cache without a server
+  // No cache without a server.
+  EXPECT_EQ(check("cache*"), returns());
   EXPECT_EQ(check("cache*C:\\symcache"), returns());
   EXPECT_EQ(check("cache*C:\\symcache;D:\\sym"), returns());
 
@@ -82,7 +83,6 @@ TEST(SymStoreTest, ParseEnvSymbolPaths_Cache) {
   EXPECT_EQ(check("SRV*C:\\symcache*\\\\corp\\symbols"),
             returns("C:\\symcache"));
   EXPECT_EQ(check("D:\\sym;srv*C:\\symcache*D:\\sym"), returns("C:\\symcache"));
-  EXPECT_EQ(check("srv**https://symbols.mozilla.org"), returns(""));
 
   // Implicit caches for following symbol servers
   EXPECT_EQ(check("cache*D:\\s;srv*\\\\corp"), returns("D:\\s"));
@@ -95,7 +95,12 @@ TEST(SymStoreTest, ParseEnvSymbolPaths_Cache) {
   EXPECT_EQ(check("srv*\\\\corp;SRV*C:\\X*http://localhost;cache*D:\\s"),
             returns("C:\\X"));
 
-  // Symbol server with custom implementation (unsupported)
+  // Fall back to default cache.
+  auto default_cache = SymbolLocatorSymStore::GetDefaultCachePath();
+  EXPECT_EQ(check("cache*;srv*\\\\corp"), returns(default_cache));
+  EXPECT_EQ(check("srv**https://symbols.mozilla.org"), returns(default_cache));
+
+  // Symbol server with custom implementation (unsupported).
   EXPECT_EQ(check("symsrv*symsrv.dll*https://symbols.mozilla.org"), returns());
   EXPECT_EQ(check("symsrv*symsrv.dll*C:\\symbols*https://symbols.mozilla.org"),
             returns());

>From 6560044567c4de7cc43f2e4ce68acae58784bff1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Mon, 13 Apr 2026 23:06:07 +0200
Subject: [PATCH 09/12] Add missing punctation in comments.

---
 lldb/unittests/Symbol/SymStoreTest.cpp | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/lldb/unittests/Symbol/SymStoreTest.cpp b/lldb/unittests/Symbol/SymStoreTest.cpp
index cf9c509443228..8f1020f9d7ee5 100644
--- a/lldb/unittests/Symbol/SymStoreTest.cpp
+++ b/lldb/unittests/Symbol/SymStoreTest.cpp
@@ -22,21 +22,21 @@ TEST(SymStoreTest, ParseEnvSymbolPaths_Srv) {
   };
   auto returns = [](auto... strs) { return std::vector<std::string>{strs...}; };
 
-  // Local paths
+  // Local paths.
   EXPECT_EQ(check(""), returns());
   EXPECT_EQ(check("C:\\ProgramData\\Symbols"),
             returns("C:\\ProgramData\\Symbols"));
   EXPECT_EQ(check("C:\\symbols;\\\\buildserver\\syms;file://D:/pdb"),
             returns("C:\\symbols", "\\\\buildserver\\syms", "file://D:/pdb"));
 
-  // Symbol servers
+  // Symbol servers.
   EXPECT_EQ(check("srv*https://msdl.microsoft.com/download/symbols"),
             returns("https://msdl.microsoft.com/download/symbols"));
   EXPECT_EQ(check("Srv*https://msdl.microsoft.com/download/symbols"),
             returns("https://msdl.microsoft.com/download/symbols"));
   EXPECT_EQ(check("SRV*http://localhost"), returns("http://localhost"));
 
-  // Symbol servers and local paths with caches
+  // Symbol servers and local paths with caches.
   EXPECT_EQ(check("SRV*C:\\symcache*\\\\corp\\symbols"),
             returns("\\\\corp\\symbols"));
   EXPECT_EQ(check("D:\\sym;srv*C:\\symcache*D:\\sym"),
@@ -44,14 +44,14 @@ TEST(SymStoreTest, ParseEnvSymbolPaths_Srv) {
   EXPECT_EQ(check("srv**https://symbols.mozilla.org"),
             returns("https://symbols.mozilla.org"));
 
-  // Symbol server with custom implementation (unsupported)
+  // Symbol server with custom implementation (unsupported).
   EXPECT_EQ(check("symsrv*symsrv.dll*https://symbols.mozilla.org"), returns());
   EXPECT_EQ(check("symsrv*symsrv.dll*C:\\symbols*https://symbols.mozilla.org"),
             returns());
   EXPECT_EQ(check("symsrv*https://symbols.mozilla.org;D:\\sym"),
             returns("D:\\sym"));
 
-  // Partially invalid specs
+  // Partially invalid specs.
   EXPECT_EQ(check("srv*;;D:\\sym;SRV*"), returns("", "D:\\sym", ""));
   EXPECT_EQ(check("srv*D:\\1*D:\\2*D:\\3;D:\\sym"), returns("D:\\sym"));
   EXPECT_EQ(check("symsrv*D:\\1;D:\\sym"), returns("D:\\sym"));
@@ -67,7 +67,7 @@ TEST(SymStoreTest, ParseEnvSymbolPaths_Cache) {
   };
   auto returns = [](auto... strs) { return std::vector<std::string>{strs...}; };
 
-  // No caches
+  // No caches.
   EXPECT_EQ(check(""), returns());
   EXPECT_EQ(check("C:\\ProgramData\\Symbols"), returns());
   EXPECT_EQ(check("C:\\symbols;\\\\buildserver\\syms;file://D:/pdb"),
@@ -79,12 +79,12 @@ TEST(SymStoreTest, ParseEnvSymbolPaths_Cache) {
   EXPECT_EQ(check("cache*C:\\symcache"), returns());
   EXPECT_EQ(check("cache*C:\\symcache;D:\\sym"), returns());
 
-  // Explicit caches for symbol servers
+  // Explicit caches for symbol servers.
   EXPECT_EQ(check("SRV*C:\\symcache*\\\\corp\\symbols"),
             returns("C:\\symcache"));
   EXPECT_EQ(check("D:\\sym;srv*C:\\symcache*D:\\sym"), returns("C:\\symcache"));
 
-  // Implicit caches for following symbol servers
+  // Implicit caches for following symbol servers.
   EXPECT_EQ(check("cache*D:\\s;srv*\\\\corp"), returns("D:\\s"));
   EXPECT_EQ(check("CACHE*D:\\s;srv*\\\\corp;SRV*http://localhost"),
             returns("D:\\s", "D:\\s"));
@@ -105,7 +105,7 @@ TEST(SymStoreTest, ParseEnvSymbolPaths_Cache) {
   EXPECT_EQ(check("symsrv*symsrv.dll*C:\\symbols*https://symbols.mozilla.org"),
             returns());
 
-  // Partially invalid specs
+  // Partially invalid specs.
   EXPECT_EQ(check("cache*C:\\1;;D:\\sym;SRV*"), returns("C:\\1"));
   EXPECT_EQ(check("cache*C:\\1;srv*D:\\1*D:\\2*D:\\3;srv*D:\\sym"),
             returns("C:\\1"));

>From 54a48b081f67c3625246f6b1974a93c2763f9372 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Tue, 14 Apr 2026 12:28:48 +0200
Subject: [PATCH 10/12] Drop extra newline at EOF

---
 .../Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp     | 1 -
 1 file changed, 1 deletion(-)

diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
index a5a3e3e79bde9..565ea9ac5c4ba 100644
--- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
@@ -479,4 +479,3 @@ std::string SymbolLocatorSymStore::GetDefaultCachePath() {
   llvm::sys::path::append(cache_dir, "lldb", "symstore");
   return cache_dir.str().str();
 }
-

>From ca1ef31255cc2f25620c2c29eb547dbc65fc8048 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Tue, 14 Apr 2026 12:38:27 +0200
Subject: [PATCH 11/12] Fix header lldb/unittests/Symbol/SymStoreTest.cpp

Co-authored-by: Nerixyz <nero.9 at hotmail.de>
---
 lldb/unittests/Symbol/SymStoreTest.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lldb/unittests/Symbol/SymStoreTest.cpp b/lldb/unittests/Symbol/SymStoreTest.cpp
index 8f1020f9d7ee5..dcbc193c78650 100644
--- a/lldb/unittests/Symbol/SymStoreTest.cpp
+++ b/lldb/unittests/Symbol/SymStoreTest.cpp
@@ -1,4 +1,4 @@
-//===-- SymbolsTest.cpp ---------------------------------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.

>From f15fb528da8c37fed3060680902beafef768f64f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Tue, 14 Apr 2026 13:09:36 +0200
Subject: [PATCH 12/12] Move cache path validation to LocateSymStoreEntry()
 after 2f6b6ceee004

---
 .../SymStore/SymbolLocatorSymStore.cpp        | 48 +++++++++----------
 1 file changed, 22 insertions(+), 26 deletions(-)

diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
index 565ea9ac5c4ba..f9cb7adc52c5e 100644
--- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
@@ -154,11 +154,11 @@ ParseSrvEntry(llvm::StringRef entry) {
   case 2:
     return MakeLookupEntry(parts[1]);
   case 3: {
-    if (llvm::sys::path::is_absolute(parts[1]))
-      return MakeLookupEntry(parts[2], parts[1]);
-    // Fall back to LLDB's default cache for empty and invalid cache values.
-    return MakeLookupEntry(parts[2],
-                           SymbolLocatorSymStore::GetDefaultCachePath());
+    // Fall back to LLDB's default cache for empty values.
+    if (parts[1].empty())
+      return MakeLookupEntry(parts[2],
+                             SymbolLocatorSymStore::GetDefaultCachePath());
+    return MakeLookupEntry(parts[2], parts[1]);
   }
   default:
     return {}; // Ignore entries with invalid number of parts.
@@ -178,8 +178,8 @@ std::optional<std::string> ParseCacheEntry(llvm::StringRef entry) {
   if (parts.size() == 2)
     value = parts.back();
 
-  // Fall back to LLDB's default cache for empty and invalid values.
-  if (!llvm::sys::path::is_absolute(value))
+  // Fall back to LLDB's default cache for empty values.
+  if (value.empty())
     return SymbolLocatorSymStore::GetDefaultCachePath();
 
   return value.str();
@@ -229,7 +229,7 @@ RequestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key,
     return {};
   }
 
-  // Download into a temporary file. Cache coming soon.
+  // Download into a temporary file.
   llvm::SmallString<128> tmp_file;
   constexpr bool erase_on_reboot = true;
   path::system_temp_directory(erase_on_reboot, tmp_file);
@@ -343,29 +343,25 @@ LocateSymStoreEntry(const SymbolLocatorSymStore::LookupEntry &entry,
 
   llvm::StringRef url = entry.source;
   if (url.starts_with("http://") || url.starts_with("https://")) {
+    // Always fall back LLDB default cache. After all, once the files have been
+    // successfully downloaded, we want to save them somewhere.
+    std::string cache_path = GetGlobalPluginProperties().GetCachePath();
+
+    // Override, if the entry has a valid cache path.
+    if (entry.cache && llvm::sys::path::is_absolute(*entry.cache))
+      cache_path = *entry.cache;
+
     // Check cache first.
-    if (entry.cache) {
-      if (auto spec = FindFileInLocalSymStore(*entry.cache, key, pdb_name)) {
-        LLDB_LOG_VERBOSE(log, "Found {0} in SymStore cache {1}", pdb_name,
-                         *entry.cache);
-        return *spec;
-      }
-    } else {
-      // Check LLDB default cache to avoid duplicate downloads in the same
-      // session.
-      if (auto spec = FindFileInLocalSymStore(default_cache, key, pdb_name)) {
-        LLDB_LOG_VERBOSE(log, "Found {0} in SymStore cache {1}", pdb_name,
-                         default_cache);
-        return *spec;
-      }
+    if (auto spec = FindFileInLocalSymStore(cache_path, key, pdb_name)) {
+      LLDB_LOG(log, "Found {0} in SymStore cache {1}", pdb_name, cache_path);
+      return *spec;
     }
 
     // Download and move to cache.
     if (auto spec = RequestFileFromSymStoreServerHTTP(url, key, pdb_name)) {
-      LLDB_LOG_VERBOSE(log, "Downloaded {0} from SymStore {1}", pdb_name, url);
-      std::string cache = entry.cache.value_or(default_cache);
-      spec = MoveToLocalSymStore(cache, key, pdb_name, *spec);
-      LLDB_LOG_VERBOSE(log, "Added {0} to SymStore cache {1}", pdb_name, cache);
+      LLDB_LOG(log, "Downloaded {0} from SymStore {1}", pdb_name, url);
+      spec = MoveToLocalSymStore(cache_path, key, pdb_name, *spec);
+      LLDB_LOG(log, "Added {0} to SymStore cache {1}", pdb_name, cache_path);
       return *spec;
     }
 



More information about the lldb-commits mailing list