[Lldb-commits] [lldb] Reland "[lldb] Initial plugin and test for SymbolLocatorSymStore" (PR #185658)

Stefan Gränitz via lldb-commits lldb-commits at lists.llvm.org
Thu Mar 12 08:14:43 PDT 2026


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

>From 069d77c58181e8e9dde30b97434bcbd97e6ff40e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Tue, 10 Mar 2026 13:43:53 +0100
Subject: [PATCH 1/7] Reland "[lldb] Initial plugin and test for
 SymbolLocatorSymStore"

---
 .../ObjectFile/PECOFF/ObjectFilePECOFF.cpp    |  21 +++
 .../ObjectFile/PECOFF/ObjectFilePECOFF.h      |   2 +
 .../Plugins/SymbolLocator/CMakeLists.txt      |   1 +
 .../SymbolLocator/SymStore/CMakeLists.txt     |  20 +++
 .../SymStore/SymbolLocatorSymStore.cpp        | 147 ++++++++++++++++++
 .../SymStore/SymbolLocatorSymStore.h          |  50 ++++++
 .../SymbolLocatorSymStoreProperties.td        |   7 +
 .../PECOFF/SymbolVendorPECOFF.cpp             |  48 +++---
 lldb/test/API/symstore/Makefile               |   2 +
 lldb/test/API/symstore/TestSymStoreLocal.py   | 121 ++++++++++++++
 lldb/test/API/symstore/main.c                 |   5 +
 11 files changed, 401 insertions(+), 23 deletions(-)
 create mode 100644 lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt
 create mode 100644 lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
 create mode 100644 lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h
 create mode 100644 lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td
 create mode 100644 lldb/test/API/symstore/Makefile
 create mode 100644 lldb/test/API/symstore/TestSymStoreLocal.py
 create mode 100644 lldb/test/API/symstore/main.c

diff --git a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
index 3a17b4c46a788..cec47d96b33d2 100644
--- a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
+++ b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
@@ -1108,6 +1108,27 @@ std::optional<FileSpec> ObjectFilePECOFF::GetDebugLink() {
   return std::nullopt;
 }
 
+std::optional<FileSpec> ObjectFilePECOFF::GetPDBPath() {
+  llvm::StringRef pdb_file;
+  const llvm::codeview::DebugInfo *pdb_info = nullptr;
+  if (llvm::Error Err = m_binary->getDebugPDBInfo(pdb_info, pdb_file)) {
+    // DebugInfo section is corrupt.
+    Log *log = GetLog(LLDBLog::Object);
+    llvm::StringRef file = m_binary->getFileName();
+    LLDB_LOG_ERROR(
+        log, std::move(Err),
+        "Failed to read Codeview record for PDB debug info file ({1}): {0}",
+        file);
+    return std::nullopt;
+  }
+  if (pdb_file.empty()) {
+    // No DebugInfo section present.
+    return std::nullopt;
+  }
+  return FileSpec(pdb_file, FileSpec::GuessPathStyle(pdb_file).value_or(
+                                FileSpec::Style::native));
+}
+
 uint32_t ObjectFilePECOFF::ParseDependentModules() {
   ModuleSP module_sp(GetModule());
   if (!module_sp)
diff --git a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h
index 8002e70e604bb..30bd672dc68f8 100644
--- a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h
+++ b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h
@@ -130,6 +130,8 @@ class ObjectFilePECOFF : public lldb_private::ObjectFile {
   /// contains it.
   std::optional<lldb_private::FileSpec> GetDebugLink();
 
+  std::optional<lldb_private::FileSpec> GetPDBPath();
+
   uint32_t GetDependentModules(lldb_private::FileSpecList &files) override;
 
   lldb_private::Address GetEntryPointAddress() override;
diff --git a/lldb/source/Plugins/SymbolLocator/CMakeLists.txt b/lldb/source/Plugins/SymbolLocator/CMakeLists.txt
index 3b466f71dca58..9b9ec470b86a9 100644
--- a/lldb/source/Plugins/SymbolLocator/CMakeLists.txt
+++ b/lldb/source/Plugins/SymbolLocator/CMakeLists.txt
@@ -6,6 +6,7 @@ set_property(DIRECTORY PROPERTY LLDB_PLUGIN_KIND SymbolLocator)
 # prevents an unstripped binary from being requested from the Debuginfod
 # provider.
 add_subdirectory(Debuginfod)
+add_subdirectory(SymStore)
 add_subdirectory(Default)
 if (CMAKE_SYSTEM_NAME MATCHES "Darwin")
   add_subdirectory(DebugSymbols)
diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt b/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt
new file mode 100644
index 0000000000000..b0da27f26c6a8
--- /dev/null
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt
@@ -0,0 +1,20 @@
+lldb_tablegen(SymbolLocatorSymStoreProperties.inc -gen-lldb-property-defs
+  SOURCE SymbolLocatorSymStoreProperties.td
+  TARGET LLDBPluginSymbolLocatorSymStorePropertiesGen)
+
+lldb_tablegen(SymbolLocatorSymStorePropertiesEnum.inc -gen-lldb-property-enum-defs
+  SOURCE SymbolLocatorSymStoreProperties.td
+  TARGET LLDBPluginSymbolLocatorSymStorePropertiesEnumGen)
+
+add_lldb_library(lldbPluginSymbolLocatorSymStore PLUGIN
+  SymbolLocatorSymStore.cpp
+
+  LINK_LIBS
+    lldbCore
+    lldbHost
+    lldbSymbol
+  )
+
+add_dependencies(lldbPluginSymbolLocatorSymStore
+  LLDBPluginSymbolLocatorSymStorePropertiesGen
+  LLDBPluginSymbolLocatorSymStorePropertiesEnumGen)
diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
new file mode 100644
index 0000000000000..d008a7d3e8e9a
--- /dev/null
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp
@@ -0,0 +1,147 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/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() {
+  // First version can only locate PDB in local SymStore (no download yet).
+  PluginManager::RegisterPlugin(
+      GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance,
+      nullptr, LocateExecutableSymbolFile, nullptr, nullptr,
+      SymbolLocatorSymStore::DebuggerInitialize);
+}
+
+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::StringRef SymbolLocatorSymStore::GetPluginDescriptionStatic() {
+  return "Symbol locator for PDB in SymStore";
+}
+
+SymbolLocator *SymbolLocatorSymStore::CreateInstance() {
+  return new SymbolLocatorSymStore();
+}
+
+// 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
+//
+static 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);
+}
+
+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_LOGV(log, "Failed to resolve symbol PDB module: PDB name empty");
+    return {};
+  }
+
+  LLDB_LOGV(log, "LocateExecutableSymbolFile {0} with UUID {1}", pdb_name,
+            uuid.GetAsString());
+  if (uuid.GetBytes().size() != 20) {
+    LLDB_LOGV(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) {
+    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;
+    }
+  }
+
+  return {};
+}
diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h
new file mode 100644
index 0000000000000..52ec04cae387b
--- /dev/null
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h
@@ -0,0 +1,50 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_PLUGINS_SYMBOLLOCATOR_SYMSTORE_SYMBOLLOCATORSYMSTORE_H
+#define LLDB_SOURCE_PLUGINS_SYMBOLLOCATOR_SYMSTORE_SYMBOLLOCATORSYMSTORE_H
+
+#include "lldb/Core/Debugger.h"
+#include "lldb/Symbol/SymbolLocator.h"
+#include "lldb/lldb-private.h"
+
+namespace lldb_private {
+
+/// This plugin implements lookup in Microsoft SymStore instances. This can work
+/// cross-platform and for arbitrary debug info formats, but the focus is on PDB
+/// with PE/COFF binaries on Windows.
+class SymbolLocatorSymStore : public SymbolLocator {
+public:
+  SymbolLocatorSymStore();
+
+  static void Initialize();
+  static void Terminate();
+  static void DebuggerInitialize(Debugger &debugger);
+
+  static llvm::StringRef GetPluginNameStatic() { return "symstore"; }
+  static llvm::StringRef GetPluginDescriptionStatic();
+
+  static lldb_private::SymbolLocator *CreateInstance();
+
+  /// PluginInterface protocol.
+  /// \{
+  llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
+  /// \}
+
+  // Locate the symbol file given a module specification.
+  //
+  // Locating the file should happen only on the local computer or using the
+  // current computers global settings.
+  static std::optional<FileSpec>
+  LocateExecutableSymbolFile(const ModuleSpec &module_spec,
+                             const FileSpecList &default_search_paths);
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_SYMBOLLOCATOR_SYMSTORE_SYMBOLLOCATORSYMSTORE_H
diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td
new file mode 100644
index 0000000000000..0cd631a80b90b
--- /dev/null
+++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td
@@ -0,0 +1,7 @@
+include "../../../../include/lldb/Core/PropertiesBase.td"
+
+let Definition = "symbollocatorsymstore", Path = "plugin.symbol-locator.symstore" in {
+  def SymStoreURLs : Property<"urls", "Array">,
+    ElementType<"String">,
+    Desc<"List of local symstore directories to query for symbols">;
+}
diff --git a/lldb/source/Plugins/SymbolVendor/PECOFF/SymbolVendorPECOFF.cpp b/lldb/source/Plugins/SymbolVendor/PECOFF/SymbolVendorPECOFF.cpp
index 20ccfa54a106c..1797e5b7677ee 100644
--- a/lldb/source/Plugins/SymbolVendor/PECOFF/SymbolVendorPECOFF.cpp
+++ b/lldb/source/Plugins/SymbolVendor/PECOFF/SymbolVendorPECOFF.cpp
@@ -71,6 +71,9 @@ SymbolVendorPECOFF::CreateInstance(const lldb::ModuleSP &module_sp,
 
   // If the module specified a filespec, use that.
   FileSpec fspec = module_sp->GetSymbolFileFileSpec();
+  // Otherwise, use the PDB path from CodeView.
+  if (!fspec)
+    fspec = obj_file->GetPDBPath().value_or(FileSpec());
   // Otherwise, try gnu_debuglink, if one exists.
   if (!fspec)
     fspec = obj_file->GetDebugLink().value_or(FileSpec());
@@ -101,31 +104,30 @@ SymbolVendorPECOFF::CreateInstance(const lldb::ModuleSP &module_sp,
   // This objfile is for debugging purposes.
   dsym_objfile_sp->SetType(ObjectFile::eTypeDebugInfo);
 
-  // Get the module unified section list and add our debug sections to
-  // that.
+  // For DWARF get the module unified section list and add our debug sections
+  // to that.
   SectionList *module_section_list = module_sp->GetSectionList();
   SectionList *objfile_section_list = dsym_objfile_sp->GetSectionList();
-  if (!objfile_section_list || !module_section_list)
-    return nullptr;
-
-  static const SectionType g_sections[] = {
-      eSectionTypeDWARFDebugAbbrev,   eSectionTypeDWARFDebugAranges,
-      eSectionTypeDWARFDebugFrame,    eSectionTypeDWARFDebugInfo,
-      eSectionTypeDWARFDebugLine,     eSectionTypeDWARFDebugLoc,
-      eSectionTypeDWARFDebugLocLists, eSectionTypeDWARFDebugMacInfo,
-      eSectionTypeDWARFDebugNames,    eSectionTypeDWARFDebugPubNames,
-      eSectionTypeDWARFDebugPubTypes, eSectionTypeDWARFDebugRanges,
-      eSectionTypeDWARFDebugStr,      eSectionTypeDWARFDebugTypes,
-  };
-  for (SectionType section_type : g_sections) {
-    if (SectionSP section_sp =
-            objfile_section_list->FindSectionByType(section_type, true)) {
-      if (SectionSP module_section_sp =
-              module_section_list->FindSectionByType(section_type, true))
-        module_section_list->ReplaceSection(module_section_sp->GetID(),
-                                            section_sp);
-      else
-        module_section_list->AddSection(section_sp);
+  if (objfile_section_list && module_section_list) {
+    static const SectionType g_sections[] = {
+        eSectionTypeDWARFDebugAbbrev,   eSectionTypeDWARFDebugAranges,
+        eSectionTypeDWARFDebugFrame,    eSectionTypeDWARFDebugInfo,
+        eSectionTypeDWARFDebugLine,     eSectionTypeDWARFDebugLoc,
+        eSectionTypeDWARFDebugLocLists, eSectionTypeDWARFDebugMacInfo,
+        eSectionTypeDWARFDebugNames,    eSectionTypeDWARFDebugPubNames,
+        eSectionTypeDWARFDebugPubTypes, eSectionTypeDWARFDebugRanges,
+        eSectionTypeDWARFDebugStr,      eSectionTypeDWARFDebugTypes,
+    };
+    for (SectionType section_type : g_sections) {
+      if (SectionSP section_sp =
+              objfile_section_list->FindSectionByType(section_type, true)) {
+        if (SectionSP module_section_sp =
+                module_section_list->FindSectionByType(section_type, true))
+          module_section_list->ReplaceSection(module_section_sp->GetID(),
+                                              section_sp);
+        else
+          module_section_list->AddSection(section_sp);
+      }
     }
   }
 
diff --git a/lldb/test/API/symstore/Makefile b/lldb/test/API/symstore/Makefile
new file mode 100644
index 0000000000000..c9319d6e6888a
--- /dev/null
+++ b/lldb/test/API/symstore/Makefile
@@ -0,0 +1,2 @@
+C_SOURCES := main.c
+include Makefile.rules
diff --git a/lldb/test/API/symstore/TestSymStoreLocal.py b/lldb/test/API/symstore/TestSymStoreLocal.py
new file mode 100644
index 0000000000000..154af876fea3f
--- /dev/null
+++ b/lldb/test/API/symstore/TestSymStoreLocal.py
@@ -0,0 +1,121 @@
+import os
+import shutil
+import tempfile
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+
+
+"""
+Test debug symbol acquisition from a local SymStore repository. This can work
+cross-platform and for arbitrary debug info formats. We only support PDB
+currently.
+"""
+
+
+class MockedSymStore:
+    """
+    Context Manager to populate a file structure equivalent to SymStore.exe in a
+    temporary directory.
+    """
+
+    def __init__(self, test, exe, pdb):
+        self._test = test
+        self._exe = exe
+        self._pdb = pdb
+        self._tmp = None
+
+    def get_key_pdb(self, exe):
+        """
+        Module UUID: 12345678-1234-5678-9ABC-DEF012345678-00000001
+        To SymStore key: 12345678123456789ABCDEF0123456781
+        """
+        spec = lldb.SBModuleSpec()
+        spec.SetFileSpec(lldb.SBFileSpec(self._test.getBuildArtifact(exe)))
+        module = lldb.SBModule(spec)
+        raw = module.GetUUIDString().replace("-", "").upper()
+        if len(raw) != 40:
+            raise RuntimeError("Unexpected number of bytes in embedded UUID")
+        guid_hex = raw[:32]
+        age = int(raw[32:], 16)
+        return guid_hex + str(age)
+
+    def __enter__(self):
+        """
+        Mock local symstore directory tree, move PDB there and report path.
+        """
+        key = None
+        if self._test.getDebugInfo() == "pdb":
+            key = self.get_key_pdb(self._exe)
+        self._test.assertIsNotNone(key)
+        self._tmp = tempfile.mkdtemp()
+        pdb_dir = os.path.join(self._tmp, self._pdb, key)
+        os.makedirs(pdb_dir)
+        shutil.move(
+            self._test.getBuildArtifact(self._pdb),
+            os.path.join(pdb_dir, self._pdb),
+        )
+        return self._tmp
+
+    def __exit__(self, *exc_info):
+        """
+        Clean up and delete original exe so next make won't skip link command.
+        """
+        shutil.rmtree(self._tmp)
+        self._test.runCmd("settings clear plugin.symbol-locator.symstore")
+        os.remove(self._test.getBuildArtifact(self._exe))
+
+
+class SymStoreLocalTests(TestBase):
+    TEST_WITH_PDB_DEBUG_INFO = True
+
+    def build_inferior(self):
+        if self.getDebugInfo() != "pdb":
+            self.skipTest("Non-PDB debug info variants not yet supported")
+        self.build()
+        exe_file = "a.out"
+        sym_file = "a.pdb"
+        self.assertTrue(os.path.isfile(self.getBuildArtifact(exe_file)))
+        self.assertTrue(os.path.isfile(self.getBuildArtifact(sym_file)))
+        return exe_file, sym_file
+
+    def try_breakpoint(self, exe, should_have_loc, ext_lookup=True):
+        enable = "true" if ext_lookup else "false"
+        self.runCmd(f"settings set symbols.enable-external-lookup {enable}")
+        target = self.dbg.CreateTarget(self.getBuildArtifact(exe))
+        self.assertTrue(target and target.IsValid(), "Target is valid")
+        bp = target.BreakpointCreateByName("func")
+        self.assertTrue(bp and bp.IsValid(), "Breakpoint is valid")
+        self.assertEqual(bp.GetNumLocations(), 1 if should_have_loc else 0)
+        self.dbg.DeleteTarget(target)
+
+    def test_no_symstore_url(self):
+        """
+        Check that breakpoint doesn't resolve without SymStore.
+        """
+        exe, sym = self.build_inferior()
+        with MockedSymStore(self, exe, sym):
+            self.try_breakpoint(exe, should_have_loc=False)
+
+    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}"
+            )
+            self.try_breakpoint(exe, ext_lookup=False, should_have_loc=False)
+
+    def test_basic(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}"
+            )
+            self.try_breakpoint(exe, should_have_loc=True)
diff --git a/lldb/test/API/symstore/main.c b/lldb/test/API/symstore/main.c
new file mode 100644
index 0000000000000..a95762e80ea44
--- /dev/null
+++ b/lldb/test/API/symstore/main.c
@@ -0,0 +1,5 @@
+int func(int argc, const char *argv[]) {
+  return (argc + 1) * (argv[argc][0] + 2);
+}
+
+int main(int argc, const char *argv[]) { return func(0, argv); }

>From f9d329ce25fca6f60967dce8e1aa8540672b2b35 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Tue, 10 Mar 2026 14:08:26 +0100
Subject: [PATCH 2/7] Create mock symstore in build directory and not in
 mkdtemp

---
 lldb/test/API/symstore/TestSymStoreLocal.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lldb/test/API/symstore/TestSymStoreLocal.py b/lldb/test/API/symstore/TestSymStoreLocal.py
index 154af876fea3f..cedc76ac4354c 100644
--- a/lldb/test/API/symstore/TestSymStoreLocal.py
+++ b/lldb/test/API/symstore/TestSymStoreLocal.py
@@ -49,7 +49,7 @@ def __enter__(self):
         if self._test.getDebugInfo() == "pdb":
             key = self.get_key_pdb(self._exe)
         self._test.assertIsNotNone(key)
-        self._tmp = tempfile.mkdtemp()
+        self._tmp = self._test.getBuildArtifact("tmp")
         pdb_dir = os.path.join(self._tmp, self._pdb, key)
         os.makedirs(pdb_dir)
         shutil.move(
@@ -63,8 +63,8 @@ def __exit__(self, *exc_info):
         Clean up and delete original exe so next make won't skip link command.
         """
         shutil.rmtree(self._tmp)
-        self._test.runCmd("settings clear plugin.symbol-locator.symstore")
         os.remove(self._test.getBuildArtifact(self._exe))
+        self._test.runCmd("settings clear plugin.symbol-locator.symstore")
 
 
 class SymStoreLocalTests(TestBase):
@@ -109,7 +109,7 @@ def test_external_lookup_off(self):
             )
             self.try_breakpoint(exe, ext_lookup=False, should_have_loc=False)
 
-    def test_basic(self):
+    def test_local_dir(self):
         """
         Check that breakpoint resolves with local SymStore.
         """

>From a0f995fbea11999b4ce8a603b958243e9a06d833 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Wed, 11 Mar 2026 14:35:37 +0100
Subject: [PATCH 3/7] Stop checking log output in
 NativePDB/find-pdb-next-to-exe.test

---
 .../Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test     | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test b/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test
index c35c82ad84d2f..8e71225a5470e 100644
--- a/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test
+++ b/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test
@@ -16,14 +16,12 @@
 # Regular setup - PDB is at the original path
 # RUN: %lldb -S %t/init.input -s %t/check.input %t/build/a.exe | FileCheck --check-prefix=BOTH-ORIG %s
 # BOTH-ORIG: (lldb) target create
-# BOTH-ORIG-NEXT: Loading {{.*[/\\]}}build{{[/\\]}}a.pdb for {{.*[/\\]}}build{{[/\\]}}a.exe
 # BOTH-ORIG: (A) a = (x = 47)
 
 # Move the executable to a different directory but keep the PDB.
 # RUN: mv %t/build/a.exe %t/dir1
 # RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=PDB-ORIG %s
 # PDB-ORIG: (lldb) target create
-# PDB-ORIG-NEXT: Loading {{.*[/\\]}}build{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe
 # PDB-ORIG: (A) a = (x = 47)
 
 # Copy the PDB to the same directory and all search dirs. LLDB should prefer the original PDB.
@@ -36,21 +34,18 @@
 # RUN: rm %t/build/a.pdb
 # RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=NEXT-TO-EXE %s
 # NEXT-TO-EXE: (lldb) target create
-# NEXT-TO-EXE-NEXT: Loading {{.*[/\\]}}dir1{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe
 # NEXT-TO-EXE: (A) a = (x = 47)
 
 # Remove the PDB next to the exe. LLDB should now use the one in dir2 (first in list).
 # RUN: rm %t/dir1/a.pdb
 # RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=DIR2 %s
 # DIR2: (lldb) target create
-# DIR2-NEXT: Loading {{.*[/\\]}}dir2{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe
 # DIR2: (A) a = (x = 47)
 
 # Remove the PDB in dir2. LLDB should now use the one in dir3 (second in list).
 # RUN: rm %t/dir2/a.pdb
 # RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=DIR3 %s
 # DIR3: (lldb) target create
-# DIR3-NEXT: Loading {{.*[/\\]}}dir3{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe
 # DIR3: (A) a = (x = 47)
 
 # Remove the last PDB in dir3. Now, there's no matching PDB anymore.

>From 841e342ddc5e07dd8db2e9879305d32e37fb3a49 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Wed, 11 Mar 2026 15:12:15 +0100
Subject: [PATCH 4/7] Access object file through module once more

---
 lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp b/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp
index e35195fec2efc..26b89eaefd37a 100644
--- a/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp
+++ b/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp
@@ -239,7 +239,8 @@ uint32_t SymbolFilePDB::CalculateAbilities() {
 
   if (!m_session_up) {
     // Lazily load and match the PDB file, but only do this once.
-    std::string exePath = m_objfile_sp->GetFileSpec().GetPath();
+    std::string exePath =
+        m_objfile_sp->GetModule()->GetObjectFile()->GetFileSpec().GetPath();
     auto error = loadDataForEXE(PDB_ReaderType::DIA, llvm::StringRef(exePath),
                                 m_session_up);
     if (error) {

>From 4a2facdb024dd8ed18e3045e647e84d419d4c4be 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 15:06:12 +0100
Subject: [PATCH 5/7] Move Loading log from loadMatchingPDBFile() to
 CalculateAbilities()

---
 .../Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp     | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp
index bed15839be1cd..b094e7afc3dac 100644
--- a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp
+++ b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp
@@ -169,8 +169,6 @@ loadMatchingPDBFile(std::string exe_path, llvm::BumpPtrAllocator &allocator) {
   if (expected_info->getGuid() != guid)
     return nullptr;
 
-  LLDB_LOG(GetLog(LLDBLog::Symbols), "Loading {0} for {1}", pdb->getFilePath(),
-           exe_path);
   return pdb;
 }
 
@@ -398,6 +396,9 @@ uint32_t SymbolFileNativePDB::CalculateAbilities() {
     if (!pdb_file)
       return 0;
 
+    LLDB_LOG(GetLog(LLDBLog::Symbols), "Loading {0} for {1}", pdb_file->getFilePath(),
+             m_objfile_sp->GetModule()->GetObjectFile()->GetFileSpec().GetPath());
+
     auto expected_index = PdbIndex::create(pdb_file);
     if (!expected_index) {
       llvm::consumeError(expected_index.takeError());

>From e9f2e08c4f9b46681bbd7c60df6e1b4d47e81450 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 15:06:33 +0100
Subject: [PATCH 6/7] Revert "Stop checking log output in
 NativePDB/find-pdb-next-to-exe.test"

This reverts commit a0f995fbea11999b4ce8a603b958243e9a06d833.
---
 .../Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test     | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test b/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test
index 8e71225a5470e..c35c82ad84d2f 100644
--- a/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test
+++ b/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test
@@ -16,12 +16,14 @@
 # Regular setup - PDB is at the original path
 # RUN: %lldb -S %t/init.input -s %t/check.input %t/build/a.exe | FileCheck --check-prefix=BOTH-ORIG %s
 # BOTH-ORIG: (lldb) target create
+# BOTH-ORIG-NEXT: Loading {{.*[/\\]}}build{{[/\\]}}a.pdb for {{.*[/\\]}}build{{[/\\]}}a.exe
 # BOTH-ORIG: (A) a = (x = 47)
 
 # Move the executable to a different directory but keep the PDB.
 # RUN: mv %t/build/a.exe %t/dir1
 # RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=PDB-ORIG %s
 # PDB-ORIG: (lldb) target create
+# PDB-ORIG-NEXT: Loading {{.*[/\\]}}build{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe
 # PDB-ORIG: (A) a = (x = 47)
 
 # Copy the PDB to the same directory and all search dirs. LLDB should prefer the original PDB.
@@ -34,18 +36,21 @@
 # RUN: rm %t/build/a.pdb
 # RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=NEXT-TO-EXE %s
 # NEXT-TO-EXE: (lldb) target create
+# NEXT-TO-EXE-NEXT: Loading {{.*[/\\]}}dir1{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe
 # NEXT-TO-EXE: (A) a = (x = 47)
 
 # Remove the PDB next to the exe. LLDB should now use the one in dir2 (first in list).
 # RUN: rm %t/dir1/a.pdb
 # RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=DIR2 %s
 # DIR2: (lldb) target create
+# DIR2-NEXT: Loading {{.*[/\\]}}dir2{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe
 # DIR2: (A) a = (x = 47)
 
 # Remove the PDB in dir2. LLDB should now use the one in dir3 (second in list).
 # RUN: rm %t/dir2/a.pdb
 # RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=DIR3 %s
 # DIR3: (lldb) target create
+# DIR3-NEXT: Loading {{.*[/\\]}}dir3{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe
 # DIR3: (A) a = (x = 47)
 
 # Remove the last PDB in dir3. Now, there's no matching PDB anymore.

>From 6c09e2d10223b847ade3e8c287889ce35941053c 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 15:14:12 +0100
Subject: [PATCH 7/7] clang-format

---
 .../Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp    | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp
index b094e7afc3dac..d083768c3cdd0 100644
--- a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp
+++ b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp
@@ -396,8 +396,10 @@ uint32_t SymbolFileNativePDB::CalculateAbilities() {
     if (!pdb_file)
       return 0;
 
-    LLDB_LOG(GetLog(LLDBLog::Symbols), "Loading {0} for {1}", pdb_file->getFilePath(),
-             m_objfile_sp->GetModule()->GetObjectFile()->GetFileSpec().GetPath());
+    LLDB_LOG(
+        GetLog(LLDBLog::Symbols), "Loading {0} for {1}",
+        pdb_file->getFilePath(),
+        m_objfile_sp->GetModule()->GetObjectFile()->GetFileSpec().GetPath());
 
     auto expected_index = PdbIndex::create(pdb_file);
     if (!expected_index) {



More information about the lldb-commits mailing list