diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig index 3d1d04e47e70b..9e8528357bf76 100644 --- a/lldb/bindings/python/python-wrapper.swig +++ b/lldb/bindings/python/python-wrapper.swig @@ -542,6 +542,18 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBEvent(PyObject * data return sb_ptr; } +void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBModule(PyObject * data) { + lldb::SBModule *sb_ptr = nullptr; + + int valid_cast = + SWIG_ConvertPtr(data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBModule, 0); + + if (valid_cast == -1) + return NULL; + + return sb_ptr; +} + void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBStream(PyObject * data) { lldb::SBStream *sb_ptr = nullptr; diff --git a/lldb/include/lldb/API/SBModule.h b/lldb/include/lldb/API/SBModule.h index 85332066ee687..7f4e9ef470532 100644 --- a/lldb/include/lldb/API/SBModule.h +++ b/lldb/include/lldb/API/SBModule.h @@ -306,6 +306,7 @@ class LLDB_API SBModule { friend class SBType; friend class lldb_private::python::SWIGBridge; + friend class lldb_private::ScriptInterpreter; explicit SBModule(const lldb::ModuleSP &module_sp); diff --git a/lldb/include/lldb/Interpreter/Interfaces/JITLoaderInterface.h b/lldb/include/lldb/Interpreter/Interfaces/JITLoaderInterface.h new file mode 100644 index 0000000000000..b18d7ae5ae3f5 --- /dev/null +++ b/lldb/include/lldb/Interpreter/Interfaces/JITLoaderInterface.h @@ -0,0 +1,30 @@ +//===-- JITLoaderInterface.h ------------------------------------*- C++ -*-===// +// +// 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_JITLOADERINTERFACE_H +#define LLDB_INTERPRETER_INTERFACES_JITLOADERINTERFACE_H + +#include "ScriptedThreadInterface.h" +#include "lldb/lldb-private.h" + +namespace lldb_private { +class JITLoaderInterface : virtual public ScriptedInterface { +public: + + virtual llvm::Expected + CreatePluginObject(llvm::StringRef class_name, + lldb_private::ExecutionContext &exe_ctx) = 0; + + virtual void DidAttach() {}; + virtual void DidLaunch() {}; + virtual void ModulesDidLoad(lldb_private::ModuleList &module_list) {}; + +}; +} // namespace lldb_private + +#endif // LLDB_INTERPRETER_INTERFACES_JITLOADERINTERFACE_H diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h index 3a4a7ae924584..99531844c80f3 100644 --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -16,6 +16,7 @@ #include "lldb/API/SBEvent.h" #include "lldb/API/SBExecutionContext.h" #include "lldb/API/SBLaunchInfo.h" +#include "lldb/API/SBModule.h" #include "lldb/API/SBMemoryRegionInfo.h" #include "lldb/API/SBStream.h" #include "lldb/Breakpoint/BreakpointOptions.h" @@ -24,6 +25,7 @@ #include "lldb/Core/ThreadedCommunication.h" #include "lldb/Host/PseudoTerminal.h" #include "lldb/Host/StreamFile.h" +#include "lldb/Interpreter/Interfaces/JITLoaderInterface.h" #include "lldb/Interpreter/Interfaces/OperatingSystemInterface.h" #include "lldb/Interpreter/Interfaces/ScriptedPlatformInterface.h" #include "lldb/Interpreter/Interfaces/ScriptedProcessInterface.h" @@ -560,6 +562,10 @@ class ScriptInterpreter : public PluginInterface { return {}; } + virtual lldb::JITLoaderInterfaceSP CreateJITLoaderInterface() { + return {}; + } + virtual lldb::ScriptedPlatformInterfaceUP GetScriptedPlatformInterface() { return {}; } @@ -597,6 +603,8 @@ class ScriptInterpreter : public PluginInterface { lldb::ExecutionContextRefSP GetOpaqueTypeFromSBExecutionContext( const lldb::SBExecutionContext &exe_ctx) const; + lldb::ModuleSP GetOpaqueTypeFromSBModule(const lldb::SBModule &module) const; + protected: Debugger &m_debugger; lldb::ScriptLanguage m_script_lang; diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index 536a69fb89759..305a682f0ad83 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -87,6 +87,7 @@ class ProcessProperties : public Properties { Args GetExtraStartupCommands() const; void SetExtraStartupCommands(const Args &args); FileSpec GetPythonOSPluginPath() const; + FileSpec GetPythonJITLoaderPath() const; uint32_t GetVirtualAddressableBits() const; void SetVirtualAddressableBits(uint32_t bits); uint32_t GetHighmemVirtualAddressableBits() const; diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h index c664d1398f74d..837c734a98059 100644 --- a/lldb/include/lldb/lldb-forward.h +++ b/lldb/include/lldb/lldb-forward.h @@ -114,6 +114,7 @@ class Instruction; class InstructionList; class InstrumentationRuntime; class JITLoader; +class JITLoaderInterface; class JITLoaderList; class Language; class LanguageCategory; @@ -364,6 +365,7 @@ typedef std::shared_ptr IOHandlerSP; typedef std::shared_ptr IOObjectSP; typedef std::shared_ptr IRExecutionUnitSP; typedef std::shared_ptr JITLoaderSP; +typedef std::shared_ptr JITLoaderInterfaceSP; typedef std::unique_ptr JITLoaderListUP; typedef std::shared_ptr LanguageRuntimeSP; typedef std::unique_ptr SystemRuntimeUP; diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp index 63655cc5a50c6..0b12425c1c504 100644 --- a/lldb/source/Interpreter/ScriptInterpreter.cpp +++ b/lldb/source/Interpreter/ScriptInterpreter.cpp @@ -130,6 +130,12 @@ ScriptInterpreter::GetOpaqueTypeFromSBExecutionContext( return exe_ctx.m_exe_ctx_sp; } +lldb::ModuleSP +ScriptInterpreter::GetOpaqueTypeFromSBModule( + const lldb::SBModule &module) const { + return module.m_opaque_sp; +} + lldb::ScriptLanguage ScriptInterpreter::StringToLanguage(const llvm::StringRef &language) { if (language.equals_insensitive(LanguageToString(eScriptLanguageNone))) diff --git a/lldb/source/Plugins/JITLoader/CMakeLists.txt b/lldb/source/Plugins/JITLoader/CMakeLists.txt index e52230199109f..aad64f1e7c8bd 100644 --- a/lldb/source/Plugins/JITLoader/CMakeLists.txt +++ b/lldb/source/Plugins/JITLoader/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(GDB) +add_subdirectory(Python) diff --git a/lldb/source/Plugins/JITLoader/Python/CMakeLists.txt b/lldb/source/Plugins/JITLoader/Python/CMakeLists.txt new file mode 100644 index 0000000000000..a64ade6009a2b --- /dev/null +++ b/lldb/source/Plugins/JITLoader/Python/CMakeLists.txt @@ -0,0 +1,11 @@ +add_lldb_library(lldbPluginJITLoaderPython PLUGIN + JITLoaderPython.cpp + + LINK_LIBS + lldbCore + lldbInterpreter + lldbSymbol + lldbTarget + lldbValueObject + lldbPluginProcessUtility + ) diff --git a/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.cpp b/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.cpp new file mode 100644 index 0000000000000..8981d55fc4661 --- /dev/null +++ b/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.cpp @@ -0,0 +1,146 @@ +//===-- JITLoaderPython.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" + +#if LLDB_ENABLE_PYTHON + +#include "JITLoaderPython.h" + +#include "Plugins/Process/Utility/RegisterContextDummy.h" +#include "Plugins/Process/Utility/RegisterContextMemory.h" +#include "Plugins/Process/Utility/ThreadMemory.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/ScriptInterpreter.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadList.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/RegisterValue.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/Utility/StructuredData.h" +#include "lldb/ValueObject/ValueObjectVariable.h" + +#include + +using namespace lldb; +using namespace lldb_private; + +LLDB_PLUGIN_DEFINE(JITLoaderPython) + +void JITLoaderPython::Initialize() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + GetPluginDescriptionStatic(), CreateInstance, + nullptr); +} + +void JITLoaderPython::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +JITLoaderSP JITLoaderPython::CreateInstance(Process *process, bool force) { + // Python JITLoader plug-ins must be requested by name, so force must + // be true + FileSpec python_os_plugin_spec(process->GetPythonJITLoaderPath()); + if (python_os_plugin_spec && + FileSystem::Instance().Exists(python_os_plugin_spec)) { + std::unique_ptr os_up( + new JITLoaderPython(process, python_os_plugin_spec)); + if (os_up.get() && os_up->IsValid()) + return os_up; + } + return nullptr; +} + +llvm::StringRef JITLoaderPython::GetPluginDescriptionStatic() { + return "JIT loader plug-in that implements a JIT loader using a python " + "class that implements the necessary JITLoader functionality."; +} + +JITLoaderPython::JITLoaderPython(lldb_private::Process *process, + const FileSpec &python_module_path) + : JITLoader(process), m_interpreter(nullptr), m_script_object_sp() { + if (!process) + return; + TargetSP target_sp = process->CalculateTarget(); + if (!target_sp) + return; + m_interpreter = target_sp->GetDebugger().GetScriptInterpreter(); + if (!m_interpreter) + return; + + std::string os_plugin_class_name( + python_module_path.GetFilename().AsCString("")); + if (os_plugin_class_name.empty()) + return; + + LoadScriptOptions options; + char python_module_path_cstr[PATH_MAX]; + python_module_path.GetPath(python_module_path_cstr, + sizeof(python_module_path_cstr)); + Status error; + if (!m_interpreter->LoadScriptingModule(python_module_path_cstr, options, + error)) + return; + + // Strip the ".py" extension if there is one + size_t py_extension_pos = os_plugin_class_name.rfind(".py"); + if (py_extension_pos != std::string::npos) + os_plugin_class_name.erase(py_extension_pos); + // Add ".JITLoaderPlugin" to the module name to get a string like + // "modulename.JITLoaderPlugin" + os_plugin_class_name += ".JITLoaderPlugin"; + + JITLoaderInterfaceSP interface_sp = + m_interpreter->CreateJITLoaderInterface(); + if (!interface_sp) + return; + + ExecutionContext exe_ctx(process); + auto obj_or_err = interface_sp->CreatePluginObject( + os_plugin_class_name, exe_ctx); + + if (!obj_or_err) { + llvm::consumeError(obj_or_err.takeError()); + return; + } + + StructuredData::GenericSP owned_script_object_sp = *obj_or_err; + if (!owned_script_object_sp->IsValid()) + return; + + m_script_object_sp = owned_script_object_sp; + m_interface_sp = interface_sp; +} + +JITLoaderPython::~JITLoaderPython() = default; + +void JITLoaderPython::DidAttach() { + if (m_interface_sp) + m_interface_sp->DidAttach(); +} + +void JITLoaderPython::DidLaunch() { + if (m_interface_sp) + m_interface_sp->DidLaunch(); +} + +void JITLoaderPython::ModulesDidLoad(lldb_private::ModuleList &module_list) { + if (m_interface_sp) + m_interface_sp->ModulesDidLoad(module_list); +} + +#endif // #if LLDB_ENABLE_PYTHON diff --git a/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.h b/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.h new file mode 100644 index 0000000000000..87146ad216de3 --- /dev/null +++ b/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.h @@ -0,0 +1,62 @@ +//===-- JITLoaderPython.h ---------------------------------------*- C++ -*-===// +// +// 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 liblldb_JITLoaderPython_h_ +#define liblldb_JITLoaderPython_h_ + +#include "lldb/Host/Config.h" + +#if LLDB_ENABLE_PYTHON + +#include "lldb/Target/JITLoader.h" +#include "lldb/Utility/StructuredData.h" + +namespace lldb_private { +class ScriptInterpreter; +} + +class JITLoaderPython : public lldb_private::JITLoader { +public: + JITLoaderPython(lldb_private::Process *process, + const lldb_private::FileSpec &python_module_path); + + ~JITLoaderPython() override; + + // Static Functions + static lldb::JITLoaderSP CreateInstance(lldb_private::Process *process, + bool force); + + static void Initialize(); + + static void Terminate(); + + static llvm::StringRef GetPluginNameStatic() { return "python"; } + + static llvm::StringRef GetPluginDescriptionStatic(); + + // lldb_private::PluginInterface Methods + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + + // lldb_private::JITLoader Methods + void DidAttach() override; + void DidLaunch() override; + void ModulesDidLoad(lldb_private::ModuleList &module_list) override; + +protected: + bool IsValid() const { + return m_script_object_sp && m_script_object_sp->IsValid(); + } + + lldb_private::ScriptInterpreter *m_interpreter = nullptr; + lldb::JITLoaderInterfaceSP m_interface_sp; + lldb_private::StructuredData::GenericSP m_script_object_sp; +}; + +#endif // LLDB_ENABLE_PYTHON + +#endif // liblldb_JITLoaderPython_h_ diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt index ee5e48ad5cdc3..ed254707ebb00 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt @@ -20,6 +20,7 @@ if (LLDB_ENABLE_LIBEDIT) endif() add_lldb_library(lldbPluginScriptInterpreterPythonInterfaces PLUGIN + JITLoaderPythonInterface.cpp OperatingSystemPythonInterface.cpp ScriptInterpreterPythonInterfaces.cpp ScriptedPlatformPythonInterface.cpp @@ -40,5 +41,3 @@ add_lldb_library(lldbPluginScriptInterpreterPythonInterfaces PLUGIN LINK_COMPONENTS Support ) - - diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.cpp new file mode 100644 index 0000000000000..43d77964efa49 --- /dev/null +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.cpp @@ -0,0 +1,79 @@ +//===-- JITLoaderPythonInterface.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/Core/ModuleList.h" +#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 "JITLoaderPythonInterface.h" + +using namespace lldb; +using namespace lldb_private; + +JITLoaderPythonInterface::JITLoaderPythonInterface( + ScriptInterpreterPythonImpl &interpreter) + : ScriptedPythonInterface(interpreter) {} + +llvm::Expected +JITLoaderPythonInterface::CreatePluginObject( + llvm::StringRef class_name, ExecutionContext &exe_ctx) { + return ScriptedPythonInterface::CreatePluginObject(class_name, nullptr, + exe_ctx.GetProcessSP()); +} + +void JITLoaderPythonInterface::Initialize() { + const std::vector ci_usages = { + "settings set target.process.python-jit-loader-plugin-path "}; + const std::vector api_usages = {}; + PluginManager::RegisterPlugin( + GetPluginNameStatic(), llvm::StringRef("JIT loader python plugin"), + CreateInstance, eScriptLanguagePython, {ci_usages, api_usages}); +} + +void JITLoaderPythonInterface::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +//------------------------------------------------------------------------------ +// JITLoader API overrides +//------------------------------------------------------------------------------ + +void JITLoaderPythonInterface::DidAttach() { + Status error; + Dispatch("did_attach", error); +} + +void JITLoaderPythonInterface::DidLaunch() { + Status error; + Dispatch("did_launch", error); +} + +void JITLoaderPythonInterface::ModulesDidLoad(ModuleList &module_list) { + Status error; + // There is no SBModuleList, so we need to deliver each module individually + // to the python scripts since it uses the LLDB public API. + module_list.ForEach([&](const lldb::ModuleSP &module_sp_ref) { + lldb::ModuleSP module_sp(module_sp_ref); + Dispatch("module_did_load", error, module_sp); + return true; // Keep iterating + }); +} + +#endif diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.h new file mode 100644 index 0000000000000..cb8ef5b8c43ad --- /dev/null +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.h @@ -0,0 +1,104 @@ +//===-- JITLoaderPythonInterface.h ------------------------------*- C++ -*-===// +// +// 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_JITLOADERPYTHONINTERFACE_H +#define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_JITLOADERPYTHONINTERFACE_H + +#include "lldb/Host/Config.h" +#include "lldb/Interpreter/Interfaces/JITLoaderInterface.h" + +#if LLDB_ENABLE_PYTHON + +#include "ScriptedThreadPythonInterface.h" + +#include + +/// Defines a JITLoader interface for Python. +/// +/// Users can implement a JIT loader in python by implementing a class that +/// named "JITLoaderPlugin" in a module. The path to this module is specified +/// in the settings using: +/// +/// (lldb) setting set target.process.python-jit-loader-path +/// +/// When the process starts up it will load this module and call methods on the +/// python class. The python class must implement the following methods: +/// +/// #--------------------------------------------------------------------------- +/// # The class must be named "JITLoaderPlugin" in the python file. +/// #--------------------------------------------------------------------------- +/// class JITLoaderPlugin: +/// #----------------------------------------------------------------------- +/// # Construct this object with reference to the process that owns this +/// # JIT loader. +/// #----------------------------------------------------------------------- +/// def __init__(self, process: lldb.SBProcess): +/// self.process = process +/// +/// #----------------------------------------------------------------------- +/// # Called when attaching is completed. +/// #----------------------------------------------------------------------- +/// def did_attach(self): +/// pass +/// +/// #----------------------------------------------------------------------- +/// # Called when launching is completed. +/// #----------------------------------------------------------------------- +/// def did_launch(self): +/// pass +/// +/// #----------------------------------------------------------------------- +/// # Called once for each module that is loaded into the debug sessions. +/// # This allows clients to search to symbols or references to JIT'ed +/// # functions in each module as it gets loaded. Note that this function +/// # can be called prior to did_attach() or did_launch() being called as +/// # libraries get loaded during the attach or launch. +/// #----------------------------------------------------------------------- +/// def module_did_load(self, module: lldb.SBModule): +/// pass +/// + +namespace lldb_private { +class JITLoaderPythonInterface + : virtual public JITLoaderInterface, + virtual public ScriptedPythonInterface, + public PluginInterface { +public: + JITLoaderPythonInterface(ScriptInterpreterPythonImpl &interpreter); + + llvm::Expected + CreatePluginObject(llvm::StringRef class_name, + ExecutionContext &exe_ctx) override; + + + llvm::SmallVector + GetAbstractMethodRequirements() const override { + return llvm::SmallVector( + {{"did_attach"}, + {"did_launch"}, + {"module_did_load", 1}}); + } + + void DidAttach() override; + void DidLaunch() override; + void ModulesDidLoad(lldb_private::ModuleList &module_list) override; + + static void Initialize(); + + static void Terminate(); + + static llvm::StringRef GetPluginNameStatic() { + return "JITLoaderPythonInterface"; + } + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } +}; +} // namespace lldb_private + +#endif // LLDB_ENABLE_PYTHON +#endif // LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_JITLOADERPYTHONINTERFACE_H diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h index 26c80b7568691..9853299347c8a 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h @@ -15,6 +15,7 @@ #if LLDB_ENABLE_PYTHON +#include "JITLoaderPythonInterface.h" #include "OperatingSystemPythonInterface.h" #include "ScriptedPlatformPythonInterface.h" #include "ScriptedProcessPythonInterface.h" diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp index cf11c06e8a95d..6d2b95fd7b6ec 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp @@ -178,4 +178,22 @@ ScriptedPythonInterface::ExtractValueFromPythonObject< return m_interpreter.GetOpaqueTypeFromSBExecutionContext(*sb_exe_ctx); } +template <> +lldb::ModuleSP +ScriptedPythonInterface::ExtractValueFromPythonObject< + lldb::ModuleSP>(python::PythonObject &p, Status &error) { + + lldb::SBModule *sb_module = + reinterpret_cast( + python::LLDBSWIGPython_CastPyObjectToSBModule(p.get())); + + if (!sb_module) { + error = Status::FromErrorStringWithFormat( + "Couldn't cast lldb::SBModule to lldb::ModuleSP."); + return {}; + } + + return m_interpreter.GetOpaqueTypeFromSBModule(*sb_module); +} + #endif diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h index 4b9f463ef5605..5fde77f245b2f 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h @@ -460,6 +460,10 @@ class ScriptedPythonInterface : virtual public ScriptedInterface { return python::SWIGBridge::ToSWIGWrapper(arg); } + python::PythonObject Transform(lldb::ModuleSP arg) { + return python::SWIGBridge::ToSWIGWrapper(arg); + } + template void ReverseTransform(T &original_arg, U transformed_arg, Status &error) { // If U is not a PythonObject, don't touch it! @@ -589,6 +593,11 @@ lldb::ExecutionContextRefSP ScriptedPythonInterface::ExtractValueFromPythonObject< lldb::ExecutionContextRefSP>(python::PythonObject &p, Status &error); +template <> +lldb::ModuleSP +ScriptedPythonInterface::ExtractValueFromPythonObject< + lldb::ModuleSP>(python::PythonObject &p, Status &error); + } // namespace lldb_private #endif // LLDB_ENABLE_PYTHON diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h index 504b3aa0a4df1..6ccc85134173d 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h @@ -269,6 +269,7 @@ void *LLDBSWIGPython_CastPyObjectToSBAttachInfo(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBLaunchInfo(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBError(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBEvent(PyObject *data); +void *LLDBSWIGPython_CastPyObjectToSBModule(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBStream(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBValue(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 0c864dc85ce71..54093765f87db 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -1565,6 +1565,11 @@ ScriptInterpreterPythonImpl::CreateOperatingSystemInterface() { return std::make_shared(*this); } +JITLoaderInterfaceSP ScriptInterpreterPythonImpl::CreateJITLoaderInterface() { + return std::make_shared(*this); +} + + StructuredData::ObjectSP ScriptInterpreterPythonImpl::CreateStructuredDataFromScriptObject( ScriptObject obj) { diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h index 5d776080be8f4..8054a2a018738 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -112,6 +112,8 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython { lldb::OperatingSystemInterfaceSP CreateOperatingSystemInterface() override; + lldb::JITLoaderInterfaceSP CreateJITLoaderInterface() override; + StructuredData::ObjectSP LoadPluginModule(const FileSpec &file_spec, lldb_private::Status &error) override; diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index 13ff12b4ff953..624e9830e70f6 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -218,6 +218,12 @@ FileSpec ProcessProperties::GetPythonOSPluginPath() const { return GetPropertyAtIndexAs(idx, {}); } +FileSpec ProcessProperties::GetPythonJITLoaderPath() const { + const uint32_t idx = ePropertyPythonJITLoaderPath; + return GetPropertyAtIndexAs(idx, {}); +} + + uint32_t ProcessProperties::GetVirtualAddressableBits() const { const uint32_t idx = ePropertyVirtualAddressableBits; return GetPropertyAtIndexAs( diff --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td index 14fa33cc22c18..49230a1269aad 100644 --- a/lldb/source/Target/TargetProperties.td +++ b/lldb/source/Target/TargetProperties.td @@ -247,6 +247,9 @@ let Definition = "process" in { def PythonOSPluginPath: Property<"python-os-plugin-path", "FileSpec">, DefaultUnsignedValue<1>, Desc<"A path to a python OS plug-in module file that contains a OperatingSystemPlugIn class.">; + def PythonJITLoaderPath: Property<"python-jit-loader-path", "FileSpec">, + DefaultUnsignedValue<1>, + Desc<"A path to a python JIT loader plug-in module file that contains a JITLoaderPlugin class.">; def StopOnSharedLibraryEvents: Property<"stop-on-sharedlibrary-events", "Boolean">, Global, DefaultFalse, diff --git a/lldb/test/API/functionalities/jitloader_python/Makefile b/lldb/test/API/functionalities/jitloader_python/Makefile new file mode 100644 index 0000000000000..a05f32c46829d --- /dev/null +++ b/lldb/test/API/functionalities/jitloader_python/Makefile @@ -0,0 +1,10 @@ +CXX_SOURCES := main.cpp + +all: a.out jit.out + +include Makefile.rules + + +jit.out: + "$(MAKE)" -f $(MAKEFILE_RULES) \ + CXX_SOURCES=jit.cpp EXE=jit.out diff --git a/lldb/test/API/functionalities/jitloader_python/TestJITLoaderPython.py b/lldb/test/API/functionalities/jitloader_python/TestJITLoaderPython.py new file mode 100644 index 0000000000000..c492549398d73 --- /dev/null +++ b/lldb/test/API/functionalities/jitloader_python/TestJITLoaderPython.py @@ -0,0 +1,105 @@ +"""Test for the JITLoaderPython interface""" + +import os +import unittest + +import lldb +from lldbsuite.test import lldbutil +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +# Find an unmapped region large enough to contain the file specified by path. +def find_empty_memory_region_for_file(process, file_path): + file_size = os.path.getsize(file_path) + load_addr = 0 + while True: + region = lldb.SBMemoryRegionInfo() + error = process.GetMemoryRegionInfo(load_addr, region) + if error.Fail(): + return None + + region_base_addr = region.GetRegionBase() + region_end_addr = region.GetRegionEnd() + + # Abort on bad region + if region_base_addr >= region_end_addr: + return None + + load_addr = region_end_addr # In case we loop set the next load address + + # Skip regions that have any permissions, we are looking for an unmapped + # region to pretend to load our 'jit.out' file in. + if region.IsReadable() or region.IsWritable() or region.IsExecutable(): + continue + + if region_base_addr == 0: + # Don't try to map something at zero, add a small offset. + region_base_addr += 0x40000 + + if region_base_addr + file_size <= region_end_addr: + return region_base_addr + + if region_end_addr == lldb.LLDB_INVALID_ADDRESS: + return None + + +class JITLoaderPythonTestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def test_jit(self): + """Tests the python JITLoader interface.""" + self.build() + + python_jit_loader_path = self.getSourcePath("jit_loader.py") + # self.runCmd("log enable -f %s lldb jit" % (logfile)) + self.runCmd( + "settings set target.process.python-jit-loader-path '%s'" + % (python_jit_loader_path) + ) + + def cleanup(): + self.runCmd("settings clear target.process.python-jit-loader-path") + + self.addTearDownHook(cleanup) + + exe = self.getBuildArtifact("a.out") + main_source_spec = lldb.SBFileSpec("main.cpp", False) + # Launch the process. + (target, process, thread, bkpt1) = lldbutil.run_to_source_breakpoint( + self, "// Breakpoint 1", main_source_spec + ) + + jit_exe = self.getBuildArtifact("jit.out") + jit_exe_addr = find_empty_memory_region_for_file(process, jit_exe) + + frame = thread.GetFrameAtIndex(0) + # Update the JIT entry path to match our compile jit program so the + # python JIT loader can get the path to the "jit.out" program. + result = frame.EvaluateExpression(f'entry.path = "{jit_exe}"') + self.assertTrue(result.GetError().Success(), "failed to set the jit.out path") + jit_exe_addr = find_empty_memory_region_for_file(process, jit_exe) + if jit_exe_addr == None: + return # Couldn't find an empty memory range to load jit_exe + # Update the JIT entry address to the address in a region that is not + # mapped. + result = frame.EvaluateExpression(f"entry.address = {jit_exe_addr}") + self.assertTrue( + result.GetError().Success(), + f"failed to set the jit.out address {result.GetError()}", + ) + + lldbutil.continue_to_source_breakpoint( + self, process, "// Breakpoint 2", main_source_spec + ) + # The Python JIT loader should have added this module to the target + # by the time we hit the second breakpoint + jit_module = target.module["jit.out"] + self.assertTrue(jit_module.IsValid(), "jit.out module isn't in target") + + lldbutil.continue_to_source_breakpoint( + self, process, "// Breakpoint 3", main_source_spec + ) + + jit_module = target.module["jit.out"] + self.assertTrue(jit_module is None, "jit.out module wasn't removed from target") diff --git a/lldb/test/API/functionalities/jitloader_python/jit.cpp b/lldb/test/API/functionalities/jitloader_python/jit.cpp new file mode 100644 index 0000000000000..6a8ec50e6637e --- /dev/null +++ b/lldb/test/API/functionalities/jitloader_python/jit.cpp @@ -0,0 +1,44 @@ +#include + +// GDB JIT interface +enum JITAction { JIT_NOACTION, JIT_REGISTER_FN, JIT_UNREGISTER_FN }; + +struct JITCodeEntry +{ + struct JITCodeEntry* next; + struct JITCodeEntry* prev; + const char *symfile_addr; + uint64_t symfile_size; +}; + +struct JITDescriptor +{ + uint32_t version; + uint32_t action_flag; + struct JITCodeEntry* relevant_entry; + struct JITCodeEntry* first_entry; +}; + +struct JITDescriptor __jit_debug_descriptor = { 1, JIT_NOACTION, 0, 0 }; + +void __jit_debug_register_code() +{ +} +// end GDB JIT interface + +struct JITCodeEntry entry; + +int main() +{ + // Create a code entry with a bogus size + entry.next = entry.prev = 0; + entry.symfile_addr = (char *)&entry; + entry.symfile_size = (uint64_t)47<<32; + + __jit_debug_descriptor.relevant_entry = __jit_debug_descriptor.first_entry = &entry; + __jit_debug_descriptor.action_flag = JIT_REGISTER_FN; + + __jit_debug_register_code(); + + return 0; +} diff --git a/lldb/test/API/functionalities/jitloader_python/jit_loader.py b/lldb/test/API/functionalities/jitloader_python/jit_loader.py new file mode 100644 index 0000000000000..861228980d90c --- /dev/null +++ b/lldb/test/API/functionalities/jitloader_python/jit_loader.py @@ -0,0 +1,79 @@ +import lldb + +# Keep a global map of process ID to JITLoaderPlugin instances for the +# breakpoint callback function "jit_breakpoint_callback" below. +pid_to_jit_loader = {} + + +JIT_NOACTION = 0 +JIT_LOAD = 1 +JIT_UNLOAD = 2 + + +def jit_breakpoint_callback( + frame: lldb.SBFrame, + bp_loc: lldb.SBBreakpointLocation, + extra_args: lldb.SBStructuredData, + dict, +): + pid = frame.thread.process.GetProcessID() + jit_loader = pid_to_jit_loader[pid] + jit_loader.breakpoint_callback(frame, bp_loc, extra_args, dict) + return False + + +class JITLoaderPlugin: + def __init__(self, process: lldb.SBProcess): + global pid_to_jit_loader + self.process = process + self.bp = None + pid_to_jit_loader[process.GetProcessID()] = self + + def breakpoint_callback( + self, + frame: lldb.SBFrame, + bp_loc: lldb.SBBreakpointLocation, + extra_args: lldb.SBStructuredData, + dict, + ): + thread = frame.thread + process = thread.process + target = process.target + entry = frame.EvaluateExpression("entry") + if entry.GetError().Fail(): + return + action = frame.EvaluateExpression("action") + if action.GetError().Fail(): + return + action_value = action.unsigned + if action_value == JIT_NOACTION: + return + # Get path from summary, but trim off the double quotes + path = entry.member["path"].GetSummary()[1:-1] + if action_value == JIT_LOAD: + module_spec = lldb.SBModuleSpec() + module_spec.SetFileSpec(lldb.SBFileSpec(path, False)) + module = lldb.SBModule(module_spec) + if module.IsValid(): + target.AddModule(module) + load_addr = entry.member["address"].unsigned + target.SetModuleLoadAddress(module, load_addr) + elif action_value == JIT_UNLOAD: + module = target.module[path] + if module.IsValid(): + target.ClearModuleLoadAddress(module) + target.RemoveModule(module) + + def did_attach_or_launch(self): + self.bp = self.process.target.BreakpointCreateByName("jit_module_action") + quaklified_callback_name = f"{__name__}.jit_breakpoint_callback" + error = self.bp.SetScriptCallbackFunction(quaklified_callback_name) + + def did_attach(self): + self.did_attach_or_launch() + + def did_launch(self): + self.did_attach_or_launch() + + def module_did_load(self, module: lldb.SBModule): + pass diff --git a/lldb/test/API/functionalities/jitloader_python/main.cpp b/lldb/test/API/functionalities/jitloader_python/main.cpp new file mode 100644 index 0000000000000..3b1d5e22919a7 --- /dev/null +++ b/lldb/test/API/functionalities/jitloader_python/main.cpp @@ -0,0 +1,48 @@ +#include +#include + +enum JITAction { + JIT_NOACTION = 0, + JIT_LOAD = 1, + JIT_UNLOAD = 2 +}; + +struct JITEntry +{ + struct JITEntry* next = nullptr; + const char *path = nullptr; + uint64_t address = 0; +}; + + +JITEntry *g_entry_list = nullptr; + +void jit_module_action(JITEntry *entry, JITAction action) +{ + printf("entry = %p, action = %i\n", (void *)entry, action); +} +// end GDB JIT interface + + +int main() +{ + // Create an empty JITEntry. The test case will set the path and address + // with valid values at the first breakpoint. We build a "jit.out" binary + // in the python test and we will load it at a address, so the test case + // will calculate the path to the "jit.out" and the address to load it at + // and set the values with some expressions. + JITEntry entry; + + // Call the "jit_module_action" function to cause our JIT module to be + // added to our target and loaded at an address. + jit_module_action(&entry, JIT_LOAD); // Breakpoint 1 + printf("loaded module %s at %16.16" PRIx64 "\n", + entry.path, + entry.address); + // Call the "jit_module_action" function to cause our JIT module to be + // unloaded at an address. + jit_module_action(&entry, JIT_UNLOAD); // Breakpoint 2 + printf("unloaded module %s" PRIx64 "\n", // Breakpoint 3 + entry.path); + return 0; +} diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp index f7b5e3aeefe17..1ccd052f22f18 100644 --- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp +++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp @@ -337,6 +337,11 @@ lldb_private::python::SWIGBridge::ToSWIGWrapper(Event *event) { return python::PythonObject(); } +python::PythonObject +lldb_private::python::SWIGBridge::ToSWIGWrapper(lldb::ModuleSP) { + return python::PythonObject(); +} + python::PythonObject lldb_private::python::SWIGBridge::ToSWIGWrapper(const Stream *stream) { return python::PythonObject();