diff --git a/lldb/bindings/python/python-scripted-process.swig b/lldb/bindings/python/python-scripted-process.swig new file mode 100644 --- /dev/null +++ b/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 +%} diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig --- a/lldb/bindings/python/python-wrapper.swig +++ b/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(session_dictionary_name); + auto pfunc = PythonObject::ResolveNameWithDictionary(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 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 ( diff --git a/lldb/bindings/python/python.swig b/lldb/bindings/python/python.swig --- a/lldb/bindings/python/python.swig +++ b/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 diff --git a/lldb/examples/python/scripted_process.py b/lldb/examples/python/scripted_process.py new file mode 100644 --- /dev/null +++ b/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__)) + diff --git a/lldb/include/lldb/Host/ProcessLaunchInfo.h b/lldb/include/lldb/Host/ProcessLaunchInfo.h --- a/lldb/include/lldb/Host/ProcessLaunchInfo.h +++ b/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; }; } diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ b/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; diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h --- a/lldb/include/lldb/lldb-forward.h +++ b/lldb/include/lldb/lldb-forward.h @@ -341,6 +341,7 @@ typedef std::weak_ptr ListenerWP; typedef std::shared_ptr MemoryHistorySP; typedef std::unique_ptr MemoryRegionInfoUP; +typedef std::shared_ptr MemoryRegionInfoSP; typedef std::shared_ptr ModuleSP; typedef std::weak_ptr ModuleWP; typedef std::shared_ptr ObjectFileSP; diff --git a/lldb/source/Commands/CommandObjectPlatform.cpp b/lldb/source/Commands/CommandObjectPlatform.cpp --- a/lldb/source/Commands/CommandObjectPlatform.cpp +++ b/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" diff --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp --- a/lldb/source/Commands/CommandObjectProcess.cpp +++ b/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 diff --git a/lldb/source/Commands/CommandOptionsProcessLaunch.h b/lldb/source/Commands/CommandOptionsProcessLaunch.h --- a/lldb/source/Commands/CommandOptionsProcessLaunch.h +++ b/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); diff --git a/lldb/source/Commands/CommandOptionsProcessLaunch.cpp b/lldb/source/Commands/CommandOptionsProcessLaunch.cpp --- a/lldb/source/Commands/CommandOptionsProcessLaunch.cpp +++ b/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); diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td --- a/lldb/source/Commands/Options.td +++ b/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 { diff --git a/lldb/source/Host/common/ProcessLaunchInfo.cpp b/lldb/source/Host/common/ProcessLaunchInfo.cpp --- a/lldb/source/Host/common/ProcessLaunchInfo.cpp +++ b/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( diff --git a/lldb/source/Plugins/Process/CMakeLists.txt b/lldb/source/Plugins/Process/CMakeLists.txt --- a/lldb/source/Plugins/Process/CMakeLists.txt +++ b/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) diff --git a/lldb/source/Plugins/Process/Scripted/CMakeLists.txt b/lldb/source/Plugins/Process/Scripted/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/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 diff --git a/lldb/source/Plugins/Process/Scripted/ScriptedProcess.h b/lldb/source/Plugins/Process/Scripted/ScriptedProcess.h new file mode 100644 --- /dev/null +++ b/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 diff --git a/lldb/source/Plugins/Process/Scripted/ScriptedProcess.cpp b/lldb/source/Plugins/Process/Scripted/ScriptedProcess.cpp new file mode 100644 --- /dev/null +++ b/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(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; +} diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ b/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; diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ b/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, diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp --- a/lldb/source/Target/Target.cpp +++ b/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__); diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp --- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp +++ b/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;