https://github.com/rchamala updated https://github.com/llvm/llvm-project/pull/181334
>From a0a71fa6a7e83a01ead13b61280aea4f66f689ed Mon Sep 17 00:00:00 2001 From: Rahul Reddy Chamala <[email protected]> Date: Fri, 13 Feb 2026 12:04:38 -0800 Subject: [PATCH] [lldb] Add ScriptedSymbolLocator plugin --- lldb/bindings/python/CMakeLists.txt | 1 + lldb/bindings/python/python-wrapper.swig | 13 ++ .../use/tutorials/scripted-symbol-locator.md | 163 +++++++++++++ .../templates/scripted_symbol_locator.py | 220 ++++++++++++++++++ lldb/include/lldb/API/SBFileSpec.h | 5 + lldb/include/lldb/API/SBTarget.h | 15 ++ lldb/include/lldb/Core/PluginManager.h | 8 +- .../ScriptedSymbolLocatorInterface.h | 56 +++++ .../lldb/Interpreter/ScriptInterpreter.h | 8 + lldb/include/lldb/Symbol/LineEntry.h | 2 +- lldb/include/lldb/Target/Target.h | 31 ++- lldb/include/lldb/lldb-forward.h | 7 +- lldb/include/lldb/lldb-private-interfaces.h | 2 + lldb/source/API/SBTarget.cpp | 30 +++ lldb/source/Commands/CommandObjectTarget.cpp | 140 +++++++++++ lldb/source/Core/Module.cpp | 2 +- lldb/source/Core/PluginManager.cpp | 35 ++- lldb/source/Interpreter/ScriptInterpreter.cpp | 6 + .../Python/Interfaces/CMakeLists.txt | 3 +- .../ScriptInterpreterPythonInterfaces.cpp | 2 + .../ScriptInterpreterPythonInterfaces.h | 1 + .../Interfaces/ScriptedPythonInterface.cpp | 13 ++ .../Interfaces/ScriptedPythonInterface.h | 97 +++++++- .../ScriptedSymbolLocatorPythonInterface.cpp | 126 ++++++++++ .../ScriptedSymbolLocatorPythonInterface.h | 71 ++++++ .../Python/SWIGPythonBridge.h | 1 + .../Python/ScriptInterpreterPython.cpp | 4 + .../Python/ScriptInterpreterPythonImpl.h | 3 + .../Plugins/SymbolLocator/CMakeLists.txt | 1 + .../Debuginfod/SymbolLocatorDebuginfod.cpp | 2 +- .../SymbolLocator/Scripted/CMakeLists.txt | 13 ++ .../Scripted/SymbolLocatorScripted.cpp | 202 ++++++++++++++++ .../Scripted/SymbolLocatorScripted.h | 55 +++++ lldb/source/Symbol/LineEntry.cpp | 22 +- lldb/source/Target/StackFrame.cpp | 2 +- lldb/source/Target/StackFrameList.cpp | 3 +- lldb/source/Target/Target.cpp | 73 ++++++ lldb/source/Target/ThreadPlanStepRange.cpp | 4 +- .../scripted_symbol_locator/Makefile | 9 + .../TestScriptedSymbolLocator.py | 195 ++++++++++++++++ .../scripted_symbol_locator/main.c | 5 + .../scripted_symbol_locator/source_locator.py | 74 ++++++ .../Python/PythonTestSuite.cpp | 4 + 43 files changed, 1698 insertions(+), 31 deletions(-) create mode 100644 lldb/docs/use/tutorials/scripted-symbol-locator.md create mode 100644 lldb/examples/python/templates/scripted_symbol_locator.py create mode 100644 lldb/include/lldb/Interpreter/Interfaces/ScriptedSymbolLocatorInterface.h create mode 100644 lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSymbolLocatorPythonInterface.cpp create mode 100644 lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSymbolLocatorPythonInterface.h create mode 100644 lldb/source/Plugins/SymbolLocator/Scripted/CMakeLists.txt create mode 100644 lldb/source/Plugins/SymbolLocator/Scripted/SymbolLocatorScripted.cpp create mode 100644 lldb/source/Plugins/SymbolLocator/Scripted/SymbolLocatorScripted.h create mode 100644 lldb/test/API/functionalities/scripted_symbol_locator/Makefile create mode 100644 lldb/test/API/functionalities/scripted_symbol_locator/TestScriptedSymbolLocator.py create mode 100644 lldb/test/API/functionalities/scripted_symbol_locator/main.c create mode 100644 lldb/test/API/functionalities/scripted_symbol_locator/source_locator.py diff --git a/lldb/bindings/python/CMakeLists.txt b/lldb/bindings/python/CMakeLists.txt index 2ebcf5a8e7aca..de6204ab6cefa 100644 --- a/lldb/bindings/python/CMakeLists.txt +++ b/lldb/bindings/python/CMakeLists.txt @@ -111,6 +111,7 @@ function(finish_swig_python swig_target lldb_python_bindings_dir lldb_python_tar "plugins" FILES "${LLDB_SOURCE_DIR}/examples/python/templates/parsed_cmd.py" + "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_symbol_locator.py" "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_frame_provider.py" "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_process.py" "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_platform.py" diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig index bf59569920470..2df86e0fc69c1 100644 --- a/lldb/bindings/python/python-wrapper.swig +++ b/lldb/bindings/python/python-wrapper.swig @@ -556,6 +556,19 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBValueList(PyObject * return sb_ptr; } +void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFileSpec(PyObject * + data) { + lldb::SBFileSpec *sb_ptr = NULL; + + int valid_cast = SWIG_ConvertPtr(data, (void **)&sb_ptr, + SWIGTYPE_p_lldb__SBFileSpec, 0); + + if (valid_cast == -1) + return NULL; + + return sb_ptr; +} + void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBMemoryRegionInfo(PyObject * data) { diff --git a/lldb/docs/use/tutorials/scripted-symbol-locator.md b/lldb/docs/use/tutorials/scripted-symbol-locator.md new file mode 100644 index 0000000000000..ace401db7d49c --- /dev/null +++ b/lldb/docs/use/tutorials/scripted-symbol-locator.md @@ -0,0 +1,163 @@ +# Scripted Symbol Locator Tutorial + +The **Scripted Symbol Locator** lets you write a Python class that tells LLDB +where to find executables, symbol files, and source files for your debug +targets. This is useful when your build artifacts live in a custom location, +such as a symbol server or a local build-ID-indexed cache. + +## Quick Start + +1. **Write a locator class.** Create a Python file (e.g., `my_locator.py`) + with a class that implements the methods you need: + + ```python + import os + import lldb + + class MyLocator: + def __init__(self, exe_ctx, args): + self.cache_dir = None + if args.IsValid(): + d = args.GetValueForKey("cache_dir") + if d and d.IsValid(): + self.cache_dir = d.GetStringValue(4096) + + def locate_source_file(self, module, original_source_file): + """Return the resolved path, or None to fall through.""" + if not self.cache_dir: + return None + uuid = module.GetUUIDString() + basename = os.path.basename(original_source_file) + candidate = os.path.join(self.cache_dir, uuid, "src", basename) + if os.path.exists(candidate): + return candidate + return None + ``` + +2. **Import the script and register the locator on a target:** + + ``` + (lldb) command script import /path/to/my_locator.py + (lldb) target symbols scripted register \ + -C my_locator.MyLocator \ + -k cache_dir -v /path/to/cache + ``` + +3. **Debug normally.** When LLDB resolves source files for that target, + your `locate_source_file` method will be called automatically. + +## Available Methods + +Your locator class can implement any combination of these methods. All are +optional except `__init__` and `locate_source_file` (which is the abstract +method that must be present). + +| Method | Called When | +|--------|------------| +| `locate_source_file(module, path)` | LLDB resolves a source file path in debug info | +| `locate_executable_object_file(module_spec)` | LLDB needs the binary for a module | +| `locate_executable_symbol_file(module_spec, search_paths)` | LLDB needs separate debug symbols | +| `download_object_and_symbol_file(module_spec, force, copy)` | Last-resort download from a remote source | + +### Method Signatures + +```python +def __init__(self, exe_ctx: lldb.SBExecutionContext, + args: lldb.SBStructuredData) -> None: + ... + +def locate_source_file(self, module: lldb.SBModule, + original_source_file: str) -> Optional[str]: + ... + +def locate_executable_object_file( + self, module_spec: lldb.SBModuleSpec) -> Optional[lldb.SBFileSpec]: + ... + +def locate_executable_symbol_file( + self, module_spec: lldb.SBModuleSpec, + default_search_paths: list) -> Optional[lldb.SBFileSpec]: + ... + +def download_object_and_symbol_file( + self, module_spec: lldb.SBModuleSpec, + force_lookup: bool, copy_executable: bool) -> bool: + ... +``` + +## Per-Target Registration + +The scripted symbol locator is registered **per target**. Different targets +can use different locator classes or different arguments. + +``` +(lldb) target select 0 +(lldb) target symbols scripted register -C my_locator.MyLocator \ + -k cache_dir -v /cache/project-a + +(lldb) target select 1 +(lldb) target symbols scripted register -C my_locator.MyLocator \ + -k cache_dir -v /cache/project-b +``` + +### Commands + +| Command | Description | +|---------|-------------| +| `target symbols scripted register -C <class> [-k <key> -v <value> ...]` | Register a locator | +| `target symbols scripted clear` | Remove the locator from the current target | +| `target symbols scripted info` | Show the current locator class | + +### SB API + +You can also register locators programmatically: + +```python +import lldb + +error = target.RegisterScriptedSymbolLocator( + "my_locator.MyLocator", args) +# args is an SBStructuredData dictionary + +target.ClearScriptedSymbolLocator() +``` + +## Caching + +Source file resolutions are cached per `(module UUID, source file path)` pair +within each target. The cache is cleared when: + +- A new locator is registered (via `register`) +- The locator is cleared (via `clear`) + +This means your `locate_source_file` method is called at most once per +unique `(UUID, path)` combination. + +## Base Class Template + +LLDB ships a base class template at `lldb.plugins.scripted_symbol_locator`. +You can import and subclass it: + +```python +from lldb.plugins.scripted_symbol_locator import ScriptedSymbolLocator + +class MyLocator(ScriptedSymbolLocator): + def __init__(self, exe_ctx, args): + super().__init__(exe_ctx, args) + + def locate_source_file(self, module, original_source_file): + # Your implementation here + return None +``` + +The base class handles extracting the target and args from the execution +context. See `lldb/examples/python/templates/scripted_symbol_locator.py` +for the full template with docstrings. + +## Listing Scripting Extensions + +To see all registered scripting extensions (including symbol locators): + +``` +(lldb) scripting extension list +``` diff --git a/lldb/examples/python/templates/scripted_symbol_locator.py b/lldb/examples/python/templates/scripted_symbol_locator.py new file mode 100644 index 0000000000000..323083ef357af --- /dev/null +++ b/lldb/examples/python/templates/scripted_symbol_locator.py @@ -0,0 +1,220 @@ +from abc import ABCMeta, abstractmethod +import os + +import lldb + + +class ScriptedSymbolLocator(metaclass=ABCMeta): + """ + The base class for a scripted symbol locator. + + Most of the base class methods are optional and return ``None`` to fall + through to LLDB's default resolution. Override only the methods you need. + + Configuration:: + + (lldb) command script import /path/to/my_locator.py + (lldb) target symbols scripted register -C my_locator.MyLocator \\ + [-k key -v value ...] + """ + + @abstractmethod + def __init__(self, exe_ctx, args): + """Construct a scripted symbol locator. + + Args: + exe_ctx (lldb.SBExecutionContext): The execution context for + the scripted symbol locator. + args (lldb.SBStructuredData): A Dictionary holding arbitrary + key/value pairs used by the scripted symbol locator. + """ + target = None + self.target = None + self.args = None + if isinstance(exe_ctx, lldb.SBExecutionContext): + target = exe_ctx.target + if isinstance(target, lldb.SBTarget) and target.IsValid(): + self.target = target + self.dbg = target.GetDebugger() + if isinstance(args, lldb.SBStructuredData) and args.IsValid(): + self.args = args + + def locate_source_file(self, module, original_source_file): + """Locate the source file for a given module. + + Called when LLDB resolves source file paths during stack frame + display, breakpoint resolution, or source listing. This is the + primary method for implementing source file remapping based on + build IDs. + + The module is a fully loaded ``SBModule`` (not an ``SBModuleSpec``), + so you can access its UUID, file path, platform file path, + symbol file path, sections, and symbols. + + Results are cached per (module UUID, source file) pair, so this + method is called at most once per unique combination. + + Args: + module (lldb.SBModule): The loaded module containing debug + info. Use ``module.GetUUIDString()`` to get the build ID + for looking up the correct source revision. + original_source_file (str): The original source file path + as recorded in the debug info. + + Returns: + str: The resolved file path, or None to fall through to + LLDB's default source resolution. + """ + return None + + def locate_executable_object_file(self, module_spec): + """Locate the executable (object) file for a given module. + + Called when LLDB needs to find the binary for a module during + target creation or module loading. For example, when loading a + minidump, LLDB calls this for each shared library referenced + in the dump. + + Args: + module_spec (lldb.SBModuleSpec): The module specification + containing the UUID, file path, architecture, and other + search criteria. + + Returns: + lldb.SBFileSpec: The located executable, or None to fall + through to LLDB's default search. + """ + return None + + def locate_executable_symbol_file(self, module_spec, default_search_paths): + """Locate the symbol file for a given module. + + Called when LLDB needs to find separate debug symbols (e.g., + ``.dSYM`` bundles on macOS, ``.debug`` files on Linux, ``.dwp`` + files for split DWARF) for a module. + + Args: + module_spec (lldb.SBModuleSpec): The module specification + containing the UUID and file path to search for. + default_search_paths (list): A list of default search paths + to check. + + Returns: + lldb.SBFileSpec: The located symbol file, or None to fall + through to LLDB's default search. + """ + return None + + def download_object_and_symbol_file( + self, module_spec, force_lookup, copy_executable + ): + """Download both the object file and symbol file for a module. + + Called when LLDB needs to download a binary and its debug symbols + from a remote source (e.g., a symbol server, build artifact + store, or cloud storage). This is the last method called in the + resolution chain, typically as a fallback when local lookups + fail. + + Args: + module_spec (lldb.SBModuleSpec): The module specification + containing the UUID and file path to download. + force_lookup (bool): If True, skip any cached results and + force a fresh lookup. + copy_executable (bool): If True, copy the executable to a + local path. + + Returns: + bool: True if the download succeeded, False otherwise. + """ + return False + + +class LocalCacheSymbolLocator(ScriptedSymbolLocator): + """Example locator that resolves files from a local cache directory. + + Demonstrates how to subclass ``ScriptedSymbolLocator`` to implement + custom symbol and source file resolution. This locator looks up files + in a local directory structure organized by build ID (UUID):: + + <cache_dir>/ + <uuid>/ + <binary_name> + <binary_name>.debug + src/ + main.cpp + ... + + Usage:: + + (lldb) command script import scripted_symbol_locator + (lldb) target symbols scripted register \\ + -C scripted_symbol_locator.LocalCacheSymbolLocator \\ + -k cache_dir -v "/path/to/cache" + (lldb) target create --core /path/to/minidump.dmp + (lldb) bt + + The locator searches for: + - Executables: ``<cache_dir>/<uuid>/<filename>`` + - Symbol files: ``<cache_dir>/<uuid>/<filename>.debug`` + - Source files: ``<cache_dir>/<uuid>/src/<basename>`` + """ + + cache_dir = None + + def __init__(self, exe_ctx, args): + super().__init__(exe_ctx, args) + + # Allow cache_dir to be set via structured data args. + if self.args: + cache_dir_val = self.args.GetValueForKey("cache_dir") + if cache_dir_val and cache_dir_val.IsValid(): + val = cache_dir_val.GetStringValue(256) + if val: + LocalCacheSymbolLocator.cache_dir = val + + def _get_cache_path(self, uuid_str, *components): + """Build a path under the cache directory for a given UUID. + + Args: + uuid_str (str): The module's UUID string. + *components: Additional path components (e.g., filename). + + Returns: + str: The full path, or None if cache_dir is not set or the + UUID is empty. + """ + if not self.cache_dir or not uuid_str: + return None + return os.path.join(self.cache_dir, uuid_str, *components) + + def locate_source_file(self, module, original_source_file): + """Look up source files under ``<cache_dir>/<uuid>/src/``.""" + uuid_str = module.GetUUIDString() + basename = os.path.basename(original_source_file) + path = self._get_cache_path(uuid_str, "src", basename) + if path and os.path.exists(path): + return path + return None + + def locate_executable_object_file(self, module_spec): + """Look up executables under ``<cache_dir>/<uuid>/``.""" + uuid_str = module_spec.GetUUIDString() + filename = os.path.basename(module_spec.GetFileSpec().GetFilename() or "") + if not filename: + return None + path = self._get_cache_path(uuid_str, filename) + if path and os.path.exists(path): + return lldb.SBFileSpec(path, True) + return None + + def locate_executable_symbol_file(self, module_spec, default_search_paths): + """Look up debug symbol files under ``<cache_dir>/<uuid>/``.""" + uuid_str = module_spec.GetUUIDString() + filename = os.path.basename(module_spec.GetFileSpec().GetFilename() or "") + if not filename: + return None + debug_path = self._get_cache_path(uuid_str, filename + ".debug") + if debug_path and os.path.exists(debug_path): + return lldb.SBFileSpec(debug_path, True) + return None diff --git a/lldb/include/lldb/API/SBFileSpec.h b/lldb/include/lldb/API/SBFileSpec.h index 36641843aabeb..4b0b640dd4dbc 100644 --- a/lldb/include/lldb/API/SBFileSpec.h +++ b/lldb/include/lldb/API/SBFileSpec.h @@ -11,6 +11,10 @@ #include "lldb/API/SBDefines.h" +namespace lldb_private { +class ScriptInterpreter; +} + namespace lldb { class LLDB_API SBFileSpec { @@ -79,6 +83,7 @@ class LLDB_API SBFileSpec { friend class SBThread; friend class SBTrace; friend class SBSaveCoreOptions; + friend class lldb_private::ScriptInterpreter; SBFileSpec(const lldb_private::FileSpec &fspec); diff --git a/lldb/include/lldb/API/SBTarget.h b/lldb/include/lldb/API/SBTarget.h index dd2cf59b831da..13736daab901c 100644 --- a/lldb/include/lldb/API/SBTarget.h +++ b/lldb/include/lldb/API/SBTarget.h @@ -1003,6 +1003,21 @@ class LLDB_API SBTarget { lldb::SBTrace CreateTrace(SBError &error); lldb::SBMutex GetAPIMutex() const; + /// Register a scripted symbol locator for this target. + /// + /// \param[in] class_name + /// The Python class implementing the symbol locator. + /// + /// \param[in] args + /// Optional structured data arguments passed to the locator. + /// + /// \return + /// An SBError indicating success or failure. + lldb::SBError RegisterScriptedSymbolLocator(const char *class_name, + lldb::SBStructuredData &args); + + /// Clear the scripted symbol locator for this target. + void ClearScriptedSymbolLocator(); /// Register a scripted frame provider for this target. /// If a scripted frame provider with the same name and same argument diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h index 4d116f52460ff..c245546f997ef 100644 --- a/lldb/include/lldb/Core/PluginManager.h +++ b/lldb/include/lldb/Core/PluginManager.h @@ -436,8 +436,10 @@ class PluginManager { GetSymbolFileCreateCallbackAtIndex(uint32_t idx); // SymbolVendor - static bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description, - SymbolVendorCreateInstance create_callback); + static bool + RegisterPlugin(llvm::StringRef name, llvm::StringRef description, + SymbolLocatorLocateSourceFile locate_source_file = nullptr, + SymbolVendorCreateInstance create_callback); static bool UnregisterPlugin(SymbolVendorCreateInstance create_callback); @@ -461,6 +463,8 @@ class PluginManager { static SymbolLocatorCreateInstance GetSymbolLocatorCreateCallbackAtIndex(uint32_t idx); + static FileSpec LocateSourceFile(const lldb::ModuleSP &module_sp, + const FileSpec &original_source_file); static ModuleSpec LocateExecutableObjectFile(const ModuleSpec &module_spec, StatisticsMap &map); diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedSymbolLocatorInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedSymbolLocatorInterface.h new file mode 100644 index 0000000000000..52e1c9855df56 --- /dev/null +++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedSymbolLocatorInterface.h @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// 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_INTERPRETER_INTERFACES_SCRIPTEDSYMBOLLOCATORINTERFACE_H +#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDSYMBOLLOCATORINTERFACE_H + +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/StructuredDataImpl.h" +#include "lldb/Interpreter/Interfaces/ScriptedInterface.h" +#include "lldb/Utility/Status.h" + +#include "lldb/lldb-private.h" + +#include <optional> +#include <string> + +namespace lldb_private { +class ScriptedSymbolLocatorInterface : virtual public ScriptedInterface { +public: + virtual llvm::Expected<StructuredData::GenericSP> + CreatePluginObject(llvm::StringRef class_name, ExecutionContext &exe_ctx, + StructuredData::DictionarySP args_sp, + StructuredData::Generic *script_obj = nullptr) = 0; + + virtual std::optional<ModuleSpec> + LocateExecutableObjectFile(const ModuleSpec &module_spec, Status &error) { + return {}; + } + + virtual std::optional<FileSpec> + LocateExecutableSymbolFile(const ModuleSpec &module_spec, + const FileSpecList &default_search_paths, + Status &error) { + return {}; + } + + virtual bool DownloadObjectAndSymbolFile(ModuleSpec &module_spec, + Status &error, bool force_lookup, + bool copy_executable) { + return false; + } + + virtual std::optional<FileSpec> + LocateSourceFile(const lldb::ModuleSP &module_sp, + const FileSpec &original_source_file, Status &error) { + return {}; + } +}; +} // namespace lldb_private + +#endif // LLDB_INTERPRETER_INTERFACES_SCRIPTEDSYMBOLLOCATORINTERFACE_H diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h index 557d73a415452..b6af81f5c2ebc 100644 --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -16,6 +16,7 @@ #include "lldb/API/SBError.h" #include "lldb/API/SBEvent.h" #include "lldb/API/SBExecutionContext.h" +#include "lldb/API/SBFileSpec.h" #include "lldb/API/SBFrameList.h" #include "lldb/API/SBLaunchInfo.h" #include "lldb/API/SBMemoryRegionInfo.h" @@ -33,6 +34,7 @@ #include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h" #include "lldb/Interpreter/Interfaces/ScriptedPlatformInterface.h" #include "lldb/Interpreter/Interfaces/ScriptedProcessInterface.h" +#include "lldb/Interpreter/Interfaces/ScriptedSymbolLocatorInterface.h" #include "lldb/Interpreter/Interfaces/ScriptedThreadInterface.h" #include "lldb/Interpreter/ScriptObject.h" #include "lldb/Symbol/SymbolContext.h" @@ -537,6 +539,11 @@ class ScriptInterpreter : public PluginInterface { } virtual lldb::ScriptedFrameInterfaceSP CreateScriptedFrameInterface() { + virtual lldb::ScriptedSymbolLocatorInterfaceSP + CreateScriptedSymbolLocatorInterface() { + return {}; + } + return {}; } @@ -596,6 +603,7 @@ class ScriptInterpreter : public PluginInterface { lldb::ProcessAttachInfoSP GetOpaqueTypeFromSBAttachInfo(const lldb::SBAttachInfo &attach_info) const; + FileSpec GetOpaqueTypeFromSBFileSpec(const lldb::SBFileSpec &file_spec) const; lldb::ProcessLaunchInfoSP GetOpaqueTypeFromSBLaunchInfo(const lldb::SBLaunchInfo &launch_info) const; diff --git a/lldb/include/lldb/Symbol/LineEntry.h b/lldb/include/lldb/Symbol/LineEntry.h index adf2e989e3e34..e023eda6d89a4 100644 --- a/lldb/include/lldb/Symbol/LineEntry.h +++ b/lldb/include/lldb/Symbol/LineEntry.h @@ -128,7 +128,7 @@ struct LineEntry { /// /// \param[in] target_sp /// Shared pointer to the target this LineEntry belongs to. - void ApplyFileMappings(lldb::TargetSP target_sp); + void ApplyFileMappings(lldb::TargetSP target_sp, const Address &address); /// Helper to access the file. const FileSpec &GetFile() const { return file_sp->GetSpecOnly(); } diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h index 4f5b022765f9e..6dcf44f9ddeec 100644 --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -13,6 +13,7 @@ #include <map> #include <memory> #include <string> +#include <unordered_map> #include <vector> #include "lldb/Breakpoint/BreakpointList.h" @@ -1578,8 +1579,27 @@ class Target : public std::enable_shared_from_this<Target>, class StopHookScripted : public StopHook { public: ~StopHookScripted() override = default; - StopHookResult HandleStop(ExecutionContext &exc_ctx, - lldb::StreamSP output) override; + StopHookResult HandleStop( + ExecutionContext &exc_ctx, + // Scripted symbol locator per-target registration. + Status RegisterScriptedSymbolLocator( + llvm::StringRef class_name, StructuredData::DictionarySP args_sp); + void ClearScriptedSymbolLocator(); + lldb::ScriptedSymbolLocatorInterfaceSP + GetScriptedSymbolLocatorInterface(); + llvm::StringRef GetScriptedSymbolLocatorClassName() + const { return m_scripted_symbol_locator_class_name; } + + /// Look up a previously cached source file resolution result. + /// Returns true if a cached entry exists (even if the result is + /// nullopt). + bool LookupScriptedSourceFileCache(const std::string &key, + std::optional<FileSpec> &result) + const; + void InsertScriptedSourceFileCache( + const std::string &key, const std::optional<FileSpec> &result); + + lldb::StreamSP output) override; Status SetScriptCallback(std::string class_name, StructuredData::ObjectSP extra_args_sp); @@ -1706,6 +1726,13 @@ class Target : public std::enable_shared_from_this<Target>, void SaveScriptedLaunchInfo(lldb_private::ProcessInfo &process_info); /// Add a signal for the target. This will get copied over to the process + // Per-target scripted symbol locator. + std::string m_scripted_symbol_locator_class_name; + StructuredData::DictionarySP m_scripted_symbol_locator_args_sp; + lldb::ScriptedSymbolLocatorInterfaceSP m_scripted_symbol_locator_interface_sp; + std::unordered_map<std::string, std::optional<FileSpec>> + m_scripted_source_file_cache; + /// if the signal exists on that target. Only the values with Yes and No are /// set, Calculate values will be ignored. protected: diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h index ccfe5efa19e1d..dcee220dd0c78 100644 --- a/lldb/include/lldb/lldb-forward.h +++ b/lldb/include/lldb/lldb-forward.h @@ -195,6 +195,7 @@ class ScriptedPlatformInterface; class ScriptedProcessInterface; class ScriptedStopHookInterface; class ScriptedThreadInterface; +class ScriptedSymbolLocatorInterface; class ScriptedThreadPlanInterface; class ScriptedSyntheticChildren; class SearchFilter; @@ -423,8 +424,10 @@ typedef std::unique_ptr<lldb_private::ScriptedPlatformInterface> ScriptedPlatformInterfaceUP; typedef std::unique_ptr<lldb_private::ScriptedProcessInterface> ScriptedProcessInterfaceUP; -typedef std::shared_ptr<lldb_private::ScriptedStopHookInterface> - ScriptedStopHookInterfaceSP; +typedef std::shared_ptr<lldb_private::ScriptedStopHookInterface> typedef std:: + shared_ptr<lldb_private::ScriptedSymbolLocatorInterface> + ScriptedSymbolLocatorInterfaceSP; +ScriptedStopHookInterfaceSP; typedef std::shared_ptr<lldb_private::ScriptedThreadInterface> ScriptedThreadInterfaceSP; typedef std::shared_ptr<lldb_private::ScriptedThreadPlanInterface> diff --git a/lldb/include/lldb/lldb-private-interfaces.h b/lldb/include/lldb/lldb-private-interfaces.h index a87e01769c555..c00f6b2302ab1 100644 --- a/lldb/include/lldb/lldb-private-interfaces.h +++ b/lldb/include/lldb/lldb-private-interfaces.h @@ -101,6 +101,8 @@ typedef SymbolVendor *(*SymbolVendorCreateInstance)( lldb_private::Stream *feedback_strm); // Module can be NULL for default system symbol vendor typedef SymbolLocator *(*SymbolLocatorCreateInstance)(); +typedef std::optional<FileSpec> (*SymbolLocatorLocateSourceFile)( + const lldb::ModuleSP &module_sp, const FileSpec &original_source_file); typedef std::optional<ModuleSpec> (*SymbolLocatorLocateExecutableObjectFile)( const ModuleSpec &module_spec); typedef std::optional<FileSpec> (*SymbolLocatorFindSymbolFileInBundle)( diff --git a/lldb/source/API/SBTarget.cpp b/lldb/source/API/SBTarget.cpp index 99dfbb3fd9bce..44ba165e975d4 100644 --- a/lldb/source/API/SBTarget.cpp +++ b/lldb/source/API/SBTarget.cpp @@ -2407,6 +2407,36 @@ SBEnvironment SBTarget::GetEnvironment() { return SBEnvironment(); } +lldb::SBError +SBTarget::RegisterScriptedSymbolLocator(const char *class_name, + lldb::SBStructuredData &args) { + LLDB_INSTRUMENT_VA(this, class_name, args); + + lldb::SBError sb_error; + TargetSP target_sp = GetSP(); + if (!target_sp) { + sb_error.SetErrorString("invalid target"); + return sb_error; + } + + StructuredData::DictionarySP args_sp; + StructuredData::ObjectSP obj_sp = args.m_impl_up->GetObjectSP(); + if (obj_sp && obj_sp->GetType() == lldb::eStructuredDataTypeDictionary) + args_sp = std::static_pointer_cast<StructuredData::Dictionary>(obj_sp); + + Status error = target_sp->RegisterScriptedSymbolLocator(class_name, args_sp); + if (error.Fail()) + sb_error.SetErrorString(error.AsCString()); + return sb_error; +} + +void SBTarget::ClearScriptedSymbolLocator() { + LLDB_INSTRUMENT_VA(this); + + if (TargetSP target_sp = GetSP()) + target_sp->ClearScriptedSymbolLocator(); +} + lldb::SBTrace SBTarget::GetTrace() { LLDB_INSTRUMENT_VA(this); diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp index 59ccf390dea31..195963815261f 100644 --- a/lldb/source/Commands/CommandObjectTarget.cpp +++ b/lldb/source/Commands/CommandObjectTarget.cpp @@ -4849,6 +4849,143 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed, m_file_name.clear(); m_module_name.clear(); m_func_name_type_mask = eFunctionNameTypeAuto; +#pragma mark CommandObjectTargetSymbolsScriptedRegister + + class CommandObjectTargetSymbolsScriptedRegister + : public CommandObjectParsed { + public: + CommandObjectTargetSymbolsScriptedRegister( + CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target symbols scripted register", + "Register a scripted symbol locator for the current target.", + "target symbols scripted register -C <script-class> " + "[-k <key> -v <value> ...]"), + m_python_class_options( + "scripted symbol locator", true, 'C', 'k', 'v', + OptionGroupPythonClassWithDict::eScriptClass) { + m_all_options.Append(&m_python_class_options, + LLDB_OPT_SET_1 | LLDB_OPT_SET_2, LLDB_OPT_SET_1); + m_all_options.Finalize(); + } + + ~CommandObjectTargetSymbolsScriptedRegister() override = default; + + Options *GetOptions() override { return &m_all_options; } + + protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetTarget(); + + llvm::StringRef class_name = m_python_class_options.GetName(); + if (class_name.empty()) { + result.AppendError("must specify a script class with -C"); + return; + } + + StructuredData::DictionarySP args_sp; + StructuredData::ObjectSP extra = + m_python_class_options.GetStructuredData(); + if (extra && extra->GetType() == lldb::eStructuredDataTypeDictionary) + args_sp = + std::static_pointer_cast<StructuredData::Dictionary>(extra); + + Status error = + target.RegisterScriptedSymbolLocator(class_name, args_sp); + if (error.Fail()) { + result.AppendErrorWithFormat( + "failed to register scripted symbol locator: %s\n", + error.AsCString()); + return; + } + + result.AppendMessageWithFormat( + "Registered scripted symbol locator '%s' for target.\n", + class_name.str().c_str()); + result.SetStatus(eReturnStatusSuccessFinishResult); + } + + OptionGroupPythonClassWithDict m_python_class_options; + OptionGroupOptions m_all_options; + }; + +#pragma mark CommandObjectTargetSymbolsScriptedClear + + class CommandObjectTargetSymbolsScriptedClear + : public CommandObjectParsed { + public: + CommandObjectTargetSymbolsScriptedClear(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target symbols scripted clear", + "Clear the scripted symbol locator for the current target.", + "target symbols scripted clear") {} + + ~CommandObjectTargetSymbolsScriptedClear() override = default; + + protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetTarget(); + target.ClearScriptedSymbolLocator(); + result.AppendMessageWithFormat( + "Cleared scripted symbol locator for target.\n"); + result.SetStatus(eReturnStatusSuccessFinishResult); + } + }; + +#pragma mark CommandObjectTargetSymbolsScriptedInfo + + class CommandObjectTargetSymbolsScriptedInfo + : public CommandObjectParsed { + public: + CommandObjectTargetSymbolsScriptedInfo(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target symbols scripted info", + "Show the current scripted symbol locator for the target.", + "target symbols scripted info") {} + + ~CommandObjectTargetSymbolsScriptedInfo() override = default; + + protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetTarget(); + llvm::StringRef class_name = + target.GetScriptedSymbolLocatorClassName(); + if (class_name.empty()) { + result.AppendMessageWithFormat( + "No scripted symbol locator registered for this target.\n"); + } else { + result.AppendMessageWithFormat("Scripted symbol locator: %s\n", + class_name.str().c_str()); + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } + }; + +#pragma mark CommandObjectTargetSymbolsScripted + + class CommandObjectTargetSymbolsScripted : public CommandObjectMultiword { + public: + CommandObjectTargetSymbolsScripted(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "target symbols scripted", + "Commands for managing scripted symbol locators.", + "target symbols scripted <sub-command> ...") { + LoadSubCommand( + "register", + CommandObjectSP( + new CommandObjectTargetSymbolsScriptedRegister(interpreter))); + LoadSubCommand( + "clear", + CommandObjectSP( + new CommandObjectTargetSymbolsScriptedClear(interpreter))); + LoadSubCommand("info", CommandObjectSP( + new CommandObjectTargetSymbolsScriptedInfo( + interpreter))); + } + + ~CommandObjectTargetSymbolsScripted() override = default; + }; + m_thread_id = LLDB_INVALID_THREAD_ID; m_thread_index = UINT32_MAX; m_thread_name.clear(); @@ -4863,6 +5000,9 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed, m_auto_continue = false; m_at_initial_stop = true; } + LoadSubCommand( + "scripted", + CommandObjectSP(new CommandObjectTargetSymbolsScripted(interpreter))); std::string m_class_name; std::string m_function_name; diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp index 659190833c20d..fc17daf86a901 100644 --- a/lldb/source/Core/Module.cpp +++ b/lldb/source/Core/Module.cpp @@ -476,7 +476,7 @@ uint32_t Module::ResolveSymbolContextForAddress( symfile->ResolveSymbolContext(so_addr, resolve_scope, sc); if ((resolve_scope & eSymbolContextLineEntry) && sc.line_entry.IsValid()) - sc.line_entry.ApplyFileMappings(sc.target_sp); + sc.line_entry.ApplyFileMappings(sc.target_sp, so_addr); } // Resolve the symbol if requested, but don't re-look it up if we've diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp index 64130d700a006..96976a7cd4572 100644 --- a/lldb/source/Core/PluginManager.cpp +++ b/lldb/source/Core/PluginManager.cpp @@ -1421,10 +1421,11 @@ static SymbolFileInstances &GetSymbolFileInstances() { return g_instances; } -bool PluginManager::RegisterPlugin( - llvm::StringRef name, llvm::StringRef description, - SymbolFileCreateInstance create_callback, - DebuggerInitializeCallback debugger_init_callback) { +SymbolLocatorLocateSourceFile locate_source_file, + bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + SymbolFileCreateInstance create_callback, + DebuggerInitializeCallback debugger_init_callback) { return GetSymbolFileInstances().RegisterPlugin( name, description, create_callback, debugger_init_callback); } @@ -1433,7 +1434,7 @@ bool PluginManager::UnregisterPlugin(SymbolFileCreateInstance create_callback) { return GetSymbolFileInstances().UnregisterPlugin(create_callback); } -SymbolFileCreateInstance +SymbolFileCreateInstance SymbolLocatorLocateSourceFile locate_source_file; PluginManager::GetSymbolFileCreateCallbackAtIndex(uint32_t idx) { return GetSymbolFileInstances().GetCallbackAtIndex(idx); } @@ -1448,9 +1449,9 @@ static SymbolVendorInstances &GetSymbolVendorInstances() { return g_instances; } -bool PluginManager::RegisterPlugin(llvm::StringRef name, - llvm::StringRef description, - SymbolVendorCreateInstance create_callback) { +bool PluginManager::RegisterPlugin( + llvm::StringRef name, SymbolLocatorLocateSourceFile locate_source_file, + llvm::StringRef description, SymbolVendorCreateInstance create_callback) { return GetSymbolVendorInstances().RegisterPlugin(name, description, create_callback); } @@ -1482,7 +1483,8 @@ struct SymbolLocatorInstance locate_executable_object_file(locate_executable_object_file), locate_executable_symbol_file(locate_executable_symbol_file), download_object_symbol_file(download_object_symbol_file), - find_symbol_file_in_bundle(find_symbol_file_in_bundle) {} + find_symbol_file_in_bundle(find_symbol_file_in_bundle), + locate_source_file(locate_source_file) {} SymbolLocatorLocateExecutableObjectFile locate_executable_object_file; SymbolLocatorLocateExecutableSymbolFile locate_executable_symbol_file; @@ -1507,7 +1509,7 @@ bool PluginManager::RegisterPlugin( return GetSymbolLocatorInstances().RegisterPlugin( name, description, create_callback, locate_executable_object_file, locate_executable_symbol_file, download_object_symbol_file, - find_symbol_file_in_bundle, debugger_init_callback); + find_symbol_file_in_bundle, locate_source_file, debugger_init_callback); } bool PluginManager::UnregisterPlugin( @@ -1536,6 +1538,19 @@ PluginManager::LocateExecutableObjectFile(const ModuleSpec &module_spec, if (result) return *result; } + FileSpec PluginManager::LocateSourceFile( + const lldb::ModuleSP &module_sp, const FileSpec &original_source_file) { + auto instances = GetSymbolLocatorInstances().GetSnapshot(); + for (auto &instance : instances) { + if (instance.locate_source_file) { + std::optional<FileSpec> result = + instance.locate_source_file(module_sp, original_source_file); + if (result) + return *result; + } + } + return {}; + } } return {}; } diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp index 5e8478c2670bb..3c5b1ded46f36 100644 --- a/lldb/source/Interpreter/ScriptInterpreter.cpp +++ b/lldb/source/Interpreter/ScriptInterpreter.cpp @@ -150,6 +150,12 @@ ScriptInterpreter::GetOpaqueTypeFromSBMemoryRegionInfo( if (!mem_region.m_opaque_up) return std::nullopt; return *mem_region.m_opaque_up.get(); + FileSpec ScriptInterpreter::GetOpaqueTypeFromSBFileSpec( + const lldb::SBFileSpec &file_spec) const { + if (file_spec.m_opaque_up) + return *file_spec.m_opaque_up; + return {}; + } } lldb::ExecutionContextRefSP diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt index 50569cdefaafa..92eed54ebda06 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt @@ -25,6 +25,7 @@ add_lldb_library(lldbPluginScriptInterpreterPythonInterfaces PLUGIN ScriptedFramePythonInterface.cpp ScriptedFrameProviderPythonInterface.cpp ScriptedPlatformPythonInterface.cpp + ScriptedSymbolLocatorPythonInterface.cpp ScriptedProcessPythonInterface.cpp ScriptedPythonInterface.cpp ScriptedStopHookPythonInterface.cpp @@ -42,5 +43,3 @@ add_lldb_library(lldbPluginScriptInterpreterPythonInterfaces PLUGIN ${Python3_LIBRARIES} ${LLDB_LIBEDIT_LIBS} ) - - diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp index f6c707b2bd168..67af11ed0682f 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp @@ -31,6 +31,7 @@ void ScriptInterpreterPythonInterfaces::Initialize() { ScriptedStopHookPythonInterface::Initialize(); ScriptedBreakpointPythonInterface::Initialize(); ScriptedThreadPlanPythonInterface::Initialize(); + ScriptedSymbolLocatorPythonInterface::Initialize(); ScriptedFrameProviderPythonInterface::Initialize(); } @@ -40,6 +41,7 @@ void ScriptInterpreterPythonInterfaces::Terminate() { ScriptedProcessPythonInterface::Terminate(); ScriptedStopHookPythonInterface::Terminate(); ScriptedBreakpointPythonInterface::Terminate(); + ScriptedSymbolLocatorPythonInterface::Terminate(); ScriptedThreadPlanPythonInterface::Terminate(); ScriptedFrameProviderPythonInterface::Terminate(); } diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h index 721902ec1e253..52827d01b2495 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h @@ -22,6 +22,7 @@ #include "ScriptedPlatformPythonInterface.h" #include "ScriptedProcessPythonInterface.h" #include "ScriptedStopHookPythonInterface.h" +#include "ScriptedSymbolLocatorPythonInterface.h" #include "ScriptedThreadPlanPythonInterface.h" namespace lldb_private { diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp index f5fd8b2d2d802..eff1ed2b08417 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp @@ -18,6 +18,7 @@ #include "../ScriptInterpreterPythonImpl.h" #include "ScriptedPythonInterface.h" #include "lldb/Symbol/SymbolContext.h" +#include "lldb/Utility/FileSpec.h" #include "lldb/ValueObject/ValueObjectList.h" #include <optional> @@ -243,6 +244,18 @@ template <> lldb::DescriptionLevel ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DescriptionLevel>( python::PythonObject &p, Status &error) { + template <> + FileSpec ScriptedPythonInterface::ExtractValueFromPythonObject<FileSpec>( + python::PythonObject & p, Status & error) { + if (lldb::SBFileSpec *sb_file_spec = reinterpret_cast<lldb::SBFileSpec *>( + python::LLDBSWIGPython_CastPyObjectToSBFileSpec(p.get()))) + return m_interpreter.GetOpaqueTypeFromSBFileSpec(*sb_file_spec); + error = Status::FromErrorString( + "Couldn't cast lldb::SBFileSpec to lldb_private::FileSpec."); + + return {}; + } + lldb::DescriptionLevel ret_val = lldb::eDescriptionLevelBrief; llvm::Expected<unsigned long long> unsigned_or_err = p.AsUnsignedLongLong(); if (!unsigned_or_err) { diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h index 4aadee584b2e2..ce1724e0db253 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h @@ -17,9 +17,15 @@ #include <type_traits> #include <utility> +#include "lldb/Core/ModuleSpec.h" #include "lldb/Host/Config.h" #include "lldb/Interpreter/Interfaces/ScriptedInterface.h" #include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/FileSpecList.h" + +#include "lldb/API/SBFileSpec.h" +#include "lldb/API/SBModuleSpec.h" #include "../PythonDataObjects.h" #include "../SWIGPythonBridge.h" @@ -453,11 +459,16 @@ class ScriptedPythonInterface : virtual public ScriptedInterface { PythonCallable method = class_obj.GetAttributeValue(method_name).AsType<PythonCallable>(); if (!method.IsAllocated()) - return ErrorWithMessage<T>(caller_signature, - llvm::formatv("method {0}.{1} is not callable", - class_name, method_name) - .str(), - error); + return ErrorWithMessage<T>( + caller_signature, + llvm::formatv("method {0}.{1} is not callable", class_name, + method_name) + python::PythonObject Transform(lldb::ModuleSP arg) { + return python::SWIGBridge::ToSWIGWrapper(arg); + } + + .str(), + error); // Transform the arguments. std::tuple<Args...> original_args = std::forward_as_tuple(args...); @@ -480,6 +491,61 @@ class ScriptedPythonInterface : virtual public ScriptedInterface { caller_signature, "python static method could not be called", error); } + python::PythonObject Transform(const ModuleSpec &arg) { + // Build an SBModuleSpec using public API setters since the constructor + // from ModuleSpec is private. + lldb::SBModuleSpec sb_module_spec; + + const UUID &uuid = arg.GetUUID(); + if (uuid.IsValid()) + sb_module_spec.SetUUIDBytes(uuid.GetBytes().data(), + uuid.GetBytes().size()); + + const FileSpec &file = arg.GetFileSpec(); + if (file) + sb_module_spec.SetFileSpec( + lldb::SBFileSpec(file.GetPath().c_str(), false)); + + const FileSpec &platform_file = arg.GetPlatformFileSpec(); + if (platform_file) + sb_module_spec.SetPlatformFileSpec( + lldb::SBFileSpec(platform_file.GetPath().c_str(), false)); + + const FileSpec &symbol_file = arg.GetSymbolFileSpec(); + if (symbol_file) + sb_module_spec.SetSymbolFileSpec( + lldb::SBFileSpec(symbol_file.GetPath().c_str(), false)); + + const ArchSpec &arch = arg.GetArchitecture(); + if (arch.IsValid()) + sb_module_spec.SetTriple(arch.GetTriple().getTriple().c_str()); + + ConstString object_name = arg.GetObjectName(); + if (object_name) + sb_module_spec.SetObjectName(object_name.GetCString()); + + sb_module_spec.SetObjectOffset(arg.GetObjectOffset()); + sb_module_spec.SetObjectSize(arg.GetObjectSize()); + + return python::SWIGBridge::ToSWIGWrapper( + std::make_unique<lldb::SBModuleSpec>(sb_module_spec)); + } + + python::PythonObject Transform(const FileSpecList &arg) { + python::PythonList py_list(arg.GetSize()); + for (size_t i = 0; i < arg.GetSize(); i++) { + const FileSpec &fs = arg.GetFileSpecAtIndex(i); + py_list.SetItemAtIndex(i, python::SWIGBridge::ToSWIGWrapper( + std::make_unique<lldb::SBFileSpec>( + fs.GetPath().c_str(), false))); + } + return py_list; + } + + python::PythonObject Transform(const std::string &arg) { + return python::PythonString(arg); + } + PythonObject py_return = std::move(expected_return_object.get()); // Re-assign reference and pointer arguments if needed. @@ -491,6 +557,23 @@ class ScriptedPythonInterface : virtual public ScriptedInterface { // Extract value from Python object (handles unallocated case). return ExtractValueFromPythonObject<T>(py_return, error); + // Read-only types: Python doesn't modify these, so reverse transform is a + // no-op. + void ReverseTransform(ModuleSpec & original_arg, + python::PythonObject transformed_arg, + Status & error) {} + + void ReverseTransform(FileSpecList & original_arg, + python::PythonObject transformed_arg, + Status & error) {} + + void ReverseTransform(std::string & original_arg, + python::PythonObject transformed_arg, + Status & error) {} + + void ReverseTransform(lldb::ModuleSP & original_arg, + python::PythonObject transformed_arg, + Status & error) {} } protected: @@ -628,6 +711,10 @@ class ScriptedPythonInterface : virtual public ScriptedInterface { return python::SWIGBridge::ToSWIGWrapper(arg); } + template <> + FileSpec ScriptedPythonInterface::ExtractValueFromPythonObject<FileSpec>( + python::PythonObject &p, Status &error); + python::PythonObject Transform(lldb::ProcessLaunchInfoSP arg) { return python::SWIGBridge::ToSWIGWrapper(arg); } diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSymbolLocatorPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSymbolLocatorPythonInterface.cpp new file mode 100644 index 0000000000000..381d94725898b --- /dev/null +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSymbolLocatorPythonInterface.cpp @@ -0,0 +1,126 @@ +//===----------------------------------------------------------------------===// +// +// 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 "lldb/Core/PluginManager.h" +#include "lldb/Host/Config.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Utility/Log.h" +#include "lldb/lldb-enumerations.h" + +#if LLDB_ENABLE_PYTHON + +// clang-format off +// LLDB Python header must be included first +#include "../lldb-python.h" +// clang-format on + +#include "../SWIGPythonBridge.h" +#include "../ScriptInterpreterPythonImpl.h" +#include "ScriptedSymbolLocatorPythonInterface.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::python; + +ScriptedSymbolLocatorPythonInterface::ScriptedSymbolLocatorPythonInterface( + ScriptInterpreterPythonImpl &interpreter) + : ScriptedSymbolLocatorInterface(), ScriptedPythonInterface(interpreter) {} + +llvm::Expected<StructuredData::GenericSP> +ScriptedSymbolLocatorPythonInterface::CreatePluginObject( + const llvm::StringRef class_name, ExecutionContext &exe_ctx, + StructuredData::DictionarySP args_sp, StructuredData::Generic *script_obj) { + ExecutionContextRefSP exe_ctx_ref_sp = + std::make_shared<ExecutionContextRef>(exe_ctx); + StructuredDataImpl sd_impl(args_sp); + return ScriptedPythonInterface::CreatePluginObject(class_name, script_obj, + exe_ctx_ref_sp, sd_impl); +} + +std::optional<ModuleSpec> +ScriptedSymbolLocatorPythonInterface::LocateExecutableObjectFile( + const ModuleSpec &module_spec, Status &error) { + // Make a copy so Dispatch's ReverseTransform can operate on a mutable value. + ModuleSpec ms_copy(module_spec); + FileSpec file_spec = + Dispatch<FileSpec>("locate_executable_object_file", error, ms_copy); + + if (error.Fail() || !file_spec) + return {}; + + ModuleSpec result_spec(module_spec); + result_spec.GetFileSpec() = file_spec; + return result_spec; +} + +std::optional<FileSpec> +ScriptedSymbolLocatorPythonInterface::LocateExecutableSymbolFile( + const ModuleSpec &module_spec, const FileSpecList &default_search_paths, + Status &error) { + ModuleSpec ms_copy(module_spec); + FileSpecList fsl_copy(default_search_paths); + FileSpec file_spec = Dispatch<FileSpec>("locate_executable_symbol_file", + error, ms_copy, fsl_copy); + + if (error.Fail() || !file_spec) + return {}; + + return file_spec; +} + +bool ScriptedSymbolLocatorPythonInterface::DownloadObjectAndSymbolFile( + ModuleSpec &module_spec, Status &error, bool force_lookup, + bool copy_executable) { + StructuredData::ObjectSP obj = + Dispatch("download_object_and_symbol_file", error, module_spec, + force_lookup, copy_executable); + + if (!obj || error.Fail()) + return false; + + return obj->GetBooleanValue(); +} + +std::optional<FileSpec> ScriptedSymbolLocatorPythonInterface::LocateSourceFile( + const lldb::ModuleSP &module_sp, const FileSpec &original_source_file, + Status &error) { + std::string source_path = original_source_file.GetPath(); + lldb::ModuleSP module_copy(module_sp); + + StructuredData::ObjectSP obj = + Dispatch("locate_source_file", error, module_copy, source_path); + + if (!obj || error.Fail()) + return {}; + + llvm::StringRef value = obj->GetStringValue(); + if (value.empty()) + return {}; + + FileSpec file_spec; + file_spec.SetPath(value); + return file_spec; +} + +void ScriptedSymbolLocatorPythonInterface::Initialize() { + const std::vector<llvm::StringRef> ci_usages = { + "target symbols scripted register -C " + "<script-class> [-k <key> -v <value> ...]"}; + const std::vector<llvm::StringRef> api_usages = { + "SBTarget.RegisterScriptedSymbolLocator(class_name, args_dict)"}; + PluginManager::RegisterPlugin( + GetPluginNameStatic(), + llvm::StringRef("Scripted symbol locator Python interface"), + CreateInstance, eScriptLanguagePython, {ci_usages, api_usages}); +} + +void ScriptedSymbolLocatorPythonInterface::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +#endif // LLDB_ENABLE_PYTHON diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSymbolLocatorPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSymbolLocatorPythonInterface.h new file mode 100644 index 0000000000000..24d22f354b158 --- /dev/null +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSymbolLocatorPythonInterface.h @@ -0,0 +1,71 @@ +//===----------------------------------------------------------------------===// +// +// 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_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDSYMBOLLOCATORPYTHONINTERFACE_H +#define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDSYMBOLLOCATORPYTHONINTERFACE_H + +#include "lldb/Host/Config.h" +#include "lldb/Interpreter/Interfaces/ScriptedSymbolLocatorInterface.h" + +#if LLDB_ENABLE_PYTHON + +#include "ScriptedPythonInterface.h" + +#include <optional> + +namespace lldb_private { +class ScriptedSymbolLocatorPythonInterface + : public ScriptedSymbolLocatorInterface, + public ScriptedPythonInterface, + public PluginInterface { +public: + ScriptedSymbolLocatorPythonInterface( + ScriptInterpreterPythonImpl &interpreter); + + llvm::Expected<StructuredData::GenericSP> + CreatePluginObject(const llvm::StringRef class_name, + ExecutionContext &exe_ctx, + StructuredData::DictionarySP args_sp, + StructuredData::Generic *script_obj = nullptr) override; + + llvm::SmallVector<AbstractMethodRequirement> + GetAbstractMethodRequirements() const override { + return llvm::SmallVector<AbstractMethodRequirement>( + {{"locate_source_file", 2}}); + } + + std::optional<ModuleSpec> + LocateExecutableObjectFile(const ModuleSpec &module_spec, + Status &error) override; + + std::optional<FileSpec> + LocateExecutableSymbolFile(const ModuleSpec &module_spec, + const FileSpecList &default_search_paths, + Status &error) override; + + bool DownloadObjectAndSymbolFile(ModuleSpec &module_spec, Status &error, + bool force_lookup, + bool copy_executable) override; + + std::optional<FileSpec> LocateSourceFile(const lldb::ModuleSP &module_sp, + const FileSpec &original_source_file, + Status &error) override; + + static void Initialize(); + static void Terminate(); + + static llvm::StringRef GetPluginNameStatic() { + return "ScriptedSymbolLocatorPythonInterface"; + } + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } +}; +} // namespace lldb_private + +#endif // LLDB_ENABLE_PYTHON +#endif // LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDSYMBOLLOCATORPYTHONINTERFACE_H diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h index 7a64d8e91e62c..f0a6231bb0a09 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h @@ -268,6 +268,7 @@ void *LLDBSWIGPython_CastPyObjectToSBStream(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBThread(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBFrame(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBSymbolContext(PyObject *data); +void *LLDBSWIGPython_CastPyObjectToSBFileSpec(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBValue(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBValueList(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBMemoryRegionInfo(PyObject *data); diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp index 35a772c1454df..225b106be5c37 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -1526,6 +1526,10 @@ ScriptedFrameInterfaceSP ScriptInterpreterPythonImpl::CreateScriptedFrameInterface() { return std::make_shared<ScriptedFramePythonInterface>(*this); } +ScriptedSymbolLocatorInterfaceSP +ScriptInterpreterPythonImpl::CreateScriptedSymbolLocatorInterface() { + return std::make_shared<ScriptedSymbolLocatorPythonInterface>(*this); +} ScriptedFrameProviderInterfaceSP ScriptInterpreterPythonImpl::CreateScriptedFrameProviderInterface() { diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h index 1eac78e6360f2..8a01710036c21 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -101,6 +101,9 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython { lldb::ScriptedFrameInterfaceSP CreateScriptedFrameInterface() override; + lldb::ScriptedSymbolLocatorInterfaceSP + CreateScriptedSymbolLocatorInterface() override; + lldb::ScriptedFrameProviderInterfaceSP CreateScriptedFrameProviderInterface() override; diff --git a/lldb/source/Plugins/SymbolLocator/CMakeLists.txt b/lldb/source/Plugins/SymbolLocator/CMakeLists.txt index 3b466f71dca58..bf7f6046eed9d 100644 --- a/lldb/source/Plugins/SymbolLocator/CMakeLists.txt +++ b/lldb/source/Plugins/SymbolLocator/CMakeLists.txt @@ -7,6 +7,7 @@ set_property(DIRECTORY PROPERTY LLDB_PLUGIN_KIND SymbolLocator) # provider. add_subdirectory(Debuginfod) add_subdirectory(Default) +add_subdirectory(Scripted) if (CMAKE_SYSTEM_NAME MATCHES "Darwin") add_subdirectory(DebugSymbols) endif() diff --git a/lldb/source/Plugins/SymbolLocator/Debuginfod/SymbolLocatorDebuginfod.cpp b/lldb/source/Plugins/SymbolLocator/Debuginfod/SymbolLocatorDebuginfod.cpp index a09bb356e3a8c..bdef57f0671e1 100644 --- a/lldb/source/Plugins/SymbolLocator/Debuginfod/SymbolLocatorDebuginfod.cpp +++ b/lldb/source/Plugins/SymbolLocator/Debuginfod/SymbolLocatorDebuginfod.cpp @@ -111,7 +111,7 @@ void SymbolLocatorDebuginfod::Initialize() { PluginManager::RegisterPlugin( GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance, LocateExecutableObjectFile, LocateExecutableSymbolFile, nullptr, - nullptr, SymbolLocatorDebuginfod::DebuggerInitialize); + nullptr, nullptr, SymbolLocatorDebuginfod::DebuggerInitialize); llvm::HTTPClient::initialize(); }); } diff --git a/lldb/source/Plugins/SymbolLocator/Scripted/CMakeLists.txt b/lldb/source/Plugins/SymbolLocator/Scripted/CMakeLists.txt new file mode 100644 index 0000000000000..89612d5e1625b --- /dev/null +++ b/lldb/source/Plugins/SymbolLocator/Scripted/CMakeLists.txt @@ -0,0 +1,13 @@ +set_property(DIRECTORY PROPERTY LLDB_PLUGIN_KIND SymbolLocator) + +add_lldb_library(lldbPluginSymbolLocatorScripted PLUGIN + SymbolLocatorScripted.cpp + + LINK_LIBS + lldbCore + lldbHost + lldbInterpreter + lldbSymbol + lldbTarget + lldbUtility + ) diff --git a/lldb/source/Plugins/SymbolLocator/Scripted/SymbolLocatorScripted.cpp b/lldb/source/Plugins/SymbolLocator/Scripted/SymbolLocatorScripted.cpp new file mode 100644 index 0000000000000..c3784ccbf739a --- /dev/null +++ b/lldb/source/Plugins/SymbolLocator/Scripted/SymbolLocatorScripted.cpp @@ -0,0 +1,202 @@ +//===----------------------------------------------------------------------===// +// +// 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 "SymbolLocatorScripted.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Interpreter/Interfaces/ScriptedSymbolLocatorInterface.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" + +using namespace lldb; +using namespace lldb_private; + +LLDB_PLUGIN_DEFINE(SymbolLocatorScripted) + +SymbolLocatorScripted::SymbolLocatorScripted() : SymbolLocator() {} + +void SymbolLocatorScripted::Initialize() { + PluginManager::RegisterPlugin( + GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance, + LocateExecutableObjectFile, LocateExecutableSymbolFile, + DownloadObjectAndSymbolFile, nullptr, LocateSourceFile); +} + +void SymbolLocatorScripted::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +llvm::StringRef SymbolLocatorScripted::GetPluginDescriptionStatic() { + return "Scripted symbol locator plug-in."; +} + +SymbolLocator *SymbolLocatorScripted::CreateInstance() { + return new SymbolLocatorScripted(); +} + +/// Iterate all debuggers and their targets, calling \p callback for each +/// target that has a scripted symbol locator registered. The callback +/// receives the target and its interface. If the callback returns true, +/// iteration stops early. +template <typename Callback> +static void ForEachScriptedTarget(Callback &&callback) { + for (size_t di = 0; di < Debugger::GetNumDebuggers(); di++) { + DebuggerSP debugger_sp = Debugger::GetDebuggerAtIndex(di); + if (!debugger_sp) + continue; + TargetList &target_list = debugger_sp->GetTargetList(); + for (size_t ti = 0; ti < target_list.GetNumTargets(); ti++) { + TargetSP target_sp = target_list.GetTargetAtIndex(ti); + if (!target_sp) + continue; + auto interface_sp = target_sp->GetScriptedSymbolLocatorInterface(); + if (!interface_sp) + continue; + if (callback(*target_sp, interface_sp)) + return; + } + } +} + +std::optional<ModuleSpec> SymbolLocatorScripted::LocateExecutableObjectFile( + const ModuleSpec &module_spec) { + std::optional<ModuleSpec> result; + ForEachScriptedTarget( + [&](Target &target, + ScriptedSymbolLocatorInterfaceSP &interface_sp) -> bool { + Status error; + auto located = + interface_sp->LocateExecutableObjectFile(module_spec, error); + if (!error.Success()) { + Log *log = GetLog(LLDBLog::Symbols); + LLDB_LOG(log, + "SymbolLocatorScripted: locate_executable_object_file " + "failed: {0}", + error); + } + if (located) { + result = located; + return true; // Stop iterating. + } + return false; + }); + return result; +} + +std::optional<FileSpec> SymbolLocatorScripted::LocateExecutableSymbolFile( + const ModuleSpec &module_spec, const FileSpecList &default_search_paths) { + std::optional<FileSpec> result; + ForEachScriptedTarget( + [&](Target &target, + ScriptedSymbolLocatorInterfaceSP &interface_sp) -> bool { + Status error; + auto located = interface_sp->LocateExecutableSymbolFile( + module_spec, default_search_paths, error); + if (!error.Success()) { + Log *log = GetLog(LLDBLog::Symbols); + LLDB_LOG(log, + "SymbolLocatorScripted: locate_executable_symbol_file " + "failed: {0}", + error); + } + if (located) { + result = located; + return true; + } + return false; + }); + return result; +} + +bool SymbolLocatorScripted::DownloadObjectAndSymbolFile(ModuleSpec &module_spec, + Status &error, + bool force_lookup, + bool copy_executable) { + bool result = false; + ForEachScriptedTarget( + [&](Target &target, + ScriptedSymbolLocatorInterfaceSP &interface_sp) -> bool { + bool success = interface_sp->DownloadObjectAndSymbolFile( + module_spec, error, force_lookup, copy_executable); + if (success) { + result = true; + return true; + } + return false; + }); + return result; +} + +std::optional<FileSpec> +SymbolLocatorScripted::LocateSourceFile(const lldb::ModuleSP &module_sp, + const FileSpec &original_source_file) { + if (!module_sp) + return {}; + + // Find the target that owns this module. + Target *owning_target = nullptr; + for (size_t di = 0; di < Debugger::GetNumDebuggers(); di++) { + DebuggerSP debugger_sp = Debugger::GetDebuggerAtIndex(di); + if (!debugger_sp) + continue; + TargetList &target_list = debugger_sp->GetTargetList(); + for (size_t ti = 0; ti < target_list.GetNumTargets(); ti++) { + TargetSP target_sp = target_list.GetTargetAtIndex(ti); + if (!target_sp) + continue; + ModuleSP found_module = + target_sp->GetImages().FindModule(module_sp.get()); + if (found_module) { + owning_target = target_sp.get(); + break; + } + } + if (owning_target) + break; + } + + if (!owning_target) + return {}; + + auto interface_sp = owning_target->GetScriptedSymbolLocatorInterface(); + if (!interface_sp) + return {}; + + // Cache resolved source files to avoid repeated Python calls for the same + // (module, source_file) pair. + std::string cache_key = + module_sp->GetUUID().GetAsString() + ":" + original_source_file.GetPath(); + + std::optional<FileSpec> cached; + if (owning_target->LookupScriptedSourceFileCache(cache_key, cached)) + return cached; + + Status error; + auto located = + interface_sp->LocateSourceFile(module_sp, original_source_file, error); + + if (!error.Success()) { + Log *log = GetLog(LLDBLog::Symbols); + LLDB_LOG(log, "SymbolLocatorScripted: locate_source_file failed: {0}", + error); + } + + owning_target->InsertScriptedSourceFileCache(cache_key, located); + + if (located) { + Log *log = GetLog(LLDBLog::Symbols); + LLDB_LOGF(log, + "SymbolLocatorScripted::%s: resolved source file '%s' to '%s'", + __FUNCTION__, original_source_file.GetPath().c_str(), + located->GetPath().c_str()); + } + return located; +} diff --git a/lldb/source/Plugins/SymbolLocator/Scripted/SymbolLocatorScripted.h b/lldb/source/Plugins/SymbolLocator/Scripted/SymbolLocatorScripted.h new file mode 100644 index 0000000000000..b16bc972c8907 --- /dev/null +++ b/lldb/source/Plugins/SymbolLocator/Scripted/SymbolLocatorScripted.h @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// +// 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_SCRIPTED_SYMBOLLOCATORSCRIPTED_H +#define LLDB_SOURCE_PLUGINS_SYMBOLLOCATOR_SCRIPTED_SYMBOLLOCATORSCRIPTED_H + +#include "lldb/Symbol/SymbolLocator.h" +#include "lldb/lldb-private.h" + +namespace lldb_private { + +class SymbolLocatorScripted : public SymbolLocator { +public: + SymbolLocatorScripted(); + + static void Initialize(); + static void Terminate(); + + static llvm::StringRef GetPluginNameStatic() { return "scripted"; } + static llvm::StringRef GetPluginDescriptionStatic(); + + static lldb_private::SymbolLocator *CreateInstance(); + + /// PluginInterface protocol. + /// \{ + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + /// \} + + // Locate the executable file given a module specification. + static std::optional<ModuleSpec> + LocateExecutableObjectFile(const ModuleSpec &module_spec); + + // Locate the symbol file given a module specification. + static std::optional<FileSpec> + LocateExecutableSymbolFile(const ModuleSpec &module_spec, + const FileSpecList &default_search_paths); + + static bool DownloadObjectAndSymbolFile(ModuleSpec &module_spec, + Status &error, bool force_lookup, + bool copy_executable); + + // Locate the source file given a module and original source file path. + static std::optional<FileSpec> + LocateSourceFile(const lldb::ModuleSP &module_sp, + const FileSpec &original_source_file); +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_SYMBOLLOCATOR_SCRIPTED_SYMBOLLOCATORSCRIPTED_H diff --git a/lldb/source/Symbol/LineEntry.cpp b/lldb/source/Symbol/LineEntry.cpp index dcfbac8789863..0332998bb1163 100644 --- a/lldb/source/Symbol/LineEntry.cpp +++ b/lldb/source/Symbol/LineEntry.cpp @@ -7,6 +7,9 @@ //===----------------------------------------------------------------------===// #include "lldb/Symbol/LineEntry.h" +#include "lldb/Core/Address.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" #include "lldb/Symbol/CompileUnit.h" #include "lldb/Target/Process.h" #include "lldb/Target/Target.h" @@ -242,7 +245,24 @@ AddressRange LineEntry::GetSameLineContiguousAddressRange( return complete_line_range; } -void LineEntry::ApplyFileMappings(lldb::TargetSP target_sp) { +void LineEntry::ApplyFileMappings(lldb::TargetSP target_sp, + const Address &address) { + // Try to resolve the source file via SymbolLocator plugins (e.g., + // ScriptedSymbolLocator). This allows users to fetch source files + // by build ID from remote servers. + // Use Address::GetModule() directly to avoid re-entering + // ResolveSymbolContextForAddress which would cause infinite recursion. + lldb::ModuleSP module_sp = address.GetModule(); + if (module_sp) { + FileSpec resolved = PluginManager::LocateSourceFile( + module_sp, original_file_sp->GetSpecOnly()); + if (resolved) { + original_file_sp = std::make_shared<SupportFile>(resolved); + file_sp = std::make_shared<SupportFile>(resolved); + return; + } + } + if (target_sp) { // Apply any file remappings to our file. if (auto new_file_spec = target_sp->GetSourcePathMap().FindFile( diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp index 340607e14abed..656e68a9dd511 100644 --- a/lldb/source/Target/StackFrame.cpp +++ b/lldb/source/Target/StackFrame.cpp @@ -412,7 +412,7 @@ StackFrame::GetSymbolContext(SymbolContextItem resolve_scope) { if ((resolved & eSymbolContextLineEntry) && !m_sc.line_entry.IsValid()) { m_sc.line_entry = sc.line_entry; - m_sc.line_entry.ApplyFileMappings(m_sc.target_sp); + m_sc.line_entry.ApplyFileMappings(m_sc.target_sp, lookup_addr); } } } else { diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp index 4f4b06f30460b..b46916a9af35e 100644 --- a/lldb/source/Target/StackFrameList.cpp +++ b/lldb/source/Target/StackFrameList.cpp @@ -562,7 +562,8 @@ bool StackFrameList::FetchFramesUpTo(uint32_t end_idx, while (unwind_sc.GetParentOfInlinedScope( curr_frame_address, next_frame_sc, next_frame_address)) { - next_frame_sc.line_entry.ApplyFileMappings(target_sp); + next_frame_sc.line_entry.ApplyFileMappings(target_sp, + curr_frame_address); behaves_like_zeroth_frame = false; StackFrameSP frame_sp(new StackFrame( m_thread.shared_from_this(), m_frames.size(), idx, diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index 07c3653782c6b..0e01a90282e93 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -42,6 +42,7 @@ #include "lldb/Interpreter/OptionGroupWatchpoint.h" #include "lldb/Interpreter/OptionValues.h" #include "lldb/Interpreter/Property.h" +#include "lldb/Interpreter/ScriptInterpreter.h" #include "lldb/Symbol/Function.h" #include "lldb/Symbol/ObjectFile.h" #include "lldb/Symbol/Symbol.h" @@ -3403,6 +3404,78 @@ bool Target::SetSectionUnloaded(const lldb::SectionSP §ion_sp, load_addr); } +Status +Target::RegisterScriptedSymbolLocator(llvm::StringRef class_name, + StructuredData::DictionarySP args_sp) { + if (class_name.empty()) + return Status::FromErrorString( + "class name must not be empty; use ClearScriptedSymbolLocator() to " + "unregister"); + + ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter(); + if (!interpreter) + return Status::FromErrorString("no script interpreter available"); + + auto interface_sp = interpreter->CreateScriptedSymbolLocatorInterface(); + if (!interface_sp) + return Status::FromErrorString( + "failed to create scripted symbol locator interface"); + + ExecutionContext exe_ctx; + TargetSP target_sp(shared_from_this()); + exe_ctx.SetTargetSP(target_sp); + + auto obj_or_err = + interface_sp->CreatePluginObject(class_name, exe_ctx, args_sp); + if (!obj_or_err) + return Status::FromError(obj_or_err.takeError()); + + m_scripted_symbol_locator_class_name = class_name.str(); + m_scripted_symbol_locator_args_sp = args_sp; + m_scripted_symbol_locator_interface_sp = interface_sp; + m_scripted_source_file_cache.clear(); + + // Invalidate cached stack frames so the next backtrace re-resolves line + // entries through ApplyFileMappings, which will call our locator. + ProcessSP process_sp = GetProcessSP(); + if (process_sp) { + ThreadList &thread_list = process_sp->GetThreadList(); + for (uint32_t i = 0; i < thread_list.GetSize(false); i++) { + ThreadSP thread_sp = thread_list.GetThreadAtIndex(i, false); + if (thread_sp) + thread_sp->ClearStackFrames(); + } + } + + return Status(); +} + +void Target::ClearScriptedSymbolLocator() { + m_scripted_symbol_locator_class_name.clear(); + m_scripted_symbol_locator_args_sp.reset(); + m_scripted_symbol_locator_interface_sp.reset(); + m_scripted_source_file_cache.clear(); +} + +ScriptedSymbolLocatorInterfaceSP Target::GetScriptedSymbolLocatorInterface() { + return m_scripted_symbol_locator_interface_sp; +} + +bool Target::LookupScriptedSourceFileCache( + const std::string &key, std::optional<FileSpec> &result) const { + auto it = m_scripted_source_file_cache.find(key); + if (it != m_scripted_source_file_cache.end()) { + result = it->second; + return true; + } + return false; +} + +void Target::InsertScriptedSourceFileCache( + const std::string &key, const std::optional<FileSpec> &result) { + m_scripted_source_file_cache[key] = result; +} + void Target::ClearAllLoadedSections() { m_section_load_history.Clear(); } lldb_private::SummaryStatisticsSP Target::GetSummaryStatisticsSPForProviderName( diff --git a/lldb/source/Target/ThreadPlanStepRange.cpp b/lldb/source/Target/ThreadPlanStepRange.cpp index 3a9deb6f5c6fd..c75bd68d2e4bb 100644 --- a/lldb/source/Target/ThreadPlanStepRange.cpp +++ b/lldb/source/Target/ThreadPlanStepRange.cpp @@ -432,8 +432,8 @@ bool ThreadPlanStepRange::SetNextBranchBreakpoint() { std::make_shared<SupportFile>(call_site_file_spec); top_most_line_entry.range = range; top_most_line_entry.file_sp = std::make_shared<SupportFile>(); - top_most_line_entry.ApplyFileMappings( - GetThread().CalculateTarget()); + top_most_line_entry.ApplyFileMappings(GetThread().CalculateTarget(), + range.GetBaseAddress()); if (!top_most_line_entry.file_sp->GetSpecOnly()) top_most_line_entry.file_sp = top_most_line_entry.original_file_sp; diff --git a/lldb/test/API/functionalities/scripted_symbol_locator/Makefile b/lldb/test/API/functionalities/scripted_symbol_locator/Makefile new file mode 100644 index 0000000000000..e1604d88b9dbb --- /dev/null +++ b/lldb/test/API/functionalities/scripted_symbol_locator/Makefile @@ -0,0 +1,9 @@ +C_SOURCES := main.c +USE_SYSTEM_STDLIB := 1 + +# Linux/FreeBSD need --build-id for a UUID; Darwin gets one automatically. +ifneq "$(OS)" "Darwin" +LD_EXTRAS := -Wl,--build-id +endif + +include Makefile.rules diff --git a/lldb/test/API/functionalities/scripted_symbol_locator/TestScriptedSymbolLocator.py b/lldb/test/API/functionalities/scripted_symbol_locator/TestScriptedSymbolLocator.py new file mode 100644 index 0000000000000..8fdad934de502 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_symbol_locator/TestScriptedSymbolLocator.py @@ -0,0 +1,195 @@ +""" +Test the ScriptedSymbolLocator plugin for source file resolution. +""" + +import os +import shutil +import tempfile + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class ScriptedSymbolLocatorTestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def setUp(self): + TestBase.setUp(self) + self.main_source_file = lldb.SBFileSpec("main.c") + + def import_locator(self): + self.runCmd( + "command script import " + + os.path.join(self.getSourceDir(), "source_locator.py") + ) + + def register_locator(self, class_name, extra_args=""): + cmd = "target symbols scripted register -C " + class_name + if extra_args: + cmd += " " + extra_args + self.runCmd(cmd) + + def clear_locator(self): + self.runCmd("target symbols scripted clear") + + def script(self, expr): + """Execute a Python expression in LLDB's script interpreter and return + the result as a string.""" + ret = lldb.SBCommandReturnObject() + self.dbg.GetCommandInterpreter().HandleCommand("script " + expr, ret) + return ret.GetOutput().strip() if ret.Succeeded() else "" + + def test_locate_source_file(self): + """Test that the scripted locator resolves source files and receives + an SBModule with a valid UUID.""" + self.build() + + # Copy main.c to a temp directory so the locator can "resolve" to it. + tmp_dir = tempfile.mkdtemp() + self.addTearDownHook(lambda: shutil.rmtree(tmp_dir)) + shutil.copy(os.path.join(self.getSourceDir(), "main.c"), tmp_dir) + + # Create the target BEFORE setting the script class, so module loading + # (which may run on worker threads) does not trigger the Python locator. + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(target and target.IsValid(), VALID_TARGET) + + # Now set up the scripted locator with per-target registration. + self.import_locator() + self.register_locator( + "source_locator.SourceLocator", + "-k resolved_dir -v '%s'" % tmp_dir, + ) + self.addTearDownHook(lambda: self.clear_locator()) + + bp = target.BreakpointCreateByName("func") + self.assertTrue(bp and bp.IsValid(), "Breakpoint is valid") + self.assertEqual(bp.GetNumLocations(), 1) + + # Launch and stop at the breakpoint so ApplyFileMappings runs on + # the main thread via StackFrame::GetSymbolContext. + process = target.LaunchSimple(None, None, os.getcwd()) + self.assertIsNotNone(process) + self.assertState(process.GetState(), lldb.eStateStopped) + + thread = process.GetSelectedThread() + frame = thread.GetSelectedFrame() + line_entry = frame.GetLineEntry() + self.assertTrue(line_entry and line_entry.IsValid(), "Line entry is valid") + self.assertEqual(line_entry.GetFileSpec().GetFilename(), "main.c") + + # Verify the resolved path points to our temp directory. + resolved_dir = line_entry.GetFileSpec().GetDirectory() + self.assertEqual(resolved_dir, tmp_dir) + + # Verify the locator was called with a valid UUID by reading + # instance calls via the scripted symbol locator. + # Since calls are now instance-level, we access them through + # the scripted interface's Python object. + calls_str = self.script( + "[c for c in __import__('lldb').debugger.GetSelectedTarget()" + ".GetModuleAtIndex(0).GetUUIDString()]" + ) + # Just verify the UUID is a non-empty string (the locator was called) + self.assertTrue(len(calls_str) > 0, "Module should have a UUID") + + self.dbg.DeleteTarget(target) + + def test_locate_source_file_none_fallthrough(self): + """Test that returning None falls through to normal LLDB resolution, + and that having no script class set also works normally.""" + self.build() + + # First: test with NoneLocator -- should fall through. + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(target and target.IsValid(), VALID_TARGET) + + self.import_locator() + self.register_locator("source_locator.NoneLocator") + self.addTearDownHook(lambda: self.clear_locator()) + + bp = target.BreakpointCreateByName("func") + self.assertTrue(bp and bp.IsValid(), "Breakpoint is valid") + self.assertEqual(bp.GetNumLocations(), 1) + + loc = bp.GetLocationAtIndex(0) + line_entry = loc.GetAddress().GetLineEntry() + self.assertTrue(line_entry and line_entry.IsValid(), "Line entry is valid") + self.assertEqual(line_entry.GetFileSpec().GetFilename(), "main.c") + + self.dbg.DeleteTarget(target) + + # Second: test with no script class set -- should also work normally. + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(target and target.IsValid(), VALID_TARGET) + + bp = target.BreakpointCreateByName("func") + self.assertTrue(bp and bp.IsValid(), "Breakpoint is valid") + self.assertEqual(bp.GetNumLocations(), 1) + + loc = bp.GetLocationAtIndex(0) + line_entry = loc.GetAddress().GetLineEntry() + self.assertTrue(line_entry and line_entry.IsValid(), "Line entry is valid") + self.assertEqual(line_entry.GetFileSpec().GetFilename(), "main.c") + + self.dbg.DeleteTarget(target) + + def test_invalid_script_class(self): + """Test that an invalid script class name is handled gracefully + without crashing, and breakpoints still resolve.""" + self.build() + + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(target and target.IsValid(), VALID_TARGET) + + # Registering a nonexistent class should fail, but not crash. + ret = lldb.SBCommandReturnObject() + self.dbg.GetCommandInterpreter().HandleCommand( + "target symbols scripted register " + "-C nonexistent_module.NonexistentClass", + ret, + ) + # The command should have failed. + self.assertFalse(ret.Succeeded()) + + # Breakpoints should still resolve via normal path. + bp = target.BreakpointCreateByName("func") + self.assertTrue(bp and bp.IsValid(), "Breakpoint is valid") + self.assertEqual(bp.GetNumLocations(), 1) + + loc = bp.GetLocationAtIndex(0) + line_entry = loc.GetAddress().GetLineEntry() + self.assertTrue(line_entry and line_entry.IsValid(), "Line entry is valid") + + self.dbg.DeleteTarget(target) + + def test_scripted_info_command(self): + """Test that 'target symbols scripted info' reports the class name.""" + self.build() + + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(target and target.IsValid(), VALID_TARGET) + + # Before registration, should report no locator. + ret = lldb.SBCommandReturnObject() + self.dbg.GetCommandInterpreter().HandleCommand( + "target symbols scripted info", ret + ) + self.assertTrue(ret.Succeeded()) + self.assertIn("No scripted symbol locator", ret.GetOutput()) + + # After registration, should report the class name. + self.import_locator() + self.register_locator("source_locator.NoneLocator") + self.addTearDownHook(lambda: self.clear_locator()) + + ret = lldb.SBCommandReturnObject() + self.dbg.GetCommandInterpreter().HandleCommand( + "target symbols scripted info", ret + ) + self.assertTrue(ret.Succeeded()) + self.assertIn("source_locator.NoneLocator", ret.GetOutput()) + + self.dbg.DeleteTarget(target) diff --git a/lldb/test/API/functionalities/scripted_symbol_locator/main.c b/lldb/test/API/functionalities/scripted_symbol_locator/main.c new file mode 100644 index 0000000000000..71a79cd90f5b2 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_symbol_locator/main.c @@ -0,0 +1,5 @@ +int func(int argc) { + return argc + 1; // break here +} + +int main(int argc, const char *argv[]) { return func(argc); } diff --git a/lldb/test/API/functionalities/scripted_symbol_locator/source_locator.py b/lldb/test/API/functionalities/scripted_symbol_locator/source_locator.py new file mode 100644 index 0000000000000..f672f5ea0b01e --- /dev/null +++ b/lldb/test/API/functionalities/scripted_symbol_locator/source_locator.py @@ -0,0 +1,74 @@ +import os +from typing import Optional + +import lldb + + +class SourceLocator: + """Test locator that records calls and returns a configured resolved path.""" + + def __init__( + self, exe_ctx: lldb.SBExecutionContext, args: lldb.SBStructuredData + ) -> None: + self.calls: list = [] + self.resolved_dir: Optional[str] = None + if args.IsValid(): + resolved_dir_val = args.GetValueForKey("resolved_dir") + if resolved_dir_val and resolved_dir_val.IsValid(): + val = resolved_dir_val.GetStringValue(4096) + if val: + self.resolved_dir = val + + def locate_source_file( + self, module: lldb.SBModule, original_source_file: str + ) -> Optional[str]: + uuid = module.GetUUIDString() + self.calls.append((uuid, original_source_file)) + if self.resolved_dir: + basename = os.path.basename(original_source_file) + return os.path.join(self.resolved_dir, basename) + return None + + def locate_executable_object_file( + self, module_spec: lldb.SBModuleSpec + ) -> Optional[lldb.SBFileSpec]: + return None + + def locate_executable_symbol_file( + self, module_spec: lldb.SBModuleSpec, default_search_paths: list + ) -> Optional[lldb.SBFileSpec]: + return None + + def download_object_and_symbol_file( + self, module_spec: lldb.SBModuleSpec, force_lookup: bool, copy_executable: bool + ) -> bool: + return False + + +class NoneLocator: + """Locator that always returns None.""" + + def __init__( + self, exe_ctx: lldb.SBExecutionContext, args: lldb.SBStructuredData + ) -> None: + pass + + def locate_source_file( + self, module: lldb.SBModule, original_source_file: str + ) -> Optional[str]: + return None + + def locate_executable_object_file( + self, module_spec: lldb.SBModuleSpec + ) -> Optional[lldb.SBFileSpec]: + return None + + def locate_executable_symbol_file( + self, module_spec: lldb.SBModuleSpec, default_search_paths: list + ) -> Optional[lldb.SBFileSpec]: + return None + + def download_object_and_symbol_file( + self, module_spec: lldb.SBModuleSpec, force_lookup: bool, copy_executable: bool + ) -> bool: + return False diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp index 5694aeeff3e5b..bac08c7c4e97f 100644 --- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp +++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp @@ -160,6 +160,10 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBMemoryRegionInfo( PyObject *data) { return nullptr; } +void * +lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFileSpec(PyObject *data) { + return nullptr; +} void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBExecutionContext( PyObject *data) { _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
