rchamala 1ee03d1e09
[lldb] Add ScriptedSymbolLocator plugin for source file resolution (#181334)
## Summary                                                        
                                                                    
Based on discussion from
[RFC](https://discourse.llvm.org/t/rfc-python-callback-for-source-file-resolution/83545),
this PR adds a new `SymbolLocatorScripted` plugin that allows Python
scripts to implement custom symbol and source file resolution logic.
This enables downstream users to build custom symbol servers, source
file remapping, and build artifact resolution entirely in Python.
                                                                    
  ### Changes

- Adds `LocateSourceFile()` to the SymbolLocator plugin interface,
called during source path resolution with a fully loaded `ModuleSP`, so
the plugin has access to the module's UUID, file paths, and symbols.
- Adds `SymbolLocatorScripted` plugin that delegates all four
SymbolLocator methods (`LocateExecutableObjectFile`,
`LocateExecutableSymbolFile`, `DownloadObjectAndSymbolFile`,
`LocateSourceFile`) to a user-provided Python class.
- Adds `ScriptedSymbolLocatorPythonInterface` to bridge C++ calls to
Python, with proper GIL management and error handling.
- Results for `LocateSourceFile` are cached per (module UUID, source
file) pair.
- The Python class is configured via: `settings set
plugin.symbol-locator.scripted.script-class module.ClassName`

  ### Python class interface

  ```python
  class MyLocator:
      def __init__(self, exe_ctx, args): ...
      def locate_source_file(self, module, original_source_file):
  ...
      def locate_executable_object_file(self, module_spec): ...
      def locate_executable_symbol_file(self, module_spec,
  default_search_paths): ...
      def download_object_and_symbol_file(self, module_spec,
  force_lookup, copy_executable): ...
```

  ### Test plan
```
  Added TestScriptedSymbolLocator.py with 3 test cases:
  - test_locate_source_file — verifies the locator resolves source
  files, receives a valid SBModule with UUID, and remaps paths correctly
  - test_locate_source_file_none_fallthrough — verifies returning
None falls through to default LLDB resolution, and that having no script
  class set works normally
  - test_invalid_script_class — verifies graceful handling of
  invalid class names without crashing
```

Co-authored-by: Rahul Reddy Chamala <rachamal@fb.com>
2026-02-14 07:39:00 -08:00

382 lines
13 KiB
C++

//===-- ScriptedPythonInterface.cpp ---------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "lldb/Host/Config.h"
#include "lldb/Utility/Log.h"
#include "lldb/lldb-enumerations.h"
#if LLDB_ENABLE_PYTHON
// LLDB Python header must be included first
#include "../lldb-python.h"
#include "../ScriptInterpreterPythonImpl.h"
#include "ScriptedPythonInterface.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Symbol/SymbolContext.h"
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/FileSpecList.h"
#include "lldb/ValueObject/ValueObjectList.h"
#include <optional>
using namespace lldb;
using namespace lldb_private;
ScriptedPythonInterface::ScriptedPythonInterface(
ScriptInterpreterPythonImpl &interpreter)
: ScriptedInterface(), m_interpreter(interpreter) {}
template <>
StructuredData::ArraySP
ScriptedPythonInterface::ExtractValueFromPythonObject<StructuredData::ArraySP>(
python::PythonObject &p, Status &error) {
python::PythonList result_list(python::PyRefType::Borrowed, p.get());
return result_list.CreateStructuredArray();
}
template <>
StructuredData::DictionarySP
ScriptedPythonInterface::ExtractValueFromPythonObject<
StructuredData::DictionarySP>(python::PythonObject &p, Status &error) {
python::PythonDictionary result_dict(python::PyRefType::Borrowed, p.get());
return result_dict.CreateStructuredDictionary();
}
template <>
Status ScriptedPythonInterface::ExtractValueFromPythonObject<Status>(
python::PythonObject &p, Status &error) {
if (lldb::SBError *sb_error = reinterpret_cast<lldb::SBError *>(
python::LLDBSWIGPython_CastPyObjectToSBError(p.get())))
return m_interpreter.GetStatusFromSBError(*sb_error);
error =
Status::FromErrorString("Couldn't cast lldb::SBError to lldb::Status.");
return {};
}
template <>
Event *ScriptedPythonInterface::ExtractValueFromPythonObject<Event *>(
python::PythonObject &p, Status &error) {
if (lldb::SBEvent *sb_event = reinterpret_cast<lldb::SBEvent *>(
python::LLDBSWIGPython_CastPyObjectToSBEvent(p.get())))
return m_interpreter.GetOpaqueTypeFromSBEvent(*sb_event);
error = Status::FromErrorString(
"Couldn't cast lldb::SBEvent to lldb_private::Event.");
return nullptr;
}
template <>
lldb::StreamSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StreamSP>(
python::PythonObject &p, Status &error) {
if (lldb::SBStream *sb_stream = reinterpret_cast<lldb::SBStream *>(
python::LLDBSWIGPython_CastPyObjectToSBStream(p.get())))
return m_interpreter.GetOpaqueTypeFromSBStream(*sb_stream);
error = Status::FromErrorString(
"Couldn't cast lldb::SBStream to lldb_private::Stream.");
return nullptr;
}
template <>
lldb::StackFrameSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameSP>(
python::PythonObject &p, Status &error) {
if (lldb::SBFrame *sb_frame = reinterpret_cast<lldb::SBFrame *>(
python::LLDBSWIGPython_CastPyObjectToSBFrame(p.get())))
return m_interpreter.GetOpaqueTypeFromSBFrame(*sb_frame);
error = Status::FromErrorString(
"Couldn't cast lldb::SBFrame to lldb_private::StackFrame.");
return nullptr;
}
template <>
lldb::ThreadSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ThreadSP>(
python::PythonObject &p, Status &error) {
if (lldb::SBThread *sb_thread = reinterpret_cast<lldb::SBThread *>(
python::LLDBSWIGPython_CastPyObjectToSBThread(p.get())))
return m_interpreter.GetOpaqueTypeFromSBThread(*sb_thread);
error = Status::FromErrorString(
"Couldn't cast lldb::SBThread to lldb_private::Thread.");
return nullptr;
}
template <>
SymbolContext
ScriptedPythonInterface::ExtractValueFromPythonObject<SymbolContext>(
python::PythonObject &p, Status &error) {
if (lldb::SBSymbolContext *sb_symbol_context =
reinterpret_cast<lldb::SBSymbolContext *>(
python::LLDBSWIGPython_CastPyObjectToSBSymbolContext(p.get())))
return m_interpreter.GetOpaqueTypeFromSBSymbolContext(*sb_symbol_context);
error = Status::FromErrorString(
"Couldn't cast lldb::SBSymbolContext to lldb_private::SymbolContext.");
return {};
}
template <>
lldb::DataExtractorSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DataExtractorSP>(
python::PythonObject &p, Status &error) {
lldb::SBData *sb_data = reinterpret_cast<lldb::SBData *>(
python::LLDBSWIGPython_CastPyObjectToSBData(p.get()));
if (!sb_data) {
error = Status::FromErrorStringWithFormat(
"Couldn't cast lldb::SBData to lldb::DataExtractorSP.");
return nullptr;
}
return m_interpreter.GetDataExtractorFromSBData(*sb_data);
}
template <>
lldb::BreakpointSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::BreakpointSP>(
python::PythonObject &p, Status &error) {
lldb::SBBreakpoint *sb_breakpoint = reinterpret_cast<lldb::SBBreakpoint *>(
python::LLDBSWIGPython_CastPyObjectToSBBreakpoint(p.get()));
if (!sb_breakpoint) {
error = Status::FromErrorStringWithFormat(
"Couldn't cast lldb::SBBreakpoint to lldb::BreakpointSP.");
return nullptr;
}
return m_interpreter.GetOpaqueTypeFromSBBreakpoint(*sb_breakpoint);
}
template <>
lldb::BreakpointLocationSP
ScriptedPythonInterface::ExtractValueFromPythonObject<
lldb::BreakpointLocationSP>(python::PythonObject &p, Status &error) {
lldb::SBBreakpointLocation *sb_break_loc =
reinterpret_cast<lldb::SBBreakpointLocation *>(
python::LLDBSWIGPython_CastPyObjectToSBBreakpointLocation(p.get()));
if (!sb_break_loc) {
error = Status::FromErrorStringWithFormat(
"Couldn't cast lldb::SBBreakpointLocation to "
"lldb::BreakpointLocationSP.");
return nullptr;
}
return m_interpreter.GetOpaqueTypeFromSBBreakpointLocation(*sb_break_loc);
}
template <>
lldb::ProcessAttachInfoSP ScriptedPythonInterface::ExtractValueFromPythonObject<
lldb::ProcessAttachInfoSP>(python::PythonObject &p, Status &error) {
lldb::SBAttachInfo *sb_attach_info = reinterpret_cast<lldb::SBAttachInfo *>(
python::LLDBSWIGPython_CastPyObjectToSBAttachInfo(p.get()));
if (!sb_attach_info) {
error = Status::FromErrorStringWithFormat(
"Couldn't cast lldb::SBAttachInfo to lldb::ProcessAttachInfoSP.");
return nullptr;
}
return m_interpreter.GetOpaqueTypeFromSBAttachInfo(*sb_attach_info);
}
template <>
lldb::ProcessLaunchInfoSP ScriptedPythonInterface::ExtractValueFromPythonObject<
lldb::ProcessLaunchInfoSP>(python::PythonObject &p, Status &error) {
lldb::SBLaunchInfo *sb_launch_info = reinterpret_cast<lldb::SBLaunchInfo *>(
python::LLDBSWIGPython_CastPyObjectToSBLaunchInfo(p.get()));
if (!sb_launch_info) {
error = Status::FromErrorStringWithFormat(
"Couldn't cast lldb::SBLaunchInfo to lldb::ProcessLaunchInfoSP.");
return nullptr;
}
return m_interpreter.GetOpaqueTypeFromSBLaunchInfo(*sb_launch_info);
}
template <>
std::optional<MemoryRegionInfo>
ScriptedPythonInterface::ExtractValueFromPythonObject<
std::optional<MemoryRegionInfo>>(python::PythonObject &p, Status &error) {
lldb::SBMemoryRegionInfo *sb_mem_reg_info =
reinterpret_cast<lldb::SBMemoryRegionInfo *>(
python::LLDBSWIGPython_CastPyObjectToSBMemoryRegionInfo(p.get()));
if (!sb_mem_reg_info) {
error = Status::FromErrorStringWithFormat(
"Couldn't cast lldb::SBMemoryRegionInfo to "
"lldb_private::MemoryRegionInfo.");
return {};
}
return m_interpreter.GetOpaqueTypeFromSBMemoryRegionInfo(*sb_mem_reg_info);
}
template <>
lldb::ExecutionContextRefSP
ScriptedPythonInterface::ExtractValueFromPythonObject<
lldb::ExecutionContextRefSP>(python::PythonObject &p, Status &error) {
lldb::SBExecutionContext *sb_exe_ctx =
reinterpret_cast<lldb::SBExecutionContext *>(
python::LLDBSWIGPython_CastPyObjectToSBExecutionContext(p.get()));
if (!sb_exe_ctx) {
error = Status::FromErrorStringWithFormat(
"Couldn't cast lldb::SBExecutionContext to "
"lldb::ExecutionContextRefSP.");
return {};
}
return m_interpreter.GetOpaqueTypeFromSBExecutionContext(*sb_exe_ctx);
}
template <>
lldb::DescriptionLevel
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DescriptionLevel>(
python::PythonObject &p, Status &error) {
lldb::DescriptionLevel ret_val = lldb::eDescriptionLevelBrief;
llvm::Expected<unsigned long long> unsigned_or_err = p.AsUnsignedLongLong();
if (!unsigned_or_err) {
error = (Status::FromError(unsigned_or_err.takeError()));
return ret_val;
}
unsigned long long unsigned_val = *unsigned_or_err;
if (unsigned_val >= lldb::DescriptionLevel::kNumDescriptionLevels) {
error = Status("value too large for lldb::DescriptionLevel.");
return ret_val;
}
return static_cast<lldb::DescriptionLevel>(unsigned_val);
}
template <>
lldb::StackFrameListSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameListSP>(
python::PythonObject &p, Status &error) {
lldb::SBFrameList *sb_frame_list = reinterpret_cast<lldb::SBFrameList *>(
python::LLDBSWIGPython_CastPyObjectToSBFrameList(p.get()));
if (!sb_frame_list) {
error = Status::FromErrorStringWithFormat(
"couldn't cast lldb::SBFrameList to lldb::StackFrameListSP.");
return {};
}
return m_interpreter.GetOpaqueTypeFromSBFrameList(*sb_frame_list);
}
template <>
lldb::ValueObjectSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ValueObjectSP>(
python::PythonObject &p, Status &error) {
lldb::SBValue *sb_value = reinterpret_cast<lldb::SBValue *>(
python::LLDBSWIGPython_CastPyObjectToSBValue(p.get()));
if (!sb_value) {
error = Status::FromErrorStringWithFormat(
"couldn't cast lldb::SBValue to lldb::ValueObjectSP");
return {};
}
return m_interpreter.GetOpaqueTypeFromSBValue(*sb_value);
}
template <>
lldb::ValueObjectListSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ValueObjectListSP>(
python::PythonObject &p, Status &error) {
lldb::SBValueList *sb_value_list = reinterpret_cast<lldb::SBValueList *>(
python::LLDBSWIGPython_CastPyObjectToSBValueList(p.get()));
if (!sb_value_list) {
error = Status::FromErrorStringWithFormat(
"couldn't cast lldb::SBValueList to lldb::ValueObjectListSP");
return {};
}
lldb::ValueObjectListSP out = std::make_shared<ValueObjectList>();
for (uint32_t i = 0, e = sb_value_list->GetSize(); i < e; ++i) {
SBValue value = sb_value_list->GetValueAtIndex(i);
out->Append(m_interpreter.GetOpaqueTypeFromSBValue(value));
}
return out;
}
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()))) {
if (auto file_spec =
m_interpreter.GetOpaqueTypeFromSBFileSpec(*sb_file_spec))
return *file_spec;
}
error = Status::FromErrorString(
"couldn't cast lldb::SBFileSpec to lldb_private::FileSpec.");
return {};
}
template <>
ModuleSpec ScriptedPythonInterface::ExtractValueFromPythonObject<ModuleSpec>(
python::PythonObject &p, Status &error) {
if (lldb::SBModuleSpec *sb_module_spec =
reinterpret_cast<lldb::SBModuleSpec *>(
python::LLDBSWIGPython_CastPyObjectToSBModuleSpec(p.get()))) {
if (auto module_spec =
m_interpreter.GetOpaqueTypeFromSBModuleSpec(*sb_module_spec))
return *module_spec;
}
error = Status::FromErrorString(
"couldn't cast lldb::SBModuleSpec to lldb_private::ModuleSpec.");
return {};
}
template <>
FileSpecList
ScriptedPythonInterface::ExtractValueFromPythonObject<FileSpecList>(
python::PythonObject &p, Status &error) {
FileSpecList result;
if (lldb::SBFileSpecList *sb_list = reinterpret_cast<lldb::SBFileSpecList *>(
python::LLDBSWIGPython_CastPyObjectToSBFileSpecList(p.get()))) {
for (uint32_t i = 0; i < sb_list->GetSize(); i++) {
lldb::SBFileSpec sb_file_spec = sb_list->GetFileSpecAtIndex(i);
if (auto file_spec =
m_interpreter.GetOpaqueTypeFromSBFileSpec(sb_file_spec))
result.Append(*file_spec);
}
return result;
}
error = Status::FromErrorString(
"couldn't cast Python object to lldb::SBFileSpecList.");
return result;
}
template <>
lldb::ModuleSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ModuleSP>(
python::PythonObject &p, Status &error) {
if (lldb::SBModule *sb_module = reinterpret_cast<lldb::SBModule *>(
python::LLDBSWIGPython_CastPyObjectToSBModule(p.get())))
return m_interpreter.GetOpaqueTypeFromSBModule(*sb_module);
error = Status::FromErrorString(
"couldn't cast lldb::SBModule to lldb::ModuleSP.");
return {};
}
#endif