Author: Med Ismail Bennani Date: 2023-05-22T16:14:00-07:00 New Revision: 273a2d337f675f3ee050f281b1fecc3e806b9a3c
URL: https://github.com/llvm/llvm-project/commit/273a2d337f675f3ee050f281b1fecc3e806b9a3c DIFF: https://github.com/llvm/llvm-project/commit/273a2d337f675f3ee050f281b1fecc3e806b9a3c.diff LOG: [lldb] Move PassthroughScriptedProcess to `lldb.scripted_process` module This patch moves the `PassthroughScriptedProcess` & `PassthroughScriptedThread` classes from the `interactive_scripted_process.py` test implementation to the `lldb.scripted_process` python module. This class is very versatile so it makes more sense to ship it with the python module to make it easier for our adopters to derive their class from it instead of copying it. During the "migration", I've also noticed some bugs in the `PassthroughScriptedThread` creation and update, so I also fixed that as part of this patch. Differential Revision: https://reviews.llvm.org/D151044 Signed-off-by: Med Ismail Bennani <ism...@bennani.ma> Added: Modified: lldb/examples/python/scripted_process/scripted_process.py lldb/test/API/functionalities/interactive_scripted_process/interactive_scripted_process.py Removed: ################################################################################ diff --git a/lldb/examples/python/scripted_process/scripted_process.py b/lldb/examples/python/scripted_process/scripted_process.py index a0880fbb6268f..b809f6c8224c3 100644 --- a/lldb/examples/python/scripted_process/scripted_process.py +++ b/lldb/examples/python/scripted_process/scripted_process.py @@ -1,6 +1,7 @@ from abc import ABCMeta, abstractmethod import lldb +import json, struct, signal class ScriptedProcess(metaclass=ABCMeta): @@ -225,7 +226,6 @@ def create_breakpoint(self, addr, error): % self.__class__.__name__) return False - class ScriptedThread(metaclass=ABCMeta): """ @@ -376,6 +376,162 @@ def get_extended_info(self): """ return self.extended_info + +class PassthroughScriptedProcess(ScriptedProcess): + driving_target = None + driving_process = None + + def __init__(self, exe_ctx, args, launched_driving_process=True): + super().__init__(exe_ctx, args) + + self.driving_target = None + self.driving_process = None + + self.driving_target_idx = args.GetValueForKey("driving_target_idx") + if self.driving_target_idx and self.driving_target_idx.IsValid(): + idx = self.driving_target_idx.GetUnsignedIntegerValue(42) + self.driving_target = self.target.GetDebugger().GetTargetAtIndex(idx) + + if launched_driving_process: + self.driving_process = self.driving_target.GetProcess() + for driving_thread in self.driving_process: + structured_data = lldb.SBStructuredData() + structured_data.SetFromJSON( + json.dumps( + { + "driving_target_idx": idx, + "thread_idx": driving_thread.GetIndexID(), + } + ) + ) + + self.threads[ + driving_thread.GetThreadID() + ] = PassthroughScriptedThread(self, structured_data) + + for module in self.driving_target.modules: + path = module.file.fullpath + load_addr = module.GetObjectFileHeaderAddress().GetLoadAddress( + self.driving_target + ) + self.loaded_images.append({"path": path, "load_addr": load_addr}) + + def get_memory_region_containing_address(self, addr): + mem_region = lldb.SBMemoryRegionInfo() + error = self.driving_process.GetMemoryRegionInfo(addr, mem_region) + if error.Fail(): + return None + return mem_region + + def read_memory_at_address(self, addr, size, error): + data = lldb.SBData() + bytes_read = self.driving_process.ReadMemory(addr, size, error) + + if error.Fail(): + return data + + data.SetDataWithOwnership( + error, + bytes_read, + self.driving_target.GetByteOrder(), + self.driving_target.GetAddressByteSize(), + ) + + return data + + def write_memory_at_address(self, addr, data, error): + return self.driving_process.WriteMemory( + addr, bytearray(data.uint8.all()), error + ) + + def get_process_id(self): + return self.driving_process.GetProcessID() + + def is_alive(self): + return True + + def get_scripted_thread_plugin(self): + return f"{PassthroughScriptedThread.__module__}.{PassthroughScriptedThread.__name__}" + + +class PassthroughScriptedThread(ScriptedThread): + def __init__(self, process, args): + super().__init__(process, args) + driving_target_idx = args.GetValueForKey("driving_target_idx") + thread_idx = args.GetValueForKey("thread_idx") + + # TODO: Change to Walrus operator (:=) with oneline if assignment + # Requires python 3.8 + val = thread_idx.GetUnsignedIntegerValue() + if val is not None: + self.idx = val + + self.driving_target = None + self.driving_process = None + self.driving_thread = None + + # TODO: Change to Walrus operator (:=) with oneline if assignment + # Requires python 3.8 + val = driving_target_idx.GetUnsignedIntegerValue() + if val is not None: + self.driving_target = self.target.GetDebugger().GetTargetAtIndex(val) + self.driving_process = self.driving_target.GetProcess() + self.driving_thread = self.driving_process.GetThreadByIndexID(self.idx) + + if self.driving_thread: + self.id = self.driving_thread.GetThreadID() + + def get_thread_id(self): + return self.id + + def get_name(self): + return f"{PassthroughScriptedThread.__name__}.thread-{self.idx}" + + def get_stop_reason(self): + stop_reason = {"type": lldb.eStopReasonInvalid, "data": {}} + + if ( + self.driving_thread + and self.driving_thread.IsValid() + and self.get_thread_id() == self.driving_thread.GetThreadID() + ): + stop_reason["type"] = lldb.eStopReasonNone + + # TODO: Passthrough stop reason from driving process + if self.driving_thread.GetStopReason() != lldb.eStopReasonNone: + if "arm64" in self.scripted_process.arch: + stop_reason["type"] = lldb.eStopReasonException + stop_reason["data"][ + "desc" + ] = self.driving_thread.GetStopDescription(100) + elif self.scripted_process.arch == "x86_64": + stop_reason["type"] = lldb.eStopReasonSignal + stop_reason["data"]["signal"] = signal.SIGTRAP + else: + stop_reason["type"] = self.driving_thread.GetStopReason() + + return stop_reason + + def get_register_context(self): + if not self.driving_thread or self.driving_thread.GetNumFrames() == 0: + return None + frame = self.driving_thread.GetFrameAtIndex(0) + + GPRs = None + registerSet = frame.registers # Returns an SBValueList. + for regs in registerSet: + if "general purpose" in regs.name.lower(): + GPRs = regs + break + + if not GPRs: + return None + + for reg in GPRs: + self.register_ctx[reg.name] = int(reg.value, base=16) + + return struct.pack(f"{len(self.register_ctx)}Q", *self.register_ctx.values()) + ARM64_GPR = [ {'name': 'x0', 'bitsize': 64, 'offset': 0, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 0, 'dwarf': 0, 'generic': 'arg0', 'alt-name': 'arg0'}, {'name': 'x1', 'bitsize': 64, 'offset': 8, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 1, 'dwarf': 1, 'generic': 'arg1', 'alt-name': 'arg1'}, {'name': 'x2', 'bitsize': 64, 'offset': 16, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 2, 'dwarf': 2, 'generic': 'arg2', 'alt-name': 'arg2'}, 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 index c979099066798..61ba3fc2c7b5a 100644 --- a/lldb/test/API/functionalities/interactive_scripted_process/interactive_scripted_process.py +++ b/lldb/test/API/functionalities/interactive_scripted_process/interactive_scripted_process.py @@ -12,102 +12,10 @@ from typing import Any, Dict import lldb -from lldb.plugins.scripted_process import ScriptedProcess -from lldb.plugins.scripted_process import ScriptedThread +from lldb.plugins.scripted_process import PassthroughScriptedProcess +from lldb.plugins.scripted_process import PassthroughScriptedThread - -class PassthruScriptedProcess(ScriptedProcess): - driving_target = None - driving_process = None - - def __init__( - self, - exe_ctx: lldb.SBExecutionContext, - args: lldb.SBStructuredData, - launched_driving_process: bool = True, - ): - super().__init__(exe_ctx, args) - - self.driving_target = None - self.driving_process = None - - self.driving_target_idx = args.GetValueForKey("driving_target_idx") - if self.driving_target_idx and self.driving_target_idx.IsValid(): - if self.driving_target_idx.GetType() == lldb.eStructuredDataTypeInteger: - idx = self.driving_target_idx.GetIntegerValue(42) - if self.driving_target_idx.GetType() == lldb.eStructuredDataTypeString: - idx = int(self.driving_target_idx.GetStringValue(100)) - self.driving_target = self.target.GetDebugger().GetTargetAtIndex(idx) - - if launched_driving_process: - self.driving_process = self.driving_target.GetProcess() - for driving_thread in self.driving_process: - structured_data = lldb.SBStructuredData() - structured_data.SetFromJSON( - json.dumps( - { - "driving_target_idx": idx, - "thread_idx": driving_thread.GetIndexID(), - } - ) - ) - - self.threads[driving_thread.GetThreadID()] = PassthruScriptedThread( - self, structured_data - ) - - for module in self.driving_target.modules: - path = module.file.fullpath - load_addr = module.GetObjectFileHeaderAddress().GetLoadAddress( - self.driving_target - ) - self.loaded_images.append({"path": path, "load_addr": load_addr}) - - def get_memory_region_containing_address( - self, addr: int - ) -> lldb.SBMemoryRegionInfo: - mem_region = lldb.SBMemoryRegionInfo() - error = self.driving_process.GetMemoryRegionInfo(addr, mem_region) - if error.Fail(): - return None - return mem_region - - def read_memory_at_address( - self, addr: int, size: int, error: lldb.SBError - ) -> lldb.SBData: - data = lldb.SBData() - bytes_read = self.driving_process.ReadMemory(addr, size, error) - - if error.Fail(): - return data - - data.SetDataWithOwnership( - error, - bytes_read, - self.driving_target.GetByteOrder(), - self.driving_target.GetAddressByteSize(), - ) - - return data - - def write_memory_at_address( - self, addr: int, data: lldb.SBData, error: lldb.SBError - ) -> int: - return self.driving_process.WriteMemory( - addr, bytearray(data.uint8.all()), error - ) - - def get_process_id(self) -> int: - return 42 - - def is_alive(self) -> bool: - return True - - def get_scripted_thread_plugin(self) -> str: - return f"{PassthruScriptedThread.__module__}.{PassthruScriptedThread.__name__}" - - -class MultiplexedScriptedProcess(PassthruScriptedProcess): +class MultiplexedScriptedProcess(PassthroughScriptedProcess): def __init__(self, exe_ctx: lldb.SBExecutionContext, args: lldb.SBStructuredData): super().__init__(exe_ctx, args) self.multiplexer = None @@ -115,11 +23,11 @@ def __init__(self, exe_ctx: lldb.SBExecutionContext, args: lldb.SBStructuredData parity = args.GetValueForKey("parity") # TODO: Change to Walrus operator (:=) with oneline if assignment # Requires python 3.8 - val = extract_value_from_structured_data(parity, 0) + val = parity.GetUnsignedIntegerValue() if val is not None: self.parity = val - # Turn PassThruScriptedThread into MultiplexedScriptedThread + # Turn PassthroughScriptedThread into MultiplexedScriptedThread for thread in self.threads.values(): thread.__class__ = MultiplexedScriptedThread @@ -144,7 +52,7 @@ def get_threads_info(self) -> Dict[int, Any]: if not self.multiplexer: return super().get_threads_info() filtered_threads = self.multiplexer.get_threads_info(pid=self.get_process_id()) - # Update the filtered thread class from PassthruScriptedThread to MultiplexedScriptedThread + # Update the filtered thread class from PassthroughScriptedThread to MultiplexedScriptedThread return dict( map( lambda pair: (pair[0], MultiplexedScriptedThread(pair[1])), @@ -160,92 +68,13 @@ def create_breakpoint(self, addr, error, pid=None): def get_scripted_thread_plugin(self) -> str: return f"{MultiplexedScriptedThread.__module__}.{MultiplexedScriptedThread.__name__}" - -class PassthruScriptedThread(ScriptedThread): - def __init__(self, process, args): - super().__init__(process, args) - driving_target_idx = args.GetValueForKey("driving_target_idx") - thread_idx = args.GetValueForKey("thread_idx") - - # TODO: Change to Walrus operator (:=) with oneline if assignment - # Requires python 3.8 - val = extract_value_from_structured_data(thread_idx, 0) - if val is not None: - self.idx = val - - self.driving_target = None - self.driving_process = None - self.driving_thread = None - - # TODO: Change to Walrus operator (:=) with oneline if assignment - # Requires python 3.8 - val = extract_value_from_structured_data(driving_target_idx, 42) - if val is not None: - self.driving_target = self.target.GetDebugger().GetTargetAtIndex(val) - self.driving_process = self.driving_target.GetProcess() - self.driving_thread = self.driving_process.GetThreadByIndexID(self.idx) - - if self.driving_thread: - self.id = self.driving_thread.GetThreadID() - - def get_thread_id(self) -> int: - return self.id - - def get_name(self) -> str: - return f"{PassthruScriptedThread.__name__}.thread-{self.idx}" - - def get_stop_reason(self) -> Dict[str, Any]: - stop_reason = {"type": lldb.eStopReasonInvalid, "data": {}} - - if ( - self.driving_thread - and self.driving_thread.IsValid() - and self.get_thread_id() == self.driving_thread.GetThreadID() - ): - stop_reason["type"] = lldb.eStopReasonNone - - if self.driving_thread.GetStopReason() != lldb.eStopReasonNone: - if "arm64" in self.scripted_process.arch: - stop_reason["type"] = lldb.eStopReasonException - stop_reason["data"][ - "desc" - ] = self.driving_thread.GetStopDescription(100) - elif self.scripted_process.arch == "x86_64": - stop_reason["type"] = lldb.eStopReasonSignal - stop_reason["data"]["signal"] = signal.SIGTRAP - else: - stop_reason["type"] = self.driving_thread.GetStopReason() - - return stop_reason - - def get_register_context(self) -> str: - if not self.driving_thread or self.driving_thread.GetNumFrames() == 0: - return None - frame = self.driving_thread.GetFrameAtIndex(0) - - GPRs = None - registerSet = frame.registers # Returns an SBValueList. - for regs in registerSet: - if "general purpose" in regs.name.lower(): - GPRs = regs - break - - if not GPRs: - return None - - for reg in GPRs: - self.register_ctx[reg.name] = int(reg.value, base=16) - - return struct.pack(f"{len(self.register_ctx)}Q", *self.register_ctx.values()) - - -class MultiplexedScriptedThread(PassthruScriptedThread): +class MultiplexedScriptedThread(PassthroughScriptedThread): def get_name(self) -> str: parity = "Odd" if self.scripted_process.parity % 2 else "Even" return f"{parity}{MultiplexedScriptedThread.__name__}.thread-{self.idx}" -class MultiplexerScriptedProcess(PassthruScriptedProcess): +class MultiplexerScriptedProcess(PassthroughScriptedProcess): listener = None multiplexed_processes = None @@ -254,9 +83,9 @@ def handle_process_state_event(): # Update multiplexer process log("Updating interactive scripted process threads") dbg = self.driving_target.GetDebugger() - log("Clearing interactive scripted process threads") - self.threads.clear() + new_driving_thread_ids = [] for driving_thread in self.driving_process: + new_driving_thread_ids.append(driving_thread.id) log(f"{len(self.threads)} New thread {hex(driving_thread.id)}") structured_data = lldb.SBStructuredData() structured_data.SetFromJSON( @@ -270,10 +99,17 @@ def handle_process_state_event(): ) ) - self.threads[driving_thread.GetThreadID()] = PassthruScriptedThread( + self.threads[driving_thread.id] = PassthroughScriptedThread( self, structured_data ) + for thread_id in self.threads: + if thread_id not in new_driving_thread_ids: + log(f"Removing old thread {hex(thread_id)}") + del self.threads[thread_id] + + print(f"New thread count: {len(self.threads)}") + mux_process = self.target.GetProcess() mux_process.ForceScriptedState(lldb.eStateRunning) mux_process.ForceScriptedState(lldb.eStateStopped) @@ -284,6 +120,8 @@ def handle_process_state_event(): event = lldb.SBEvent() while True: + if not self.driving_process: + continue if self.listener.WaitForEvent(1, event): event_mask = event.GetType() if event_mask & lldb.SBProcess.eBroadcastBitStateChanged: @@ -474,16 +312,6 @@ def duplicate_target(driving_target): debugger = driving_target.GetDebugger() return debugger.CreateTargetWithFileAndTargetTriple(exe, triple) - -def extract_value_from_structured_data(data, default_val): - if data and data.IsValid(): - if data.GetType() == lldb.eStructuredDataTypeInteger: - return data.GetIntegerValue(default_val) - if data.GetType() == lldb.eStructuredDataTypeString: - return int(data.GetStringValue(100)) - return default_val - - def create_mux_process(debugger, command, exe_ctx, result, dict): if not debugger.GetNumTargets() > 0: return result.SetError( _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits