mib created this revision. mib added reviewers: LLDB, JDevlieghere, jasonmolenda, jingham, labath. Herald added subscribers: dang, mgorny. mib requested review of this revision. Herald added a project: LLDB.
This patch adds a new command options to the CommandObjectProecessLaunch for scripted processes. Among the options, the user need to specify the class name managing the scripted process. The user can also use a key-value dictionary holding arbitrary data that will be passed to the managing class. rdar://65508855 Signed-off-by: Med Ismail Bennani <medismail.benn...@gmail.com> [lldb/Interpreter] Add ScriptInterpreter Wrapper for ScriptedProcess This patch adds a ScriptedProcess interface to the ScriptInterpreter and more specifically, to the ScriptInterpreterPython. This interface will be used in the C++ `ScriptProcess` Process Plugin to call the script methods. At the moment, not all methods are implemented, they will upstreamed in upcoming patches. rdar://65508855 Signed-off-by: Med Ismail Bennani <medismail.benn...@gmail.com> [lldb/bindings] Add Python ScriptedProcess base class to lldb module In order to facilitate the writting of Scripted Processes, this patch introduces a `ScriptedProcess` python base class in the lldb module. The base class holds the python interface with all the - abstract - methods that need to be implemented by the inherited class but also some methods that can be overwritten. This patch also provides an example of a Scripted Process with the `ScriptedMachCoreProcess` class. rdar://65508855 Signed-off-by: Med Ismail Bennani <medismail.benn...@gmail.com> [lldb/Plugins] Add ScriptedProcess Process Plugin This patch introduces Scripted Processes to lldb. The goal, here, is to be able to attach to fake processes in the debugger that are backed my scripts (Python, Lua, Swift, etc ...) and inspect them statically. Scripted Processes can be used in cooperative multithreading environments like the XNU Kernel or other real-time operating systems, but it can also help us improve the debugger testing infrastructure by writting synthetic tests that simulates hard-to-reproduce process/thread states. Although ScriptedProcess is not feature-complete at the moment, it has basic execution capabilities and will improve in the following patches. rdar://65508855 Signed-off-by: Med Ismail Bennani <medismail.benn...@gmail.com> Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D95709 Files: lldb/bindings/python/python-scripted-process.swig lldb/bindings/python/python-wrapper.swig lldb/bindings/python/python.swig lldb/examples/python/scripted_process.py lldb/include/lldb/Host/ProcessLaunchInfo.h lldb/include/lldb/Interpreter/ScriptInterpreter.h lldb/include/lldb/lldb-forward.h lldb/source/Commands/CommandObjectPlatform.cpp lldb/source/Commands/CommandObjectProcess.cpp lldb/source/Commands/CommandOptionsProcessLaunch.cpp lldb/source/Commands/CommandOptionsProcessLaunch.h lldb/source/Commands/Options.td lldb/source/Host/common/ProcessLaunchInfo.cpp lldb/source/Plugins/Process/CMakeLists.txt lldb/source/Plugins/Process/Scripted/CMakeLists.txt lldb/source/Plugins/Process/Scripted/ScriptedProcess.cpp lldb/source/Plugins/Process/Scripted/ScriptedProcess.h lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h lldb/source/Target/Target.cpp lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
Index: lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp =================================================================== --- lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp +++ lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp @@ -157,6 +157,10 @@ return nullptr; } +extern "C" void *LLDBSWIGPython_CastPyObjectToSBError(void *data) { + return nullptr; +} + extern lldb::ValueObjectSP LLDBSWIGPython_GetValueObjectSPFromSBValue(void *data) { return nullptr; Index: lldb/source/Target/Target.cpp =================================================================== --- lldb/source/Target/Target.cpp +++ lldb/source/Target/Target.cpp @@ -2950,7 +2950,7 @@ // If we're not already connected to the process, and if we have a platform // that can launch a process for debugging, go ahead and do that here. if (state != eStateConnected && platform_sp && - platform_sp->CanDebugProcess()) { + platform_sp->CanDebugProcess() && !launch_info.IsScriptedProcess()) { LLDB_LOGF(log, "Target::%s asking the platform to debug the process", __FUNCTION__); Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -74,6 +74,46 @@ StructuredData::GenericSP CreateScriptCommandObject(const char *class_name) override; + StructuredData::GenericSP ScriptedProcess_CreatePluginObject( + const char *class_name, lldb::TargetSP target_sp, + StructuredData::DictionarySP args_sp) override; + + Status ScriptedProcess_Launch( + StructuredData::ObjectSP scripted_process_object_sp) override; + + size_t ScriptedProcess_GetNumMemoryRegions( + StructuredData::ObjectSP scripted_process_object_sp) override; + + lldb::MemoryRegionInfoSP ScriptedProcess_GetMemoryRegionAtIndex( + StructuredData::ObjectSP scripted_process_object_sp, + size_t index) override; + + size_t ScriptedProcess_GetNumThreads( + StructuredData::ObjectSP scripted_process_object_sp) override; + + lldb::ThreadSP ScriptedProcess_GetThreadAtIndex( + StructuredData::ObjectSP scripted_process_object_sp, + size_t index) override; + + StructuredData::DictionarySP ScriptedProcess_GetRegisterForThread( + StructuredData::ObjectSP scripted_process_object_sp) override; + + size_t ScriptedProcess_ReadMemoryAtAddress( + StructuredData::ObjectSP scripted_process_object_sp, lldb::addr_t address, + size_t size) override; + + StructuredData::DictionarySP ScriptedProcess_GetLoadedImages( + StructuredData::ObjectSP scripted_process_object_sp) override; + + lldb::pid_t ScriptedProcess_GetProcessID( + StructuredData::ObjectSP scripted_process_object_sp) override; + + bool ScriptedProcess_CanDebug( + StructuredData::ObjectSP scripted_process_object_sp) override; + + bool ScriptedProcess_IsAlive( + StructuredData::ObjectSP scripted_process_object_sp) override; + StructuredData::ObjectSP CreateScriptedThreadPlan(const char *class_name, StructuredDataImpl *args_data, Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -17,6 +17,7 @@ #include "PythonDataObjects.h" #include "PythonReadline.h" #include "ScriptInterpreterPythonImpl.h" +#include "lldb/API/SBError.h" #include "lldb/API/SBFrame.h" #include "lldb/API/SBValue.h" #include "lldb/Breakpoint/StoppointCallbackContext.h" @@ -111,6 +112,11 @@ const char *session_dictionary_name, const lldb::DebuggerSP debugger_sp); +extern "C" void *LLDBSwigPythonCreateScriptedProcess( + const char *python_class_name, const char *session_dictionary_name, + const lldb::TargetSP &target_sp, StructuredDataImpl *args_impl, + std::string &error_string); + extern "C" void *LLDBSwigPythonCreateScriptedThreadPlan( const char *python_class_name, const char *session_dictionary_name, StructuredDataImpl *args_data, @@ -150,6 +156,8 @@ extern "C" void *LLDBSWIGPython_CastPyObjectToSBValue(void *data); +extern "C" void *LLDBSWIGPython_CastPyObjectToSBError(void *data); + extern lldb::ValueObjectSP LLDBSWIGPython_GetValueObjectSPFromSBValue(void *data); @@ -2148,6 +2156,212 @@ return StructuredData::GenericSP(new StructuredPythonObject(ret_val)); } +#pragma mark ScriptedProcessInterface + +StructuredData::GenericSP +ScriptInterpreterPythonImpl::ScriptedProcess_CreatePluginObject( + const char *class_name, lldb::TargetSP target_sp, + StructuredData::DictionarySP args_sp) { + if (class_name == nullptr || class_name[0] == '\0') + return {}; + + std::string error_string; + StructuredDataImpl *args_impl = nullptr; + if (args_sp) { + args_impl = new StructuredDataImpl(); + args_impl->SetObjectSP(args_sp); + } + + void *ret_val; + + { + Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, + Locker::FreeLock); + + ret_val = LLDBSwigPythonCreateScriptedProcess( + class_name, m_dictionary_name.c_str(), target_sp, args_impl, + error_string); + } + + return StructuredData::GenericSP(new StructuredPythonObject(ret_val)); +} + +Status ScriptInterpreterPythonImpl::ScriptedProcess_Launch( + StructuredData::ObjectSP scripted_process_object_sp) { + Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock); + + static char callee_name[] = "launch"; + + if (!scripted_process_object_sp) + return Status("Python object ill-formed."); + + StructuredData::Generic *generic = scripted_process_object_sp->GetAsGeneric(); + if (!generic) + return Status("Cannot convert Python object to StructuredData::Generic."); + PythonObject implementor(PyRefType::Borrowed, + (PyObject *)generic->GetValue()); + + if (!implementor.IsAllocated()) + return Status("Python implementor not allocated."); + + PythonObject pmeth(PyRefType::Owned, + PyObject_GetAttrString(implementor.get(), callee_name)); + + if (PyErr_Occurred()) + PyErr_Clear(); + + if (!pmeth.IsAllocated()) + return Status("Python method not allocated."); + + if (PyCallable_Check(pmeth.get()) == 0) { + if (PyErr_Occurred()) + PyErr_Clear(); + return Status("Python method not callable."); + } + + if (PyErr_Occurred()) + PyErr_Clear(); + + // right now we know this function exists and is callable.. + PythonObject py_return( + PyRefType::Owned, + PyObject_CallMethod(implementor.get(), callee_name, nullptr)); + + // if it fails, print the error but otherwise go on + if (PyErr_Occurred()) { + PyErr_Print(); + PyErr_Clear(); + } + + if (PyObject *py_ret_ptr = py_return.get()) { + lldb::SBError *sb_error = + (lldb::SBError *)LLDBSWIGPython_CastPyObjectToSBError(py_ret_ptr); + + if (!sb_error) + return Status("Couldn't cast lldb::SBError to lldb::Status."); + + if (sb_error->Fail()) + return Status("error: %s", sb_error->GetCString()); + + return Status(); + } + + return Status("Returned object is null."); +} + +size_t ScriptInterpreterPythonImpl::ScriptedProcess_GetNumMemoryRegions( + StructuredData::ObjectSP scripted_process_object_sp) { + Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock); + + static char callee_name[] = "get_num_memory_regions"; + + if (!scripted_process_object_sp) + return LLDB_INVALID_ADDRESS; + + StructuredData::Generic *generic = scripted_process_object_sp->GetAsGeneric(); + if (!generic) + return LLDB_INVALID_ADDRESS; + PythonObject implementor(PyRefType::Borrowed, + (PyObject *)generic->GetValue()); + + if (!implementor.IsAllocated()) + return LLDB_INVALID_ADDRESS; + + PythonObject pmeth(PyRefType::Owned, + PyObject_GetAttrString(implementor.get(), callee_name)); + + if (PyErr_Occurred()) + PyErr_Clear(); + + if (!pmeth.IsAllocated()) + return LLDB_INVALID_ADDRESS; + + if (PyCallable_Check(pmeth.get()) == 0) { + if (PyErr_Occurred()) + PyErr_Clear(); + return LLDB_INVALID_ADDRESS; + } + + if (PyErr_Occurred()) + PyErr_Clear(); + + // right now we know this function exists and is callable.. + PythonObject py_return( + PyRefType::Owned, + PyObject_CallMethod(implementor.get(), callee_name, nullptr)); + + // if it fails, print the error but otherwise go on + if (PyErr_Occurred()) { + PyErr_Print(); + PyErr_Clear(); + } + + if (py_return.get()) { + PythonDictionary result(PyRefType::Borrowed, py_return.get()); + auto size = result.AsUnsignedLongLong(); + return (size) ? *size : LLDB_INVALID_ADDRESS; + } + return LLDB_INVALID_ADDRESS; +} + +lldb::MemoryRegionInfoSP +ScriptInterpreterPythonImpl::ScriptedProcess_GetMemoryRegionAtIndex( + StructuredData::ObjectSP scripted_process_object_sp, size_t index) { + // TODO: Implement + return nullptr; +} + +size_t ScriptInterpreterPythonImpl::ScriptedProcess_GetNumThreads( + StructuredData::ObjectSP scripted_process_object_sp) { + // TODO: Implement + return LLDB_INVALID_ADDRESS; +} + +lldb::ThreadSP ScriptInterpreterPythonImpl::ScriptedProcess_GetThreadAtIndex( + StructuredData::ObjectSP scripted_process_object_sp, size_t index) { + // TODO: Implement + return nullptr; +} + +StructuredData::DictionarySP +ScriptInterpreterPythonImpl::ScriptedProcess_GetRegisterForThread( + StructuredData::ObjectSP scripted_process_object_sp) { + // TODO: Implement + return nullptr; +} + +size_t ScriptInterpreterPythonImpl::ScriptedProcess_ReadMemoryAtAddress( + StructuredData::ObjectSP scripted_process_object_sp, lldb::addr_t address, + size_t size) { + // TODO: Implement + return LLDB_INVALID_ADDRESS; +} + +StructuredData::DictionarySP +ScriptInterpreterPythonImpl::ScriptedProcess_GetLoadedImages( + StructuredData::ObjectSP scripted_process_object_sp) { + // TODO: Implement + return nullptr; +} + +lldb::pid_t ScriptInterpreterPythonImpl::ScriptedProcess_GetProcessID( + StructuredData::ObjectSP scripted_process_object_sp) { + // TODO: Implement + return LLDB_INVALID_PROCESS_ID; +} + +bool ScriptInterpreterPythonImpl::ScriptedProcess_CanDebug( + StructuredData::ObjectSP scripted_process_object_sp) { + // TODO: Implement + return true; +} + +bool ScriptInterpreterPythonImpl::ScriptedProcess_IsAlive( + StructuredData::ObjectSP scripted_process_object_sp) { + // TODO: Implement + return true; +} + bool ScriptInterpreterPythonImpl::GenerateTypeScriptFunction( const char *oneliner, std::string &output, const void *name_token) { StringList input; Index: lldb/source/Plugins/Process/Scripted/ScriptedProcess.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Process/Scripted/ScriptedProcess.h @@ -0,0 +1,107 @@ +//===-- ScriptedProcess.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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_SCRIPTED_PROCESS_H +#define LLDB_SOURCE_PLUGINS_SCRIPTED_PROCESS_H + +#include "lldb/Target/Process.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/Status.h" + +namespace lldb_private { + +class ScriptedProcess : public Process { +protected: + class LaunchInfo { + public: + LaunchInfo(const ProcessLaunchInfo &launch_info) { + m_class_name = launch_info.GetScriptedProcessClassName(); + m_dictionary_sp = launch_info.GetScriptedProcessDictionarySP(); + } + + llvm::StringRef GetClassName() const { return m_class_name; } + StructuredData::DictionarySP GetDictionarySP() const { + return m_dictionary_sp; + } + + private: + llvm::StringRef m_class_name; + StructuredData::DictionarySP m_dictionary_sp; + }; + +public: + static lldb::ProcessSP CreateInstance(lldb::TargetSP target_sp, + lldb::ListenerSP listener_sp, + const FileSpec *crash_file_path, + bool can_connect); + + static void Initialize(); + + static void Terminate(); + + static ConstString GetPluginNameStatic(); + + static const char *GetPluginDescriptionStatic(); + + ScriptedProcess(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp, + const ScriptedProcess::LaunchInfo &launch_info); + + ~ScriptedProcess() override; + + bool CanDebug(lldb::TargetSP target_sp, + bool plugin_specified_by_name) override; + + DynamicLoader *GetDynamicLoader() override { return nullptr; } + + ConstString GetPluginName() override; + + uint32_t GetPluginVersion() override; + + SystemRuntime *GetSystemRuntime() override { return nullptr; } + + Status DoLaunch(Module *exe_module, ProcessLaunchInfo &launch_info) override; + + Status DoResume() override; + + Status DoDestroy() override; + + void RefreshStateAfterStop() override {}; + + bool IsAlive() override; + + size_t ReadMemory(lldb::addr_t addr, void *buf, size_t size, + Status &error) override; + + size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size, + Status &error) override; + + ArchSpec GetArchitecture(); + + Status GetMemoryRegionInfo(lldb::addr_t load_addr, + MemoryRegionInfo &range_info) override; + + Status + GetMemoryRegions(lldb_private::MemoryRegionInfos ®ion_list) override; + + bool GetProcessInfo(ProcessInstanceInfo &info) override; + +protected: + void Clear(); + + bool DoUpdateThreadList(ThreadList &old_thread_list, + ThreadList &new_thread_list) override; + +private: + const LaunchInfo &m_launch_info; + lldb_private::ScriptInterpreter *m_interpreter; + lldb_private::StructuredData::ObjectSP m_python_object_sp; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_SCRIPTED_PROCESS_H Index: lldb/source/Plugins/Process/Scripted/ScriptedProcess.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/Process/Scripted/ScriptedProcess.cpp @@ -0,0 +1,195 @@ +//===-- ScriptedProcess.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 "ScriptedProcess.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" + +#include "lldb/Host/OptionParser.h" + +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/OptionGroupBoolean.h" +#include "lldb/Interpreter/ScriptInterpreter.h" + +#include "lldb/Target/MemoryRegionInfo.h" + +LLDB_PLUGIN_DEFINE(ScriptedProcess) + +using namespace lldb; +using namespace lldb_private; + +ConstString ScriptedProcess::GetPluginNameStatic() { + static ConstString g_name("ScriptedProcess"); + return g_name; +} + +const char *ScriptedProcess::GetPluginDescriptionStatic() { + return "Scripted Process plug-in."; +} + +lldb::ProcessSP ScriptedProcess::CreateInstance(lldb::TargetSP target_sp, + lldb::ListenerSP listener_sp, + const FileSpec *file, + bool can_connect) { + LaunchInfo launch_info(target_sp->GetProcessLaunchInfo()); + + return std::make_shared<ScriptedProcess>(target_sp, listener_sp, launch_info); +} + +bool ScriptedProcess::CanDebug(lldb::TargetSP target_sp, + bool plugin_specified_by_name) { + return true; +} + +ScriptedProcess::ScriptedProcess(lldb::TargetSP target_sp, + lldb::ListenerSP listener_sp, + const ScriptedProcess::LaunchInfo &launch_info) + : Process(target_sp, listener_sp), m_launch_info(launch_info), + m_interpreter(nullptr), m_python_object_sp(nullptr) { + if (!target_sp) + return; + + m_interpreter = target_sp->GetDebugger().GetScriptInterpreter(); + + if (!m_interpreter) + return; + + StructuredData::ObjectSP object_sp = + m_interpreter->ScriptedProcess_CreatePluginObject( + m_launch_info.GetClassName().str().c_str(), target_sp, + m_launch_info.GetDictionarySP()); + + if (object_sp && object_sp->IsValid()) + m_python_object_sp = object_sp; +} + +ScriptedProcess::~ScriptedProcess() { + Clear(); + // We need to call finalize on the process before destroying ourselves to + // make sure all of the broadcaster cleanup goes as planned. If we destruct + // this class, then Process::~Process() might have problems trying to fully + // destroy the broadcaster. + Finalize(); +} + +void ScriptedProcess::Initialize() { + static llvm::once_flag g_once_flag; + + llvm::call_once(g_once_flag, []() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + GetPluginDescriptionStatic(), CreateInstance); + }); +} + +void ScriptedProcess::Terminate() { + PluginManager::UnregisterPlugin(ScriptedProcess::CreateInstance); +} + +ConstString ScriptedProcess::GetPluginName() { return GetPluginNameStatic(); } + +uint32_t ScriptedProcess::GetPluginVersion() { return 1; } + +Status ScriptedProcess::DoLaunch(Module *exe_module, + ProcessLaunchInfo &launch_info) { + if (!m_interpreter) + return Status("No interpreter."); + + if (!m_python_object_sp) + return Status("No python object."); + + Status status = m_interpreter->ScriptedProcess_Launch(m_python_object_sp); + + if (status.Success()) + this->SetPrivateState(eStateStopped); + + return status; +}; + +Status ScriptedProcess::DoResume() { return Status(); } + +Status ScriptedProcess::DoDestroy() { return Status(); } + +bool ScriptedProcess::IsAlive() { return true; } + +size_t ScriptedProcess::ReadMemory(lldb::addr_t addr, void *buf, size_t size, + Status &error) { + // Don't allow the caching that lldb_private::Process::ReadMemory does since + // we have it all cached in our dump file anyway. + + return DoReadMemory(addr, buf, size, error); +} + +size_t ScriptedProcess::DoReadMemory(lldb::addr_t addr, void *buf, size_t size, + Status &error) { + + m_interpreter->ScriptedProcess_ReadMemoryAtAddress(m_python_object_sp, addr, + size); + return size; +} + +ArchSpec ScriptedProcess::GetArchitecture() { + return GetTarget().GetArchitecture(); +} + +Status ScriptedProcess::GetMemoryRegionInfo(lldb::addr_t load_addr, + MemoryRegionInfo ®ion) { + // BuildMemoryRegions(); + // region = MinidumpParser::GetMemoryRegionInfo(*m_memory_regions, + // load_addr); + return Status(); +} + +Status ScriptedProcess::GetMemoryRegions(MemoryRegionInfos ®ion_list) { + Status error; + auto size = + m_interpreter->ScriptedProcess_GetNumMemoryRegions(m_python_object_sp); + + if (size == LLDB_INVALID_ADDRESS) { + error.SetErrorString("ScriptedProcess: Invalid number of memory region!"); + return error; + } + + for (uint64_t i = 0; i < size; i++) { + // FIXME: Update interface method to handle extra arg (index) + MemoryRegionInfoSP mem_region_sp = + m_interpreter->ScriptedProcess_GetMemoryRegionAtIndex( + m_python_object_sp, i); + + if (!mem_region_sp) { + // FIXME: Interpolate index in error string + error.SetErrorString( + "ScriptedProcess: Couldn't fetch memory region at index BLA"); + return error; + } + region_list.push_back(*mem_region_sp.get()); + } + + return error; +} + +void ScriptedProcess::Clear() { Process::m_thread_list.Clear(); } + +bool ScriptedProcess::DoUpdateThreadList(ThreadList &old_thread_list, + ThreadList &new_thread_list) { + return new_thread_list.GetSize(false) > 0; +} + +bool ScriptedProcess::GetProcessInfo(ProcessInstanceInfo &info) { + info.Clear(); + info.SetProcessID(GetID()); + info.SetArchitecture(GetArchitecture()); + lldb::ModuleSP module_sp = GetTarget().GetExecutableModule(); + if (module_sp) { + const bool add_exe_file_as_first_arg = false; + info.SetExecutableFile(GetTarget().GetExecutableModule()->GetFileSpec(), + add_exe_file_as_first_arg); + } + return true; +} Index: lldb/source/Plugins/Process/Scripted/CMakeLists.txt =================================================================== --- /dev/null +++ lldb/source/Plugins/Process/Scripted/CMakeLists.txt @@ -0,0 +1,13 @@ +add_lldb_library(lldbPluginScriptedProcess PLUGIN + ScriptedProcess.cpp + + LINK_LIBS + lldbCore + lldbTarget + lldbUtility + lldbPluginProcessUtility + LINK_COMPONENTS + BinaryFormat + Object + Support + ) \ No newline at end of file Index: lldb/source/Plugins/Process/CMakeLists.txt =================================================================== --- lldb/source/Plugins/Process/CMakeLists.txt +++ lldb/source/Plugins/Process/CMakeLists.txt @@ -13,6 +13,7 @@ elseif (CMAKE_SYSTEM_NAME MATCHES "Darwin") add_subdirectory(MacOSX-Kernel) endif() +add_subdirectory(Scripted) add_subdirectory(gdb-remote) add_subdirectory(Utility) add_subdirectory(elf-core) Index: lldb/source/Host/common/ProcessLaunchInfo.cpp =================================================================== --- lldb/source/Host/common/ProcessLaunchInfo.cpp +++ lldb/source/Host/common/ProcessLaunchInfo.cpp @@ -32,7 +32,9 @@ : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(0), m_file_actions(), m_pty(new PseudoTerminal), m_resume_count(0), m_monitor_callback(nullptr), m_monitor_callback_baton(nullptr), - m_monitor_signals(false), m_listener_sp(), m_hijack_listener_sp() {} + m_monitor_signals(false), m_listener_sp(), m_hijack_listener_sp(), + m_scripted_process(false), m_scripted_process_class_name(), + m_scripted_process_dictionary_sp() {} ProcessLaunchInfo::ProcessLaunchInfo(const FileSpec &stdin_file_spec, const FileSpec &stdout_file_spec, @@ -42,7 +44,9 @@ : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(launch_flags), m_file_actions(), m_pty(new PseudoTerminal), m_resume_count(0), m_monitor_callback(nullptr), m_monitor_callback_baton(nullptr), - m_monitor_signals(false), m_listener_sp(), m_hijack_listener_sp() { + m_monitor_signals(false), m_listener_sp(), m_hijack_listener_sp(), + m_scripted_process(false), m_scripted_process_class_name(), + m_scripted_process_dictionary_sp() { if (stdin_file_spec) { FileAction file_action; const bool read = true; @@ -171,6 +175,9 @@ m_resume_count = 0; m_listener_sp.reset(); m_hijack_listener_sp.reset(); + m_scripted_process = false; + m_scripted_process_class_name = ""; + m_scripted_process_dictionary_sp.reset(); } void ProcessLaunchInfo::SetMonitorProcessCallback( Index: lldb/source/Commands/Options.td =================================================================== --- lldb/source/Commands/Options.td +++ lldb/source/Commands/Options.td @@ -673,6 +673,8 @@ Desc<"Do not set up for terminal I/O to go to running process.">; def process_launch_shell_expand_args : Option<"shell-expand-args", "X">, Group<4>, Arg<"Boolean">, Desc<"Set whether to shell expand arguments to the process when launching.">; + def process_launch_scripted : Option<"scripted", "S">, GroupRange<1,4>, + Desc<"Whether the process launched is a scripted process.">; } let Command = "process attach" in { Index: lldb/source/Commands/CommandOptionsProcessLaunch.h =================================================================== --- lldb/source/Commands/CommandOptionsProcessLaunch.h +++ lldb/source/Commands/CommandOptionsProcessLaunch.h @@ -1,4 +1,4 @@ -//===-- CommandOptionsProcessLaunch.h -------------------------------------===// +//===-- CommandOptionsProcessLaunch.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. @@ -16,9 +16,9 @@ // CommandOptionsProcessLaunch -class CommandOptionsProcessLaunch : public lldb_private::Options { +class CommandOptionsProcessLaunch : public lldb_private::OptionGroup { public: - CommandOptionsProcessLaunch() : lldb_private::Options() { + CommandOptionsProcessLaunch() : lldb_private::OptionGroup() { // Keep default values of all options in one place: OptionParsingStarting // () OptionParsingStarting(nullptr); Index: lldb/source/Commands/CommandOptionsProcessLaunch.cpp =================================================================== --- lldb/source/Commands/CommandOptionsProcessLaunch.cpp +++ lldb/source/Commands/CommandOptionsProcessLaunch.cpp @@ -30,7 +30,7 @@ uint32_t option_idx, llvm::StringRef option_arg, ExecutionContext *execution_context) { Status error; - const int short_option = m_getopt_table[option_idx].val; + const int short_option = g_process_launch_options[option_idx].short_option; switch (short_option) { case 's': // Stop at program entry point @@ -134,6 +134,11 @@ launch_info.GetEnvironment().insert(option_arg); break; + case 'S': + launch_info.SetScriptedProcess(true); + launch_info.SetProcessPluginName("ScriptedProcess"); + break; + default: error.SetErrorStringWithFormat("unrecognized short option character '%c'", short_option); Index: lldb/source/Commands/CommandObjectProcess.cpp =================================================================== --- lldb/source/Commands/CommandObjectProcess.cpp +++ lldb/source/Commands/CommandObjectProcess.cpp @@ -17,6 +17,7 @@ #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/OptionGroupPythonClassWithDict.h" #include "lldb/Interpreter/Options.h" #include "lldb/Target/Platform.h" #include "lldb/Target/Process.h" @@ -108,7 +109,13 @@ interpreter, "process launch", "Launch the executable in the debugger.", nullptr, eCommandRequiresTarget, "restart"), - m_options() { + m_options(), m_class_options("scripted process (use with --Scripted)"), + m_all_options() { + m_all_options.Append(&m_options); + m_all_options.Append(&m_class_options, LLDB_OPT_SET_1 | LLDB_OPT_SET_2, + LLDB_OPT_SET_1); + m_all_options.Finalize(); + CommandArgumentEntry arg; CommandArgumentData run_args_arg; @@ -135,7 +142,7 @@ request, nullptr); } - Options *GetOptions() override { return &m_options; } + Options *GetOptions() override { return &m_all_options; } const char *GetRepeatCommand(Args ¤t_command_args, uint32_t index) override { @@ -180,6 +187,14 @@ disable_aslr = target->GetDisableASLR(); } + if (m_options.launch_info.IsScriptedProcess()) { + m_options.launch_info.SetScriptedProcessClassName( + m_class_options.GetName()); + m_options.launch_info.SetScriptedProcessDictionarySP( + m_class_options.GetStructuredData()); + target->SetProcessLaunchInfo(m_options.launch_info); + } + if (disable_aslr) m_options.launch_info.GetFlags().Set(eLaunchFlagDisableASLR); else @@ -253,6 +268,8 @@ } CommandOptionsProcessLaunch m_options; + OptionGroupPythonClassWithDict m_class_options; + OptionGroupOptions m_all_options; }; #define LLDB_OPTIONS_process_attach Index: lldb/source/Commands/CommandObjectPlatform.cpp =================================================================== --- lldb/source/Commands/CommandObjectPlatform.cpp +++ lldb/source/Commands/CommandObjectPlatform.cpp @@ -1009,11 +1009,14 @@ "Launch a new process on a remote platform.", "platform process launch program", eCommandRequiresTarget | eCommandTryTargetAPILock), - m_options() {} + m_options(), m_all_options() { + m_all_options.Append(&m_options); + m_all_options.Finalize(); + } ~CommandObjectPlatformProcessLaunch() override = default; - Options *GetOptions() override { return &m_options; } + Options *GetOptions() override { return &m_all_options; } protected: bool DoExecute(Args &args, CommandReturnObject &result) override { @@ -1085,6 +1088,7 @@ } CommandOptionsProcessLaunch m_options; + OptionGroupOptions m_all_options; }; // "platform process list" Index: lldb/include/lldb/lldb-forward.h =================================================================== --- lldb/include/lldb/lldb-forward.h +++ lldb/include/lldb/lldb-forward.h @@ -341,6 +341,7 @@ typedef std::weak_ptr<lldb_private::Listener> ListenerWP; typedef std::shared_ptr<lldb_private::MemoryHistory> MemoryHistorySP; typedef std::unique_ptr<lldb_private::MemoryRegionInfo> MemoryRegionInfoUP; +typedef std::shared_ptr<lldb_private::MemoryRegionInfo> MemoryRegionInfoSP; typedef std::shared_ptr<lldb_private::Module> ModuleSP; typedef std::weak_ptr<lldb_private::Module> ModuleWP; typedef std::shared_ptr<lldb_private::ObjectFile> ObjectFileSP; Index: lldb/include/lldb/Interpreter/ScriptInterpreter.h =================================================================== --- lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -528,6 +528,71 @@ lldb::ScriptLanguage GetLanguage() { return m_script_lang; } +#pragma mark ScriptedProcessInterface + + virtual StructuredData::GenericSP + ScriptedProcess_CreatePluginObject(const char *class_name, + lldb::TargetSP target_sp, + StructuredData::DictionarySP args_sp) { + return nullptr; + } + + virtual Status + ScriptedProcess_Launch(StructuredData::ObjectSP scripted_process_object_sp) { + return Status("ScriptedProcess did not launch"); + } + + virtual size_t ScriptedProcess_GetNumMemoryRegions( + StructuredData::ObjectSP scripted_process_object_sp) { + return LLDB_INVALID_ADDRESS; + } + + virtual lldb::MemoryRegionInfoSP ScriptedProcess_GetMemoryRegionAtIndex( + StructuredData::ObjectSP scripted_process_object_sp, size_t index) { + return nullptr; + } + + virtual size_t ScriptedProcess_GetNumThreads( + StructuredData::ObjectSP scripted_process_object_sp) { + return LLDB_INVALID_ADDRESS; + } + + virtual lldb::ThreadSP ScriptedProcess_GetThreadAtIndex( + StructuredData::ObjectSP scripted_process_object_sp, size_t index) { + return nullptr; + } + + virtual StructuredData::DictionarySP ScriptedProcess_GetRegisterForThread( + StructuredData::ObjectSP scripted_process_object_sp) { + return nullptr; + } + + virtual size_t ScriptedProcess_ReadMemoryAtAddress( + StructuredData::ObjectSP scripted_process_object_sp, lldb::addr_t address, + size_t size) { + return LLDB_INVALID_ADDRESS; + } + + virtual StructuredData::DictionarySP ScriptedProcess_GetLoadedImages( + StructuredData::ObjectSP scripted_process_object_sp) { + return nullptr; + } + + virtual lldb::pid_t ScriptedProcess_GetProcessID( + StructuredData::ObjectSP scripted_process_object_sp) { + return LLDB_INVALID_PROCESS_ID; + } + + virtual bool ScriptedProcess_CanDebug( + StructuredData::ObjectSP scripted_process_object_sp) { + return true; + } + + virtual bool + ScriptedProcess_IsAlive(StructuredData::ObjectSP scripted_process_object_sp) { + return true; + } + protected: Debugger &m_debugger; lldb::ScriptLanguage m_script_lang; Index: lldb/include/lldb/Host/ProcessLaunchInfo.h =================================================================== --- lldb/include/lldb/Host/ProcessLaunchInfo.h +++ lldb/include/lldb/Host/ProcessLaunchInfo.h @@ -20,6 +20,7 @@ #include "lldb/Host/PseudoTerminal.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/ProcessInfo.h" +#include "lldb/Utility/StructuredData.h" namespace lldb_private { @@ -146,6 +147,28 @@ return m_flags.Test(lldb::eLaunchFlagDetachOnError); } + bool IsScriptedProcess() const { return m_scripted_process; } + + void SetScriptedProcess(bool b) { m_scripted_process = b; } + + llvm::StringRef GetScriptedProcessClassName() const { + return m_scripted_process_class_name; + } + + void SetScriptedProcessClassName(llvm::StringRef name) { + m_scripted_process_class_name = name; + } + + lldb_private::StructuredData::DictionarySP + GetScriptedProcessDictionarySP() const { + return m_scripted_process_dictionary_sp; + } + + void SetScriptedProcessDictionarySP( + lldb_private::StructuredData::DictionarySP dictionary_sp) { + m_scripted_process_dictionary_sp = dictionary_sp; + } + protected: FileSpec m_working_dir; std::string m_plugin_name; @@ -161,6 +184,9 @@ // meaning to the upper levels of lldb. lldb::ListenerSP m_listener_sp; lldb::ListenerSP m_hijack_listener_sp; + bool m_scripted_process; + llvm::StringRef m_scripted_process_class_name; + StructuredData::DictionarySP m_scripted_process_dictionary_sp; }; } Index: lldb/examples/python/scripted_process.py =================================================================== --- /dev/null +++ lldb/examples/python/scripted_process.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +from typing import List + +import lldb +from lldb import ScriptedProcess + +class ScriptedMachCoreProcess(ScriptedProcess): + def __init__(self, target: lldb.SBTarget, args : lldb.SBStructuredData): + super().__init__(target, args) + + ### Main functionalities + def get_num_memory_regions(self) -> int: + return len(self.memory_regions) + def get_memory_region_at_index(self, idx: int) -> lldb.SBMemoryRegionInfo: + return self.memory_regions[idx] + def get_num_threads(self) -> int: + return len(self.threads) + def get_thread_at_index(self, idx: int) -> lldb.SBThread: + return self.threads[idx] + def get_register_for_thread(self, tid: int): + return tid + def read_memory_at_address(self, addr: int) -> lldb.SBData: + return addr + def get_loaded_images(self) -> List[str]: + return self.loaded_images + def get_process_id(self) -> int: + return 42 + + ### Process state + def can_debug(self) -> bool: + return True + def is_alive(self) -> bool: + return True + +def __lldb_init_module(debugger, dict): + debugger.HandleCommand( + "process launch -S -C %s.%s" % (__name__, + ScriptedMachCoreProcess.__name__)) + Index: lldb/bindings/python/python.swig =================================================================== --- lldb/bindings/python/python.swig +++ lldb/bindings/python/python.swig @@ -130,6 +130,7 @@ %include "interfaces.swig" %include "python-extensions.swig" %include "python-wrapper.swig" +%include "python-scripted-process.swig" %pythoncode%{ _initialize = True Index: lldb/bindings/python/python-wrapper.swig =================================================================== --- lldb/bindings/python/python-wrapper.swig +++ lldb/bindings/python/python-wrapper.swig @@ -258,6 +258,72 @@ Py_RETURN_NONE; } +SWIGEXPORT void* +LLDBSwigPythonCreateScriptedProcess +( + const char *python_class_name, + const char *session_dictionary_name, + const lldb::TargetSP& target_sp, + lldb_private::StructuredDataImpl *args_impl, + std::string &error_string +) +{ + if (python_class_name == NULL || python_class_name[0] == '\0' || !session_dictionary_name) + Py_RETURN_NONE; + + + PyErr_Cleaner py_err_cleaner(true); + + auto dict = PythonModule::MainModule().ResolveName<PythonDictionary>(session_dictionary_name); + auto pfunc = PythonObject::ResolveNameWithDictionary<PythonCallable>(python_class_name, dict); + + if (!pfunc.IsAllocated()) { + error_string.append("could not find script class: "); + error_string.append(python_class_name); + return nullptr; + } + + // I do not want the SBTarget to be deallocated when going out of scope + // because python has ownership of it and will manage memory for this + // object by itself + PythonObject target_arg(PyRefType::Owned, SBTypeToSWIGWrapper(new lldb::SBTarget(target_sp))); + + if (!target_arg.IsAllocated()) + Py_RETURN_NONE; + + llvm::Expected<PythonCallable::ArgInfo> arg_info = pfunc.GetArgInfo(); + if (!arg_info) { + llvm::handleAllErrors( + arg_info.takeError(), + [&](PythonException &E) { + error_string.append(E.ReadBacktrace()); + }, + [&](const llvm::ErrorInfoBase &E) { + error_string.append(E.message()); + }); + Py_RETURN_NONE; + } + + PythonObject result = {}; + if (arg_info.get().max_positional_args == 2) { + if (args_impl != nullptr) { + error_string.assign("args passed, but __init__ does not take an args dictionary"); + Py_RETURN_NONE; + } + result = pfunc(target_arg, dict); + } else if (arg_info.get().max_positional_args >= 3) { + PythonObject args_arg(PyRefType::Owned, SBTypeToSWIGWrapper(new lldb::SBStructuredData(args_impl))); + result = pfunc(target_arg, args_arg, dict); + } else { + error_string.assign("wrong number of arguments in __init__, should be 2 or 3 (not including self)"); + Py_RETURN_NONE; + } + + if (result.IsAllocated()) + return result.release(); + Py_RETURN_NONE; +} + SWIGEXPORT void* LLDBSwigPythonCreateScriptedThreadPlan ( @@ -801,6 +867,22 @@ return sb_ptr; } +SWIGEXPORT void* +LLDBSWIGPython_CastPyObjectToSBError +( + PyObject* data +) +{ + lldb::SBError* sb_ptr = nullptr; + + int valid_cast = SWIG_ConvertPtr(data, (void**)&sb_ptr, SWIGTYPE_p_lldb__SBError, 0); + + if (valid_cast == -1) + return NULL; + + return sb_ptr; +} + SWIGEXPORT bool LLDBSwigPythonCallCommand ( Index: lldb/bindings/python/python-scripted-process.swig =================================================================== --- /dev/null +++ lldb/bindings/python/python-scripted-process.swig @@ -0,0 +1,65 @@ +%pythoncode %{ +from abc import ABC, abstractmethod +from typing import List + +import lldb + +class ScriptedProcess(ABC): + @abstractmethod + def __init__(self, target: lldb.SBTarget, args : lldb.SBStructuredData): + self.process_id = 0 + self.memory_regions = [] + self.threads = [] + self.loaded_images = [] + self.stops = [] + self.target = None + self.args = None + if isinstance(target, lldb.SBTarget) and target.IsValid(): + self.target = target + if isinstance(args, lldb.SBStructuredData) and args.IsValid(): + self.args = args + + ### Main funcitonnalities + @abstractmethod + def get_num_memory_regions(self) -> int: + pass + + @abstractmethod + def get_memory_region_at_index(self, idx: int) -> lldb.SBMemoryRegionInfo: + pass + + @abstractmethod + def get_num_threads(self): + pass + + @abstractmethod + def get_thread_at_index(self, idx: int) -> lldb.SBThread: + pass + + @abstractmethod + def get_register_for_thread(self, tid:int): + pass + + @abstractmethod + def read_memory_at_address(self, addr:int) -> lldb.SBData: + pass + + @abstractmethod + def get_loaded_images(self) -> List[str]: # -> List[lldb.SBModule]: + pass + + def get_process_id(self) -> int + return self.process_id + + ### Process state + def launch(self) -> lldb.SBError: + return lldb.SBError() + + @abstractmethod + def can_debug(self) -> bool: + pass + + @abstractmethod + def is_alive(self) -> bool: + pass +%}
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits