diff --git a/lldb/bindings/interface/SBTargetExtensions.i b/lldb/bindings/interface/SBTargetExtensions.i --- a/lldb/bindings/interface/SBTargetExtensions.i +++ b/lldb/bindings/interface/SBTargetExtensions.i @@ -88,7 +88,7 @@ def get_modules_access_object(self): '''An accessor function that returns a modules_access() object which allows lazy module access from a lldb.SBTarget object.''' - return self.modules_access (self) + return self.modules_access(self) def get_modules_array(self): '''An accessor function that returns a list() that contains all modules in a lldb.SBTarget object.''' @@ -107,18 +107,80 @@ object.''' return lldb_iter(self, 'GetNumBreakpoints', 'GetBreakpointAtIndex') + class bkpts_access(object): + '''A helper object that will lazily hand out bkpts for a target when supplied an index.''' + def __init__(self, sbtarget): + self.sbtarget = sbtarget + + def __len__(self): + if self.sbtarget: + return int(self.sbtarget.GetNumBreakpoints()) + return 0 + + def __getitem__(self, key): + if isinstance(key, int): + count = len(self) + if -count <= key < count: + key %= count + return self.sbtarget.GetBreakpointAtIndex(key) + return None + + def get_bkpts_access_object(self): + '''An accessor function that returns a bkpts_access() object which allows lazy bkpt access from a lldb.SBtarget object.''' + return self.bkpts_access(self) + + def get_target_bkpts(self): + '''An accessor function that returns a list() that contains all bkpts in a lldb.SBtarget object.''' + bkpts = [] + for idx in range(self.GetNumBreakpoints()): + bkpts.append(self.GetBreakpointAtIndex(idx)) + return bkpts + def watchpoint_iter(self): '''Returns an iterator over all watchpoints in a lldb.SBTarget object.''' return lldb_iter(self, 'GetNumWatchpoints', 'GetWatchpointAtIndex') + class watchpoints_access(object): + '''A helper object that will lazily hand out watchpoints for a target when supplied an index.''' + def __init__(self, sbtarget): + self.sbtarget = sbtarget + + def __len__(self): + if self.sbtarget: + return int(self.sbtarget.GetNumWatchpoints()) + return 0 + + def __getitem__(self, key): + if isinstance(key, int): + count = len(self) + if -count <= key < count: + key %= count + return self.sbtarget.GetWatchpointAtIndex(key) + return None + + def get_watchpoints_access_object(self): + '''An accessor function that returns a watchpoints_access() object which allows lazy watchpoint access from a lldb.SBtarget object.''' + return self.watchpoints_access(self) + + def get_target_watchpoints(self): + '''An accessor function that returns a list() that contains all watchpoints in a lldb.SBtarget object.''' + watchpoints = [] + for idx in range(self.GetNumWatchpoints()): + bkpts.append(self.GetWatchpointAtIndex(idx)) + return watchpoints + modules = property(get_modules_array, None, doc='''A read only property that returns a list() of lldb.SBModule objects contained in this target. This list is a list all modules that the target currently is tracking (the main executable and all dependent shared libraries).''') module = property(get_modules_access_object, None, doc=r'''A read only property that returns an object that implements python operator overloading with the square brackets().\n target.module[] allows array access to any modules.\n target.module[] allows access to modules by basename, full path, or uuid string value.\n target.module[uuid.UUID()] allows module access by UUID.\n target.module[re] allows module access using a regular expression that matches the module full path.''') process = property(GetProcess, None, doc='''A read only property that returns an lldb object that represents the process (lldb.SBProcess) that this target owns.''') executable = property(GetExecutable, None, doc='''A read only property that returns an lldb object that represents the main executable module (lldb.SBModule) for this target.''') debugger = property(GetDebugger, None, doc='''A read only property that returns an lldb object that represents the debugger (lldb.SBDebugger) that owns this target.''') num_breakpoints = property(GetNumBreakpoints, None, doc='''A read only property that returns the number of breakpoints that this target has as an integer.''') + breakpoints = property(get_target_bkpts, None, doc='''A read only property that returns a list() of lldb.SBBreakpoint objects for all breakpoints in this target.''') + breakpoint = property(get_bkpts_access_object, None, doc='''A read only property that returns an object that can be used to access breakpoints as an array ("bkpt_12 = lldb.target.bkpt[12]").''') num_watchpoints = property(GetNumWatchpoints, None, doc='''A read only property that returns the number of watchpoints that this target has as an integer.''') + watchpoints = property(get_target_watchpoints, None, doc='''A read only property that returns a list() of lldb.SBwatchpoint objects for all watchpoints in this target.''') + watchpoint = property(get_watchpoints_access_object, None, doc='''A read only property that returns an object that can be used to access watchpoints as an array ("watchpoint_12 = lldb.target.watchpoint[12]").''') broadcaster = property(GetBroadcaster, None, doc='''A read only property that an lldb object that represents the broadcaster (lldb.SBBroadcaster) for this target.''') byte_order = property(GetByteOrder, None, doc='''A read only property that returns an lldb enumeration value (lldb.eByteOrderLittle, lldb.eByteOrderBig, lldb.eByteOrderInvalid) that represents the byte order for this target.''') addr_size = property(GetAddressByteSize, None, doc='''A read only property that returns the size in bytes of an address for this target.''') 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 @@ -716,6 +716,18 @@ return sb_ptr; } +void *lldb_private::LLDBSWIGPython_CastPyObjectToSBBreakpoint(PyObject * data) { + lldb::SBBreakpoint *sb_ptr = nullptr; + + int valid_cast = + SWIG_ConvertPtr(data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBBreakpoint, 0); + + if (valid_cast == -1) + return NULL; + + return sb_ptr; +} + void *lldb_private::LLDBSWIGPython_CastPyObjectToSBAttachInfo(PyObject * data) { lldb::SBAttachInfo *sb_ptr = nullptr; diff --git a/lldb/examples/python/scripted_process/scripted_process.py b/lldb/examples/python/scripted_process/scripted_process.py --- a/lldb/examples/python/scripted_process/scripted_process.py +++ b/lldb/examples/python/scripted_process/scripted_process.py @@ -209,6 +209,23 @@ """ return self.metadata + def create_breakpoint(self, addr, error): + """ Create a breakpoint in the scripted process from an address. + This is mainly used with interactive scripted process debugging. + + Args: + addr (int): Address at which the breakpoint should be set. + error (lldb.SBError): Error object. + + Returns: + SBBreakpoint: A valid breakpoint object that was created a the specified + address. None if the breakpoint creation failed. + """ + error.SetErrorString("%s doesn't support creating breakpoints." + % self.__class__.__name__) + return False + + class ScriptedThread(metaclass=ABCMeta): """ diff --git a/lldb/include/lldb/API/SBBreakpoint.h b/lldb/include/lldb/API/SBBreakpoint.h --- a/lldb/include/lldb/API/SBBreakpoint.h +++ b/lldb/include/lldb/API/SBBreakpoint.h @@ -13,6 +13,10 @@ class SBBreakpointListImpl; +namespace lldb_private { +class ScriptInterpreter; +} + namespace lldb { class LLDB_API SBBreakpoint { @@ -155,6 +159,8 @@ friend class SBBreakpointName; friend class SBTarget; + friend class lldb_private::ScriptInterpreter; + lldb::BreakpointSP GetSP() const; lldb::BreakpointWP m_opaque_wp; 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 @@ -10,6 +10,7 @@ #define LLDB_INTERPRETER_SCRIPTINTERPRETER_H #include "lldb/API/SBAttachInfo.h" +#include "lldb/API/SBBreakpoint.h" #include "lldb/API/SBData.h" #include "lldb/API/SBError.h" #include "lldb/API/SBLaunchInfo.h" @@ -587,6 +588,9 @@ Status GetStatusFromSBError(const lldb::SBError &error) const; + lldb::BreakpointSP + GetOpaqueTypeFromSBBreakpoint(const lldb::SBBreakpoint &breakpoint) const; + lldb::ProcessAttachInfoSP GetOpaqueTypeFromSBAttachInfo(const lldb::SBAttachInfo &attach_info) const; diff --git a/lldb/include/lldb/Interpreter/ScriptedProcessInterface.h b/lldb/include/lldb/Interpreter/ScriptedProcessInterface.h --- a/lldb/include/lldb/Interpreter/ScriptedProcessInterface.h +++ b/lldb/include/lldb/Interpreter/ScriptedProcessInterface.h @@ -46,6 +46,11 @@ virtual StructuredData::DictionarySP GetThreadsInfo() { return {}; } + virtual bool CreateBreakpoint(lldb::addr_t addr, Status &error) { + error.SetErrorString("ScriptedProcess don't support creating breakpoints."); + return {}; + } + virtual lldb::DataExtractorSP ReadMemoryAtAddress(lldb::addr_t address, size_t size, Status &error) { return {}; diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp --- a/lldb/source/Interpreter/ScriptInterpreter.cpp +++ b/lldb/source/Interpreter/ScriptInterpreter.cpp @@ -80,6 +80,11 @@ return data.m_opaque_sp; } +lldb::BreakpointSP ScriptInterpreter::GetOpaqueTypeFromSBBreakpoint( + const lldb::SBBreakpoint &breakpoint) const { + return breakpoint.m_opaque_wp.lock(); +} + lldb::ProcessAttachInfoSP ScriptInterpreter::GetOpaqueTypeFromSBAttachInfo( const lldb::SBAttachInfo &attach_info) const { return attach_info.m_opaque_sp; diff --git a/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp b/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp --- a/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp +++ b/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp @@ -260,7 +260,10 @@ return Status("Scripted Processes don't support hardware breakpoints"); } - return EnableSoftwareBreakpoint(bp_site); + Status error; + GetInterface().CreateBreakpoint(bp_site->GetLoadAddress(), error); + + return error; } ArchSpec ScriptedProcess::GetArchitecture() { diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h --- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h @@ -92,6 +92,7 @@ } // namespace python void *LLDBSWIGPython_CastPyObjectToSBData(PyObject *data); +void *LLDBSWIGPython_CastPyObjectToSBBreakpoint(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBAttachInfo(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBLaunchInfo(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBError(PyObject *data); diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h @@ -43,6 +43,8 @@ StructuredData::DictionarySP GetThreadsInfo() override; + bool CreateBreakpoint(lldb::addr_t addr, Status &error) override; + lldb::DataExtractorSP ReadMemoryAtAddress(lldb::addr_t address, size_t size, Status &error) override; diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp @@ -110,6 +110,22 @@ return dict; } +bool ScriptedProcessPythonInterface::CreateBreakpoint(lldb::addr_t addr, + Status &error) { + Status py_error; + StructuredData::ObjectSP obj = + Dispatch("create_breakpoint", py_error, addr, error); + + // If there was an error on the python call, surface it to the user. + if (py_error.Fail()) + error = py_error; + + if (!CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj, error)) + return {}; + + return obj->GetBooleanValue(); +} + lldb::DataExtractorSP ScriptedProcessPythonInterface::ReadMemoryAtAddress( lldb::addr_t address, size_t size, Status &error) { Status py_error; diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.h --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.h @@ -228,6 +228,11 @@ Status ScriptedPythonInterface::ExtractValueFromPythonObject( python::PythonObject &p, Status &error); +template <> +lldb::BreakpointSP +ScriptedPythonInterface::ExtractValueFromPythonObject( + python::PythonObject &p, Status &error); + template <> lldb::ProcessAttachInfoSP ScriptedPythonInterface::ExtractValueFromPythonObject< lldb::ProcessAttachInfoSP>(python::PythonObject &p, Status &error); diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.cpp --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.cpp @@ -70,6 +70,22 @@ return m_interpreter.GetDataExtractorFromSBData(*sb_data); } +template <> +lldb::BreakpointSP +ScriptedPythonInterface::ExtractValueFromPythonObject( + python::PythonObject &p, Status &error) { + lldb::SBBreakpoint *sb_breakpoint = reinterpret_cast( + LLDBSWIGPython_CastPyObjectToSBBreakpoint(p.get())); + + if (!sb_breakpoint) { + error.SetErrorString( + "Couldn't cast lldb::SBBreakpoint to lldb::BreakpointSP."); + return nullptr; + } + + return m_interpreter.GetOpaqueTypeFromSBBreakpoint(*sb_breakpoint); +} + template <> lldb::ProcessAttachInfoSP ScriptedPythonInterface::ExtractValueFromPythonObject< lldb::ProcessAttachInfoSP>(python::PythonObject &p, Status &error) { diff --git a/lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py b/lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py --- a/lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py +++ b/lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py @@ -107,7 +107,7 @@ self.assertEqual(real_pc, mux_pc, f"PC's equal for {id}") lldbutil.run_break_set_by_source_regexp(self, "also break here") - self.assertEqual(mux_target.GetNumBreakpoints(), 1) + self.assertEqual(mux_target.GetNumBreakpoints(), 2) error = mux_process.Continue() self.assertSuccess(error, "Resuming multiplexer scripted process") self.assertTrue(mux_process.IsValid(), "Got a valid process") diff --git a/lldb/test/API/functionalities/interactive_scripted_process/interactive_scripted_process.py b/lldb/test/API/functionalities/interactive_scripted_process/interactive_scripted_process.py --- a/lldb/test/API/functionalities/interactive_scripted_process/interactive_scripted_process.py +++ b/lldb/test/API/functionalities/interactive_scripted_process/interactive_scripted_process.py @@ -6,7 +6,7 @@ # -o "create_sub" \ # -o "br set -p 'also break here'" -o 'continue' -import os, json, struct, signal +import os, json, struct, signal, tempfile from threading import Thread from typing import Any, Dict @@ -152,6 +152,11 @@ ) ) + def create_breakpoint(self, addr, error, pid=None): + if not self.multiplexer: + error.SetErrorString("Multiplexer is not set.") + return self.multiplexer.create_breakpoint(addr, error, self.get_process_id()) + def get_scripted_thread_plugin(self) -> str: return f"{MultiplexedScriptedThread.__module__}.{MultiplexedScriptedThread.__name__}" @@ -300,6 +305,40 @@ ) self.multiplexed_processes = {} + # Copy breakpoints from real target to passthrough + with tempfile.NamedTemporaryFile() as tf: + bkpt_file = lldb.SBFileSpec(tf.name) + error = self.driving_target.BreakpointsWriteToFile(bkpt_file) + if error.Fail(): + log( + "Failed to save breakpoints from driving target (%s)" + % error.GetCString() + ) + bkpts_list = lldb.SBBreakpointList(self.target) + error = self.target.BreakpointsCreateFromFile(bkpt_file, bkpts_list) + if error.Fail(): + log( + "Failed create breakpoints from driving target \ + (bkpt file: %s)" + % tf.name + ) + + # Copy breakpoint from passthrough to real target + if error.Success(): + self.driving_target.DeleteAllBreakpoints() + for bkpt in self.target.breakpoints: + if bkpt.IsValid(): + for bl in bkpt: + real_bpkt = self.driving_target.BreakpointCreateBySBAddress( + bl.GetAddress() + ) + if not real_bpkt.IsValid(): + log( + "Failed to set breakpoint at address %s in \ + driving target" + % hex(bl.GetLoadAddress()) + ) + self.listener_thread = Thread( target=self.wait_for_driving_process_to_stop, daemon=True ) @@ -364,6 +403,47 @@ parity = pid % 2 return dict(filter(lambda pair: pair[0] % 2 == parity, self.threads.items())) + def create_breakpoint(self, addr, error, pid=None): + if not self.driving_target: + error.SetErrorString("%s has no driving target." % self.__class__.__name__) + return False + + def create_breakpoint_with_name(target, load_addr, name, error): + addr = lldb.SBAddress(load_addr, target) + if not addr.IsValid(): + error.SetErrorString("Invalid breakpoint address %s" % hex(load_addr)) + return False + bkpt = target.BreakpointCreateBySBAddress(addr) + if not bkpt.IsValid(): + error.SetErrorString( + "Failed to create breakpoint at address %s" + % hex(addr.GetLoadAddress()) + ) + return False + error = bkpt.AddNameWithErrorHandling(name) + return error.Success() + + name = ( + "multiplexer_scripted_process" + if not pid + else f"multiplexed_scripted_process_{pid}" + ) + + if pid is not None: + # This means that this method has been called from one of the + # multiplexed scripted process. That also means that the multiplexer + # target doesn't have this breakpoint created. + mux_error = lldb.SBError() + bkpt = create_breakpoint_with_name(self.target, addr, name, mux_error) + if mux_error.Fail(): + error.SetError( + "Failed to create breakpoint in multiplexer \ + target: %s" + % mux_error.GetCString() + ) + return False + return create_breakpoint_with_name(self.driving_target, addr, name, error) + def multiplex(mux_process, muxed_process): muxed_process.GetScriptedImplementation().multiplexer = ( 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 @@ -139,6 +139,10 @@ return nullptr; } +void *lldb_private::LLDBSWIGPython_CastPyObjectToSBBreakpoint(PyObject *data) { + return nullptr; +} + void *lldb_private::LLDBSWIGPython_CastPyObjectToSBAttachInfo(PyObject *data) { return nullptr; }