Index: lldb/bindings/python/CMakeLists.txt =================================================================== --- lldb/bindings/python/CMakeLists.txt +++ lldb/bindings/python/CMakeLists.txt @@ -104,7 +104,8 @@ "plugins" FILES "${LLDB_SOURCE_DIR}/examples/python/scripted_process/scripted_process.py" - "${LLDB_SOURCE_DIR}/examples/python/scripted_process/scripted_platform.py") + "${LLDB_SOURCE_DIR}/examples/python/scripted_process/scripted_platform.py" + "${LLDB_SOURCE_DIR}/examples/python/scripted_process/scripted_multiplexer.py") if(APPLE) create_python_package( Index: lldb/bindings/python/python-swigsafecast.swig =================================================================== --- lldb/bindings/python/python-swigsafecast.swig +++ lldb/bindings/python/python-swigsafecast.swig @@ -98,6 +98,11 @@ SWIGTYPE_p_lldb__SBLaunchInfo); } +PythonObject ToSWIGWrapper(lldb::EventSP event_sp) { + return ToSWIGHelper(new lldb::EventSP(std::move(event_sp)), + SWIGTYPE_p_lldb__SBEvent); +} + PythonObject ToSWIGWrapper(lldb::ProcessAttachInfoSP attach_info_sp) { return ToSWIGHelper(new lldb::ProcessAttachInfoSP(std::move(attach_info_sp)), SWIGTYPE_p_lldb__SBAttachInfo); Index: lldb/bindings/python/python-wrapper.swig =================================================================== --- lldb/bindings/python/python-wrapper.swig +++ lldb/bindings/python/python-wrapper.swig @@ -776,6 +776,18 @@ return sb_ptr; } +void *lldb_private::LLDBSWIGPython_CastPyObjectToSBEvent(PyObject * data) { + lldb::SBEvent *sb_ptr = nullptr; + + int valid_cast = + SWIG_ConvertPtr(data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBEvent, 0); + + if (valid_cast == -1) + return NULL; + + return sb_ptr; +} + void *lldb_private::LLDBSWIGPython_CastPyObjectToSBError(PyObject * data) { lldb::SBError *sb_ptr = nullptr; Index: lldb/examples/python/scripted_process/scripted_multiplexer.py =================================================================== --- /dev/null +++ lldb/examples/python/scripted_process/scripted_multiplexer.py @@ -0,0 +1,44 @@ +from abc import ABCMeta, abstractmethod + +import lldb + +class ScriptedMultiplexer(metaclass=ABCMeta): + + """ + The base class for a scripted multiplexer. + + Most of the base class methods are `@abstractmethod` that need to be + overwritten by the inheriting class. + + DISCLAIMER: THIS INTERFACE IS STILL UNDER DEVELOPMENT AND NOT STABLE. + THE METHODS EXPOSED MIGHT CHANGE IN THE FUTURE. + """ + + driving_process = None + + @abstractmethod + def __init__(self, exe_ctx, args): + """ Construct a scripted process. + + Args: + exe_ctx (lldb.SBExecutionContext): The execution context for the scripted process. + args (lldb.SBStructuredData): A Dictionary holding arbitrary + key/value pairs used by the scripted process. + """ + process = None + if isinstance(exe_ctx, lldb.SBExecutionContext): + process = exe_ctx.process + if process and process.IsValid(): + self.driving_process = process + + @abstractmethod + def filter_event(self, event : lldb.SBEvent) -> lldb.SBProcess: + """ Get a dictionary containing the process capabilities. + + Returns: + Dict[str:bool]: The dictionary of capability, with the capability + name as the key and a boolean flag as the value. + The dictionary can be empty. + """ + pass + Index: lldb/examples/python/scripted_process/scripted_process.py =================================================================== --- lldb/examples/python/scripted_process/scripted_process.py +++ lldb/examples/python/scripted_process/scripted_process.py @@ -224,6 +224,16 @@ """ return self.metadata + def get_scripted_multiplexer(self): + """ Get the scripted process multiplexer class name and driving target + index in a dictionary. + + Returns: + Dict: A dictionary containing scripted multiplexer information. + None if the process is not multiplexed. + """ + return None + class ScriptedThread(metaclass=ABCMeta): """ Index: lldb/include/lldb/API/SBEvent.h =================================================================== --- lldb/include/lldb/API/SBEvent.h +++ lldb/include/lldb/API/SBEvent.h @@ -14,6 +14,10 @@ #include #include +namespace lldb_private { +class ScriptInterpreter; +} + namespace lldb { class SBBroadcaster; @@ -69,6 +73,8 @@ friend class SBThread; friend class SBWatchpoint; + friend class lldb_private::ScriptInterpreter; + lldb::EventSP &GetSP() const; void reset(lldb::EventSP &event_sp); Index: lldb/include/lldb/Core/Debugger.h =================================================================== --- lldb/include/lldb/Core/Debugger.h +++ lldb/include/lldb/Core/Debugger.h @@ -24,6 +24,7 @@ #include "lldb/Host/HostThread.h" #include "lldb/Host/Terminal.h" #include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Multiplexer.h" #include "lldb/Target/Platform.h" #include "lldb/Target/TargetList.h" #include "lldb/Utility/Broadcaster.h" @@ -377,6 +378,9 @@ Target &GetSelectedOrDummyTarget(bool prefer_dummy = false); Target &GetDummyTarget() { return *m_dummy_target_sp; } + lldb::MultiplexerSP GetMultiplexer() const { return m_multiplexer_sp; } + void SetMultiplexer(lldb::MultiplexerSP mux_sp); + lldb::BroadcasterManagerSP GetBroadcasterManager() { return m_broadcaster_manager_sp; } @@ -589,6 +593,7 @@ lldb::ListenerSP m_forward_listener_sp; llvm::once_flag m_clear_once; lldb::TargetSP m_dummy_target_sp; + lldb::MultiplexerSP m_multiplexer_sp; // Events for m_sync_broadcaster enum { Index: lldb/include/lldb/Interpreter/ScriptInterpreter.h =================================================================== --- lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -13,6 +13,7 @@ #include "lldb/API/SBData.h" #include "lldb/API/SBDebugger.h" #include "lldb/API/SBError.h" +#include "lldb/API/SBEvent.h" #include "lldb/API/SBLaunchInfo.h" #include "lldb/API/SBMemoryRegionInfo.h" #include "lldb/API/SBProcess.h" @@ -23,6 +24,7 @@ #include "lldb/Core/StreamFile.h" #include "lldb/Core/ThreadedCommunication.h" #include "lldb/Host/PseudoTerminal.h" +#include "lldb/Interpreter/ScriptedMultiplexerInterface.h" #include "lldb/Interpreter/ScriptedPlatformInterface.h" #include "lldb/Interpreter/ScriptedProcessInterface.h" #include "lldb/Utility/Broadcaster.h" @@ -155,7 +157,9 @@ lldb::ScriptedProcessInterfaceUP scripted_process_interface_up = std::make_unique(), lldb::ScriptedPlatformInterfaceUP scripted_platform_interface_up = - std::make_unique()); + std::make_unique(), + lldb::ScriptedMultiplexerInterfaceUP scripted_multiplexer_interface_up = + std::make_unique()); virtual StructuredData::DictionarySP GetInterpreterInfo(); @@ -583,6 +587,10 @@ return *m_scripted_platform_interface_up; } + ScriptedMultiplexerInterface &GetScriptedMultiplexerInterface() { + return *m_scripted_multiplexer_interface_up; + } + lldb::DataExtractorSP GetDataExtractorFromSBData(const lldb::SBData &data) const; @@ -602,6 +610,8 @@ lldb::ProcessLaunchInfoSP GetOpaqueTypeFromSBLaunchInfo(const lldb::SBLaunchInfo &launch_info) const; + lldb::EventSP GetOpaqueTypeFromSBEvent(const lldb::SBEvent &event) const; + std::optional GetOpaqueTypeFromSBMemoryRegionInfo( const lldb::SBMemoryRegionInfo &mem_region) const; @@ -610,6 +620,7 @@ lldb::ScriptLanguage m_script_lang; lldb::ScriptedProcessInterfaceUP m_scripted_process_interface_up; lldb::ScriptedPlatformInterfaceUP m_scripted_platform_interface_up; + lldb::ScriptedMultiplexerInterfaceUP m_scripted_multiplexer_interface_up; }; } // namespace lldb_private Index: lldb/include/lldb/Interpreter/ScriptedMultiplexer.h =================================================================== --- /dev/null +++ lldb/include/lldb/Interpreter/ScriptedMultiplexer.h @@ -0,0 +1,48 @@ +//===-- ScriptedMultiplexer.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_SCRIPTEDMULTIPLEXER_H +#define LLDB_INTERPRETER_SCRIPTEDMULTIPLEXER_H + +// #include "lldb/Interpreter/ScriptedPlatformInterface.h" +#include "lldb/Target/Multiplexer.h" +#include "lldb/lldb-private.h" + +namespace lldb_private { + +class ScriptedMultiplexer : public Multiplexer { +public: + ScriptedMultiplexer(llvm::StringRef managing_class, Process &driving_process); + + ~ScriptedMultiplexer() override = default; + + llvm::StringRef GetName() const override { return m_managing_class; }; + lldb::ProcessSP FilterEvent(const lldb::EventSP event_sp) const override; + +protected: + std::string m_managing_class; + +private: + inline void CheckInterpreterAndScriptObject() const { + lldbassert(m_interpreter && "Invalid Script Interpreter."); + lldbassert(m_script_object_sp && "Invalid Script Object."); + } + + ScriptedMultiplexerInterface &GetInterface() const; + + static bool IsScriptLanguageSupported(lldb::ScriptLanguage language); + + Debugger &m_debugger; + lldb_private::ScriptInterpreter *m_interpreter = nullptr; + lldb_private::StructuredData::ObjectSP m_script_object_sp = nullptr; +}; + +} // namespace lldb_private + +#endif // LLDB_INTERPRETER_SCRIPTEDMULTIPLEXER_H Index: lldb/include/lldb/Interpreter/ScriptedMultiplexerInterface.h =================================================================== --- /dev/null +++ lldb/include/lldb/Interpreter/ScriptedMultiplexerInterface.h @@ -0,0 +1,31 @@ +//===-- ScriptedMultiplexerInterface.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_SCRIPTED_MUX_INTERFACE_H +#define LLDB_INTERPRETER_SCRIPTED_MUX_INTERFACE_H + +#include "lldb/Interpreter/ScriptedInterface.h" + +#include "lldb/lldb-private.h" + +namespace lldb_private { +class ScriptedMultiplexerInterface : virtual public ScriptedInterface { +public: + StructuredData::GenericSP + CreatePluginObject(llvm::StringRef class_name, ExecutionContext &exe_ctx, + StructuredData::DictionarySP args_sp, + StructuredData::Generic *script_obj = nullptr) override { + return {}; + } + + virtual lldb::ProcessSP FilterEvent(lldb::EventSP event_sp) { return {}; } +}; +} // namespace lldb_private + +#endif // LLDB_INTERPRETER_SCRIPTED_MUX_INTERFACE_H Index: lldb/include/lldb/Interpreter/ScriptedProcessInterface.h =================================================================== --- lldb/include/lldb/Interpreter/ScriptedProcessInterface.h +++ lldb/include/lldb/Interpreter/ScriptedProcessInterface.h @@ -67,11 +67,13 @@ virtual bool IsAlive() { return true; } + virtual StructuredData::DictionarySP GetMetadata() { return {}; } + virtual std::optional GetScriptedThreadPluginName() { return std::nullopt; } - virtual StructuredData::DictionarySP GetMetadata() { return {}; } + virtual StructuredData::DictionarySP GetScriptedMultiplexer() { return {}; } protected: friend class ScriptedThread; Index: lldb/include/lldb/Target/Multiplexer.h =================================================================== --- /dev/null +++ lldb/include/lldb/Target/Multiplexer.h @@ -0,0 +1,53 @@ +//===-- Multiplexer.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_TARGET_MULTIPLEXER_H +#define LLDB_TARGET_MULTIPLEXER_H + +#include + +#include "lldb/Interpreter/ScriptedProcessInterface.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/Listener.h" +#include "lldb/lldb-public.h" + +namespace lldb_private { + +/// \class Multiplexer Multiplexer.h "lldb/Target/Multiplexer.h" +/// A plug-in interface definition class for debug multiplexer that +/// includes many multiplexer abilities such as: +/// \li getting multiplexer information such as supported architectures, +/// supported binary file formats and more +/// \li launching new processes +/// \li attaching to existing processes +/// \li download/upload files +/// \li execute shell commands +/// \li listing and getting info for existing processes +/// \li attaching and possibly debugging the multiplexer's kernel +class Multiplexer { +public: + /// Default Constructor + Multiplexer(Process &driving_process); + virtual ~Multiplexer() = default; + + bool AddProcess(lldb::ProcessSP process_sp); + bool DropProcess(lldb::pid_t pid); + + virtual llvm::StringRef GetName() const = 0; + virtual lldb::ProcessSP FilterEvent(const lldb::EventSP event_sp) const = 0; + +protected: + lldb::ProcessSP m_driving_process_sp = nullptr; + lldb::ListenerSP m_listener_sp = nullptr; + std::map m_connected_processes; +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_MULTIPLEXER_H Index: lldb/include/lldb/lldb-forward.h =================================================================== --- lldb/include/lldb/lldb-forward.h +++ lldb/include/lldb/lldb-forward.h @@ -128,6 +128,7 @@ class ModuleList; class ModuleSpec; class ModuleSpecList; +class Multiplexer; class ObjectContainer; class ObjectFile; class ObjectFileJITDelegate; @@ -181,6 +182,7 @@ class Scalar; class ScriptInterpreter; class ScriptInterpreterLocker; +class ScriptedMultiplexerInterface; class ScriptedPlatformInterface; class ScriptedProcessInterface; class ScriptedThreadInterface; @@ -352,6 +354,7 @@ typedef std::shared_ptr MemoryRegionInfoSP; typedef std::shared_ptr ModuleSP; typedef std::weak_ptr ModuleWP; +typedef std::shared_ptr MultiplexerSP; typedef std::shared_ptr ObjectFileSP; typedef std::shared_ptr ObjectContainerSP; typedef std::shared_ptr @@ -380,6 +383,8 @@ typedef std::shared_ptr ScriptSummaryFormatSP; typedef std::shared_ptr ScriptInterpreterSP; +typedef std::unique_ptr + ScriptedMultiplexerInterfaceUP; typedef std::unique_ptr ScriptedPlatformInterfaceUP; typedef std::unique_ptr Index: lldb/source/Core/Debugger.cpp =================================================================== --- lldb/source/Core/Debugger.cpp +++ lldb/source/Core/Debugger.cpp @@ -866,6 +866,7 @@ GetInputFile().Close(); m_command_interpreter_up->Clear(); + m_multiplexer_sp.reset(); }); } @@ -878,6 +879,14 @@ // m_input_comm.SetCloseOnEOF(b); } +void Debugger::SetMultiplexer(lldb::MultiplexerSP mux_sp) { + if (m_multiplexer_sp) + ReportWarning(llvm::formatv("Replacing debugger multiplex {0} by {1}", + m_multiplexer_sp->GetName(), + mux_sp->GetName())); + m_multiplexer_sp = mux_sp; +} + bool Debugger::GetAsyncExecution() { return !m_command_interpreter_up->GetSynchronous(); } Index: lldb/source/Interpreter/CMakeLists.txt =================================================================== --- lldb/source/Interpreter/CMakeLists.txt +++ lldb/source/Interpreter/CMakeLists.txt @@ -52,6 +52,7 @@ Options.cpp Property.cpp ScriptInterpreter.cpp + ScriptedMultiplexer.cpp LINK_LIBS lldbCommands Index: lldb/source/Interpreter/ScriptInterpreter.cpp =================================================================== --- lldb/source/Interpreter/ScriptInterpreter.cpp +++ lldb/source/Interpreter/ScriptInterpreter.cpp @@ -30,11 +30,14 @@ ScriptInterpreter::ScriptInterpreter( Debugger &debugger, lldb::ScriptLanguage script_lang, lldb::ScriptedProcessInterfaceUP scripted_process_interface_up, - lldb::ScriptedPlatformInterfaceUP scripted_platform_interface_up) + lldb::ScriptedPlatformInterfaceUP scripted_platform_interface_up, + lldb::ScriptedMultiplexerInterfaceUP scripted_multiplexer_interface_up) : m_debugger(debugger), m_script_lang(script_lang), m_scripted_process_interface_up(std::move(scripted_process_interface_up)), m_scripted_platform_interface_up( - std::move(scripted_platform_interface_up)) {} + std::move(scripted_platform_interface_up)), + m_scripted_multiplexer_interface_up( + std::move(scripted_multiplexer_interface_up)) {} void ScriptInterpreter::CollectDataForBreakpointCommandCallback( std::vector> &bp_options_vec, @@ -108,6 +111,11 @@ *reinterpret_cast(launch_info.m_opaque_sp.get())); } +lldb::EventSP +ScriptInterpreter::GetOpaqueTypeFromSBEvent(const lldb::SBEvent &event) const { + return event.m_event_sp; +} + Status ScriptInterpreter::GetStatusFromSBError(const lldb::SBError &error) const { if (error.m_opaque_up) Index: lldb/source/Interpreter/ScriptedMultiplexer.cpp =================================================================== --- /dev/null +++ lldb/source/Interpreter/ScriptedMultiplexer.cpp @@ -0,0 +1,53 @@ +//===-- ScriptedMultiplexer.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/Interpreter/ScriptedMultiplexer.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Interpreter/ScriptInterpreter.h" + +using namespace lldb; +using namespace lldb_private; + +ScriptedMultiplexer::ScriptedMultiplexer(llvm::StringRef managing_class, + Process &driving_process) + : Multiplexer(driving_process), m_managing_class(managing_class.str()), + m_debugger(driving_process.GetTarget().GetDebugger()) { + + Status error; + m_interpreter = m_debugger.GetScriptInterpreter(); + + if (!m_interpreter) { + ScriptedInterface::ErrorWithMessage( + LLVM_PRETTY_FUNCTION, "Debugger has no Script Interpreter", error); + return; + } + + ExecutionContext exe_ctx(driving_process.shared_from_this()); + + StructuredData::GenericSP object_sp = + GetInterface().CreatePluginObject(managing_class, exe_ctx, nullptr); + + if (!object_sp || !object_sp->IsValid()) { + error.SetErrorStringWithFormat("ScriptedProcess::%s () - ERROR: %s", + __FUNCTION__, + "Failed to create valid script object"); + return; + } + + m_script_object_sp = object_sp; +} + +ScriptedMultiplexerInterface &ScriptedMultiplexer::GetInterface() const { + return m_interpreter->GetScriptedMultiplexerInterface(); +} + +lldb::ProcessSP +ScriptedMultiplexer::FilterEvent(const lldb::EventSP event_sp) const { + // FIXME: Call into ScriptedMultiplexerInterface + return m_driving_process_sp; +} Index: lldb/source/Plugins/Process/scripted/ScriptedProcess.h =================================================================== --- lldb/source/Plugins/Process/scripted/ScriptedProcess.h +++ lldb/source/Plugins/Process/scripted/ScriptedProcess.h @@ -97,6 +97,8 @@ Status DoGetMemoryRegionInfo(lldb::addr_t load_addr, MemoryRegionInfo &range_info) override; + bool Multiplex(StructuredData::DictionarySP dict_sp, Status &error); + Status DoAttach(); private: Index: lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp =================================================================== --- lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp +++ lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp @@ -19,6 +19,7 @@ #include "lldb/Interpreter/OptionGroupBoolean.h" #include "lldb/Interpreter/ScriptInterpreter.h" #include "lldb/Interpreter/ScriptedMetadata.h" +#include "lldb/Interpreter/ScriptedMultiplexer.h" #include "lldb/Symbol/LocateSymbolFile.h" #include "lldb/Target/MemoryRegionInfo.h" #include "lldb/Target/Queue.h" @@ -113,6 +114,10 @@ } m_script_object_sp = object_sp; + + // Setup multiplexer + if (auto dict_sp = GetInterface().GetScriptedMultiplexer()) + Multiplex(dict_sp, error); } ScriptedProcess::~ScriptedProcess() { @@ -137,6 +142,45 @@ PluginManager::UnregisterPlugin(ScriptedProcess::CreateInstance); } +bool ScriptedProcess::Multiplex(StructuredData::DictionarySP dict_sp, + Status &error) { + // sanitize dictionary + llvm::StringRef managing_class; + if (!dict_sp->GetValueForKeyAsString("managing_class", managing_class)) + return ScriptedInterface::ErrorWithMessage( + LLVM_PRETTY_FUNCTION, + "Missing key 'managing_class' in Scripted Multiplexer dictionary", + error); + uint32_t driving_target_index = UINT32_MAX; + if (!dict_sp->GetValueForKeyAsInteger("driving_target_idx", + driving_target_index)) + return ScriptedInterface::ErrorWithMessage( + LLVM_PRETTY_FUNCTION, + "Missing key 'driving_target_idx' in Scripted Multiplexer dictionary", + error); + + Debugger &debugger = GetTarget().GetDebugger(); + TargetSP target_sp = + debugger.GetTargetList().GetTargetAtIndex(driving_target_index); + if (!target_sp || !target_sp->IsValid()) + return ScriptedInterface::ErrorWithMessage( + LLVM_PRETTY_FUNCTION, "Provided driving target is invalid.", error); + ProcessSP driving_process_sp = target_sp->GetProcessSP(); + if (!driving_process_sp || !driving_process_sp->IsValid()) + return ScriptedInterface::ErrorWithMessage( + LLVM_PRETTY_FUNCTION, "Provided driving target has invalid process.", + error); + + if (!debugger.GetMultiplexer()) { + debugger.SetMultiplexer(lldb::MultiplexerSP( + new ScriptedMultiplexer(managing_class, *driving_process_sp))); + } + + debugger.GetMultiplexer()->AddProcess(shared_from_this()); + + return error.Success(); +} + Status ScriptedProcess::DoLoadCore() { ProcessLaunchInfo launch_info = GetTarget().GetProcessLaunchInfo(); Index: lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt +++ lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt @@ -18,6 +18,7 @@ PythonReadline.cpp ScriptInterpreterPython.cpp ScriptedPythonInterface.cpp + ScriptedMultiplexerPythonInterface.cpp ScriptedProcessPythonInterface.cpp ScriptedThreadPythonInterface.cpp ScriptedPlatformPythonInterface.cpp Index: lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h +++ lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h @@ -78,6 +78,7 @@ PythonObject ToSWIGWrapper(const SymbolContext &sym_ctx); PythonObject ToSWIGWrapper(lldb::ProcessAttachInfoSP attach_info_sp); PythonObject ToSWIGWrapper(lldb::ProcessLaunchInfoSP launch_info_sp); +PythonObject ToSWIGWrapper(lldb::EventSP event_sp); PythonObject ToSWIGWrapper(std::unique_ptr value_sb); PythonObject ToSWIGWrapper(std::unique_ptr stream_sb); PythonObject ToSWIGWrapper(std::unique_ptr data_sb); @@ -94,6 +95,7 @@ void *LLDBSWIGPython_CastPyObjectToSBTarget(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBAttachInfo(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBLaunchInfo(PyObject *data); +void *LLDBSWIGPython_CastPyObjectToSBEvent(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBError(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBValue(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBMemoryRegionInfo(PyObject *data); Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -18,6 +18,7 @@ #include "PythonReadline.h" #include "SWIGPythonBridge.h" #include "ScriptInterpreterPythonImpl.h" +#include "ScriptedMultiplexerPythonInterface.h" #include "ScriptedPlatformPythonInterface.h" #include "ScriptedProcessPythonInterface.h" @@ -416,6 +417,8 @@ std::make_unique(*this); m_scripted_platform_interface_up = std::make_unique(*this); + m_scripted_multiplexer_interface_up = + std::make_unique(*this); m_dictionary_name.append("_dict"); StreamString run_string; Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptedMultiplexerPythonInterface.h =================================================================== --- /dev/null +++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptedMultiplexerPythonInterface.h @@ -0,0 +1,37 @@ +//===-- ScriptedMultiplexerPythonInterface.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_SCRIPTED_MUX_PYTHON_INTERFACE_H +#define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_SCRIPTED_MUX_PYTHON_INTERFACE_H + +#include "lldb/Host/Config.h" + +#if LLDB_ENABLE_PYTHON + +#include "ScriptedPythonInterface.h" +#include "lldb/Interpreter/ScriptedMultiplexerInterface.h" +#include + +namespace lldb_private { +class ScriptedMultiplexerPythonInterface : public ScriptedMultiplexerInterface, + public ScriptedPythonInterface { +public: + ScriptedMultiplexerPythonInterface(ScriptInterpreterPythonImpl &interpreter); + + StructuredData::GenericSP + CreatePluginObject(llvm::StringRef class_name, ExecutionContext &exe_ctx, + StructuredData::DictionarySP args_sp, + StructuredData::Generic *script_obj = nullptr) override; + + lldb::ProcessSP FilterEvent(lldb::EventSP event_sp) override; +}; +} // namespace lldb_private + +#endif // LLDB_ENABLE_PYTHON +#endif // LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_SCRIPTED_MUX_PYTHON_INTERFACE_H Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptedMultiplexerPythonInterface.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptedMultiplexerPythonInterface.cpp @@ -0,0 +1,67 @@ +//===-- ScriptedMultiplexerPythonInterface.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/Utility/Status.h" +#include "lldb/lldb-enumerations.h" + +#if LLDB_ENABLE_PYTHON + +// LLDB Python header must be included first +#include "lldb-python.h" + +#include "SWIGPythonBridge.h" +#include "ScriptInterpreterPythonImpl.h" +#include "ScriptedMultiplexerPythonInterface.h" + +#include + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::python; + +using Locker = ScriptInterpreterPythonImpl::Locker; + +ScriptedMultiplexerPythonInterface::ScriptedMultiplexerPythonInterface( + ScriptInterpreterPythonImpl &interpreter) + : ScriptedMultiplexerInterface(), ScriptedPythonInterface(interpreter) {} + +StructuredData::GenericSP +ScriptedMultiplexerPythonInterface::CreatePluginObject( + llvm::StringRef class_name, ExecutionContext &exe_ctx, + StructuredData::DictionarySP args_sp, StructuredData::Generic *script_obj) { + if (class_name.empty()) + return {}; + + StructuredDataImpl args_impl; + std::string error_string; + + Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN, + Locker::FreeLock); + + lldb::ExecutionContextRefSP exe_ctx_ref_sp = + std::make_shared(exe_ctx); + + PythonObject ret_val = LLDBSwigPythonCreateScriptedObject( + class_name.str().c_str(), m_interpreter.GetDictionaryName(), + exe_ctx_ref_sp, args_impl, error_string); + + m_object_instance_sp = + StructuredData::GenericSP(new StructuredPythonObject(std::move(ret_val))); + + return m_object_instance_sp; +} + +lldb::ProcessSP +ScriptedMultiplexerPythonInterface::FilterEvent(lldb::EventSP event_sp) { + Status error; + return Dispatch("filter_event", error, event_sp); +} + +#endif Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h +++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h @@ -60,9 +60,11 @@ bool IsAlive() override; + StructuredData::DictionarySP GetMetadata() override; + std::optional GetScriptedThreadPluginName() override; - StructuredData::DictionarySP GetMetadata() override; + StructuredData::DictionarySP GetScriptedMultiplexer() override; private: lldb::ScriptedThreadInterfaceSP CreateScriptedThreadInterface() override; Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp +++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp @@ -208,4 +208,16 @@ return dict; } +StructuredData::DictionarySP +ScriptedProcessPythonInterface::GetScriptedMultiplexer() { + Status error; + StructuredData::DictionarySP dict = + Dispatch("get_scripted_multiplexer", error); + + if (!CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, dict, error)) + return {}; + + return dict; +} + #endif Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.h =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.h +++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.h @@ -133,6 +133,10 @@ return python::ToSWIGWrapper(arg); } + python::PythonObject Transform(lldb::EventSP arg) { + return python::ToSWIGWrapper(arg); + } + template void ReverseTransform(T &original_arg, U transformed_arg, Status &error) { // If U is not a PythonObject, don't touch it! @@ -237,6 +241,11 @@ lldb::ProcessLaunchInfoSP ScriptedPythonInterface::ExtractValueFromPythonObject< lldb::ProcessLaunchInfoSP>(python::PythonObject &p, Status &error); +template <> +lldb::EventSP +ScriptedPythonInterface::ExtractValueFromPythonObject( + python::PythonObject &p, Status &error); + template <> lldb::DataExtractorSP ScriptedPythonInterface::ExtractValueFromPythonObject( Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.cpp =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.cpp +++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.cpp @@ -146,6 +146,22 @@ return m_interpreter.GetOpaqueTypeFromSBLaunchInfo(*sb_launch_info); } +template <> +lldb::EventSP +ScriptedPythonInterface::ExtractValueFromPythonObject( + python::PythonObject &p, Status &error) { + lldb::SBEvent *sb_event = reinterpret_cast( + LLDBSWIGPython_CastPyObjectToSBLaunchInfo(p.get())); + + if (!sb_event) { + error.SetErrorString( + "Couldn't cast lldb::SBLaunchInfo to lldb::ProcessLaunchInfoSP."); + return nullptr; + } + + return m_interpreter.GetOpaqueTypeFromSBEvent(*sb_event); +} + template <> std::optional ScriptedPythonInterface::ExtractValueFromPythonObject< Index: lldb/source/Target/CMakeLists.txt =================================================================== --- lldb/source/Target/CMakeLists.txt +++ lldb/source/Target/CMakeLists.txt @@ -21,6 +21,7 @@ MemoryHistory.cpp MemoryRegionInfo.cpp MemoryTagMap.cpp + Multiplexer.cpp ModuleCache.cpp OperatingSystem.cpp PathMappingList.cpp Index: lldb/source/Target/Multiplexer.cpp =================================================================== --- /dev/null +++ lldb/source/Target/Multiplexer.cpp @@ -0,0 +1,31 @@ +//===-- Multiplexer.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/Target/Multiplexer.h" + +using namespace lldb; +using namespace lldb_private; + +Multiplexer::Multiplexer(Process &driving_process) + : m_driving_process_sp(driving_process.shared_from_this()), + m_listener_sp(Listener::MakeListener("lldb.ScriptedPlatform")) { + m_driving_process_sp->HijackBroadcaster(m_listener_sp); +} + +bool Multiplexer::AddProcess(lldb::ProcessSP process_sp) { + if (!process_sp) + return false; + m_connected_processes[process_sp->GetID()] = process_sp; + return true; +} + +bool Multiplexer::DropProcess(lldb::pid_t pid) { + if (pid == LLDB_INVALID_PROCESS_ID) + return false; + return static_cast(m_connected_processes.erase(pid)); +} Index: lldb/test/API/functionalities/scripted_multiplexer/Makefile =================================================================== --- /dev/null +++ lldb/test/API/functionalities/scripted_multiplexer/Makefile @@ -0,0 +1,8 @@ +CXX_SOURCES := main.cpp +ENABLE_THREADS := YES + +override ARCH := $(shell uname -m) + +all: a.out + +include Makefile.rules Index: lldb/test/API/functionalities/scripted_multiplexer/TestScriptedMultiplexer.py =================================================================== --- /dev/null +++ lldb/test/API/functionalities/scripted_multiplexer/TestScriptedMultiplexer.py @@ -0,0 +1,152 @@ +""" +Test python scripted process in lldb +""" + +import os, shutil + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from lldbsuite.test import lldbtest + +class ScriptedProcesTestCase(TestBase): + + NO_DEBUG_INFO_TESTCASE = True + + @skipUnlessDarwin + def test_python_plugin_package(self): + """Test that the lldb python module has a `plugins.scripted_process` + package.""" + self.expect('script import lldb.plugins', + substrs=["ModuleNotFoundError"], matching=False) + + self.expect('script dir(lldb.plugins)', + substrs=["scripted_process"]) + + self.expect('script import lldb.plugins.scripted_process', + substrs=["ModuleNotFoundError"], matching=False) + + self.expect('script dir(lldb.plugins.scripted_process)', + substrs=["ScriptedProcess"]) + + self.expect('script from lldb.plugins.scripted_process import ScriptedProcess', + substrs=["ImportError"], matching=False) + + self.expect('script dir(ScriptedProcess)', + substrs=["launch"]) + + def move_blueprint_to_dsym(self, blueprint_name): + blueprint_origin_path = os.path.join(self.getSourceDir(), blueprint_name) + dsym_bundle = self.getBuildArtifact("a.out.dSYM") + blueprint_destination_path = os.path.join(dsym_bundle, "Contents", + "Resources", "Python") + if not os.path.exists(blueprint_destination_path): + os.mkdir(blueprint_destination_path) + + blueprint_destination_path = os.path.join(blueprint_destination_path, "a_out.py") + shutil.copy(blueprint_origin_path, blueprint_destination_path) + + @skipUnlessDarwin + def test_invalid_scripted_register_context(self): + """Test that we can launch an lldb scripted process with an invalid + Scripted Thread, with invalid register context.""" + self.build() + + os.environ['SKIP_SCRIPTED_PROCESS_LAUNCH'] = '1' + def cleanup(): + del os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"] + self.addTearDownHook(cleanup) + + self.runCmd("settings set target.load-script-from-symbol-file true") + self.move_blueprint_to_dsym('invalid_scripted_process.py') + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(target, VALID_TARGET) + log_file = self.getBuildArtifact('thread.log') + self.runCmd("log enable lldb thread -f " + log_file) + self.assertTrue(os.path.isfile(log_file)) + + launch_info = lldb.SBLaunchInfo(None) + launch_info.SetProcessPluginName("ScriptedProcess") + launch_info.SetScriptedProcessClassName("a_out.InvalidScriptedProcess") + error = lldb.SBError() + + process = target.Launch(launch_info, error) + + self.assertSuccess(error) + self.assertTrue(process, PROCESS_IS_VALID) + self.assertEqual(process.GetProcessID(), 666) + self.assertEqual(process.GetNumThreads(), 0) + + addr = 0x500000000 + buff = process.ReadMemory(addr, 4, error) + self.assertEqual(buff, None) + self.assertTrue(error.Fail()) + self.assertEqual(error.GetCString(), "This is an invalid scripted process!") + + with open(log_file, 'r') as f: + log = f.read() + + self.assertIn("Failed to get scripted thread registers data.", log) + + @skipUnlessDarwin + def test_scripted_process_and_scripted_thread(self): + """Test that we can launch an lldb scripted process using the SBAPI, + check its process ID, read string from memory, check scripted thread + id, name stop reason and register context. + """ + self.build() + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(target, VALID_TARGET) + + os.environ['SKIP_SCRIPTED_PROCESS_LAUNCH'] = '1' + def cleanup(): + del os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"] + self.addTearDownHook(cleanup) + + scripted_process_example_relpath = 'dummy_scripted_process.py' + self.runCmd("command script import " + os.path.join(self.getSourceDir(), + scripted_process_example_relpath)) + + launch_info = lldb.SBLaunchInfo(None) + launch_info.SetProcessPluginName("ScriptedProcess") + launch_info.SetScriptedProcessClassName("dummy_scripted_process.DummyScriptedProcess") + + error = lldb.SBError() + process = target.Launch(launch_info, error) + self.assertTrue(process and process.IsValid(), PROCESS_IS_VALID) + self.assertEqual(process.GetProcessID(), 42) + self.assertEqual(process.GetNumThreads(), 1) + + addr = 0x500000000 + message = "Hello, world!" + buff = process.ReadCStringFromMemory(addr, len(message) + 1, error) + self.assertSuccess(error) + self.assertEqual(buff, message) + + thread = process.GetSelectedThread() + self.assertTrue(thread, "Invalid thread.") + self.assertEqual(thread.GetThreadID(), 0x19) + self.assertEqual(thread.GetName(), "DummyScriptedThread.thread-1") + self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonSignal) + + self.assertGreater(thread.GetNumFrames(), 0) + + frame = thread.GetFrameAtIndex(0) + GPRs = None + register_set = frame.registers # Returns an SBValueList. + for regs in register_set: + if 'general purpose' in regs.name.lower(): + GPRs = regs + break + + self.assertTrue(GPRs, "Invalid General Purpose Registers Set") + self.assertGreater(GPRs.GetNumChildren(), 0) + for idx, reg in enumerate(GPRs, start=1): + if idx > 21: + break + self.assertEqual(idx, int(reg.value, 16)) + + self.assertTrue(frame.IsArtificial(), "Frame is not artificial") + pc = frame.GetPCAddress().GetLoadAddress(target) + self.assertEqual(pc, 0x0100001b00) Index: lldb/test/API/functionalities/scripted_multiplexer/main.cpp =================================================================== --- /dev/null +++ lldb/test/API/functionalities/scripted_multiplexer/main.cpp @@ -0,0 +1,26 @@ +#include +#include +#include + +enum class Parity { + Even, + Odd, +}; + +void even_or_odd(size_t thread_id) { + while (true) { + // break here + Parity parity = static_cast(thread_id % 2); + } +} + +int main() { + std::vector threads; + for (size_t i = 0; i < 10; i++) + threads.push_back(std::thread(even_or_odd, i)); + + for (std::thread &thread : threads) + thread.join(); + + return 0; +} Index: lldb/test/API/functionalities/scripted_multiplexer/multiplexed_scripted_process.py =================================================================== --- /dev/null +++ lldb/test/API/functionalities/scripted_multiplexer/multiplexed_scripted_process.py @@ -0,0 +1,186 @@ +import os,json,struct,signal + +from typing import Any, Dict + +import lldb +from lldb.plugins.scripted_process import ScriptedProcess +from lldb.plugins.scripted_process import ScriptedThread + +class StackCoreScriptedProcess(ScriptedProcess): + def get_module_with_name(self, target, name): + for module in target.modules: + if name in module.GetFileSpec().GetFilename(): + return module + return None + + def __init__(self, exe_ctx: lldb.SBExecutionContext, args : lldb.SBStructuredData): + super().__init__(exe_ctx, args) + + self.corefile_target = None + self.corefile_process = None + + self.backing_target_idx = args.GetValueForKey("backing_target_idx") + if (self.backing_target_idx and self.backing_target_idx.IsValid()): + if self.backing_target_idx.GetType() == lldb.eStructuredDataTypeInteger: + idx = self.backing_target_idx.GetIntegerValue(42) + if self.backing_target_idx.GetType() == lldb.eStructuredDataTypeString: + idx = int(self.backing_target_idx.GetStringValue(100)) + self.corefile_target = self.target.GetDebugger().GetTargetAtIndex(idx) + self.corefile_process = self.corefile_target.GetProcess() + for corefile_thread in self.corefile_process: + structured_data = lldb.SBStructuredData() + structured_data.SetFromJSON(json.dumps({ + "backing_target_idx" : idx, + "thread_idx" : corefile_thread.GetIndexID() + })) + + self.threads[corefile_thread.GetThreadID()] = StackCoreScriptedThread(self, structured_data) + + if len(self.threads) == 2: + self.threads[len(self.threads) - 1].is_stopped = True + + corefile_module = self.get_module_with_name(self.corefile_target, + "libbaz.dylib") + if not corefile_module or not corefile_module.IsValid(): + return + module_path = os.path.join(corefile_module.GetFileSpec().GetDirectory(), + corefile_module.GetFileSpec().GetFilename()) + if not os.path.exists(module_path): + return + module_load_addr = corefile_module.GetObjectFileHeaderAddress().GetLoadAddress(self.corefile_target) + + self.loaded_images.append({"path": module_path, + "load_addr": module_load_addr}) + + def get_memory_region_containing_address(self, addr: int) -> lldb.SBMemoryRegionInfo: + mem_region = lldb.SBMemoryRegionInfo() + error = self.corefile_process.GetMemoryRegionInfo(addr, mem_region) + if error.Fail(): + return None + return mem_region + + def get_thread_with_id(self, tid: int): + return {} + + def get_registers_for_thread(self, tid: int): + return {} + + def read_memory_at_address(self, addr: int, size: int, error: lldb.SBError) -> lldb.SBData: + data = lldb.SBData() + bytes_read = self.corefile_process.ReadMemory(addr, size, error) + + if error.Fail(): + return data + + data.SetDataWithOwnership(error, bytes_read, + self.corefile_target.GetByteOrder(), + self.corefile_target.GetAddressByteSize()) + + return data + + def get_loaded_images(self): + return self.loaded_images + + def get_process_id(self) -> int: + return 42 + + def should_stop(self) -> bool: + return True + + def is_alive(self) -> bool: + return True + + def get_scripted_thread_plugin(self): + return StackCoreScriptedThread.__module__ + "." + StackCoreScriptedThread.__name__ + + +class StackCoreScriptedThread(ScriptedThread): + def __init__(self, process, args): + super().__init__(process, args) + backing_target_idx = args.GetValueForKey("backing_target_idx") + thread_idx = args.GetValueForKey("thread_idx") + self.is_stopped = False + + def extract_value_from_structured_data(data, default_val): + if data and data.IsValid(): + if data.GetType() == lldb.eStructuredDataTypeInteger: + return data.GetIntegerValue(default_val) + if data.GetType() == lldb.eStructuredDataTypeString: + return int(data.GetStringValue(100)) + return None + + #TODO: Change to Walrus operator (:=) with oneline if assignment + # Requires python 3.8 + val = extract_value_from_structured_data(thread_idx, 0) + if val is not None: + self.idx = val + + self.corefile_target = None + self.corefile_process = None + self.corefile_thread = None + + #TODO: Change to Walrus operator (:=) with oneline if assignment + # Requires python 3.8 + val = extract_value_from_structured_data(backing_target_idx, 42) + if val is not None: + self.corefile_target = self.target.GetDebugger().GetTargetAtIndex(val) + self.corefile_process = self.corefile_target.GetProcess() + self.corefile_thread = self.corefile_process.GetThreadByIndexID(self.idx) + + if self.corefile_thread: + self.id = self.corefile_thread.GetThreadID() + + def get_thread_id(self) -> int: + return self.id + + def get_name(self) -> str: + return StackCoreScriptedThread.__name__ + ".thread-" + str(self.id) + + def get_stop_reason(self) -> Dict[str, Any]: + stop_reason = { "type": lldb.eStopReasonInvalid, "data": { }} + + if self.corefile_thread and self.corefile_thread.IsValid() \ + and self.get_thread_id() == self.corefile_thread.GetThreadID(): + stop_reason["type"] = lldb.eStopReasonNone + + if self.is_stopped: + if 'arm64' in self.scripted_process.arch: + stop_reason["type"] = lldb.eStopReasonException + stop_reason["data"]["desc"] = self.corefile_thread.GetStopDescription(100) + elif self.scripted_process.arch == 'x86_64': + stop_reason["type"] = lldb.eStopReasonSignal + stop_reason["data"]["signal"] = signal.SIGTRAP + else: + stop_reason["type"] = self.corefile_thread.GetStopReason() + + return stop_reason + + def get_register_context(self) -> str: + if not self.corefile_thread or self.corefile_thread.GetNumFrames() == 0: + return None + frame = self.corefile_thread.GetFrameAtIndex(0) + + GPRs = None + registerSet = frame.registers # Returns an SBValueList. + for regs in registerSet: + if 'general purpose' in regs.name.lower(): + GPRs = regs + break + + if not GPRs: + return None + + for reg in GPRs: + self.register_ctx[reg.name] = int(reg.value, base=16) + + return struct.pack("{}Q".format(len(self.register_ctx)), *self.register_ctx.values()) + + +def __lldb_init_module(debugger, dict): + if not 'SKIP_SCRIPTED_PROCESS_LAUNCH' in os.environ: + debugger.HandleCommand( + "process launch -C %s.%s" % (__name__, + StackCoreScriptedProcess.__name__)) + else: + print("Name of the class that will manage the scripted process: '%s.%s'" + % (__name__, StackCoreScriptedProcess.__name__)) Index: lldb/test/API/functionalities/scripted_platform/Makefile =================================================================== --- /dev/null +++ lldb/test/API/functionalities/scripted_platform/Makefile @@ -0,0 +1,6 @@ +CXX_SOURCES := main.cpp +ENABLE_THREADS := YES + +all: a.out + +include Makefile.rules Index: lldb/test/API/functionalities/scripted_platform/TestScriptedPlatform.py =================================================================== --- /dev/null +++ lldb/test/API/functionalities/scripted_platform/TestScriptedPlatform.py @@ -0,0 +1,151 @@ +""" +Test python scripted process in lldb +""" + +import os, shutil + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from lldbsuite.test import lldbtest + +class ScriptedProcesTestCase(TestBase): + + NO_DEBUG_INFO_TESTCASE = True + + def test_python_plugin_package(self): + """Test that the lldb python module has a `plugins.scripted_process` + package.""" + self.expect('script import lldb.plugins', + substrs=["ModuleNotFoundError"], matching=False) + + self.expect('script dir(lldb.plugins)', + substrs=["scripted_process"]) + + self.expect('script import lldb.plugins.scripted_process', + substrs=["ModuleNotFoundError"], matching=False) + + self.expect('script dir(lldb.plugins.scripted_process)', + substrs=["ScriptedProcess"]) + + self.expect('script from lldb.plugins.scripted_process import ScriptedProcess', + substrs=["ImportError"], matching=False) + + self.expect('script dir(ScriptedProcess)', + substrs=["launch"]) + + def move_blueprint_to_dsym(self, blueprint_name): + blueprint_origin_path = os.path.join(self.getSourceDir(), blueprint_name) + dsym_bundle = self.getBuildArtifact("a.out.dSYM") + blueprint_destination_path = os.path.join(dsym_bundle, "Contents", + "Resources", "Python") + if not os.path.exists(blueprint_destination_path): + os.mkdir(blueprint_destination_path) + + blueprint_destination_path = os.path.join(blueprint_destination_path, "a_out.py") + shutil.copy(blueprint_origin_path, blueprint_destination_path) + + @skipUnlessDarwin + def test_invalid_scripted_register_context(self): + """Test that we can launch an lldb scripted process with an invalid + Scripted Thread, with invalid register context.""" + self.build() + + os.environ['SKIP_SCRIPTED_PROCESS_LAUNCH'] = '1' + def cleanup(): + del os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"] + self.addTearDownHook(cleanup) + + self.runCmd("settings set target.load-script-from-symbol-file true") + self.move_blueprint_to_dsym('invalid_scripted_process.py') + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(target, VALID_TARGET) + log_file = self.getBuildArtifact('thread.log') + self.runCmd("log enable lldb thread -f " + log_file) + self.assertTrue(os.path.isfile(log_file)) + + launch_info = lldb.SBLaunchInfo(None) + launch_info.SetProcessPluginName("ScriptedProcess") + launch_info.SetScriptedProcessClassName("a_out.InvalidScriptedProcess") + error = lldb.SBError() + + process = target.Launch(launch_info, error) + + self.assertSuccess(error) + self.assertTrue(process, PROCESS_IS_VALID) + self.assertEqual(process.GetProcessID(), 666) + self.assertEqual(process.GetNumThreads(), 0) + + addr = 0x500000000 + buff = process.ReadMemory(addr, 4, error) + self.assertEqual(buff, None) + self.assertTrue(error.Fail()) + self.assertEqual(error.GetCString(), "This is an invalid scripted process!") + + with open(log_file, 'r') as f: + log = f.read() + + self.assertIn("Failed to get scripted thread registers data.", log) + + @skipUnlessDarwin + def test_scripted_process_and_scripted_thread(self): + """Test that we can launch an lldb scripted process using the SBAPI, + check its process ID, read string from memory, check scripted thread + id, name stop reason and register context. + """ + self.build() + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(target, VALID_TARGET) + + os.environ['SKIP_SCRIPTED_PROCESS_LAUNCH'] = '1' + def cleanup(): + del os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"] + self.addTearDownHook(cleanup) + + scripted_process_example_relpath = 'dummy_scripted_process.py' + self.runCmd("command script import " + os.path.join(self.getSourceDir(), + scripted_process_example_relpath)) + + launch_info = lldb.SBLaunchInfo(None) + launch_info.SetProcessPluginName("ScriptedProcess") + launch_info.SetScriptedProcessClassName("dummy_scripted_process.DummyScriptedProcess") + + error = lldb.SBError() + process = target.Launch(launch_info, error) + self.assertTrue(process and process.IsValid(), PROCESS_IS_VALID) + self.assertEqual(process.GetProcessID(), 42) + self.assertEqual(process.GetNumThreads(), 1) + + addr = 0x500000000 + message = "Hello, world!" + buff = process.ReadCStringFromMemory(addr, len(message) + 1, error) + self.assertSuccess(error) + self.assertEqual(buff, message) + + thread = process.GetSelectedThread() + self.assertTrue(thread, "Invalid thread.") + self.assertEqual(thread.GetThreadID(), 0x19) + self.assertEqual(thread.GetName(), "DummyScriptedThread.thread-1") + self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonSignal) + + self.assertGreater(thread.GetNumFrames(), 0) + + frame = thread.GetFrameAtIndex(0) + GPRs = None + register_set = frame.registers # Returns an SBValueList. + for regs in register_set: + if 'general purpose' in regs.name.lower(): + GPRs = regs + break + + self.assertTrue(GPRs, "Invalid General Purpose Registers Set") + self.assertGreater(GPRs.GetNumChildren(), 0) + for idx, reg in enumerate(GPRs, start=1): + if idx > 21: + break + self.assertEqual(idx, int(reg.value, 16)) + + self.assertTrue(frame.IsArtificial(), "Frame is not artificial") + pc = frame.GetPCAddress().GetLoadAddress(target) + self.assertEqual(pc, 0x0100001b00) Index: lldb/test/API/functionalities/scripted_platform/main.cpp =================================================================== --- /dev/null +++ lldb/test/API/functionalities/scripted_platform/main.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +int main() { + size_t num_threads = 10; + std::vector threads; + + std::mutex mutex; + std::unique_lock lock(mutex); + + for (size_t i = 0; i < num_threads; i++) { + std::cout << "Spawning thread " << i << std::endl; + threads.push_back(std::thread([]() { + while (true) { + ; + } + })); + } + + for (auto &t : threads) { + if (t.joinable()) + t.join(); + } + + lock.unlock(); + + return 0; +} Index: lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp =================================================================== --- lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp +++ lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp @@ -159,6 +159,10 @@ return nullptr; } +void *lldb_private::LLDBSWIGPython_CastPyObjectToSBEvent(PyObject *data) { + return nullptr; +} + void *lldb_private::LLDBSWIGPython_CastPyObjectToSBError(PyObject *data) { return nullptr; }