1 from abc
import ABCMeta
, abstractmethod
4 import json
, struct
, signal
7 class ScriptedProcess(metaclass
=ABCMeta
):
9 The base class for a scripted process.
11 Most of the base class methods are `@abstractmethod` that need to be
12 overwritten by the inheriting class.
22 def __init__(self
, exe_ctx
, args
):
23 """Construct a scripted process.
26 exe_ctx (lldb.SBExecutionContext): The execution context for the scripted process.
27 args (lldb.SBStructuredData): A Dictionary holding arbitrary
28 key/value pairs used by the scripted process.
34 if isinstance(exe_ctx
, lldb
.SBExecutionContext
):
35 target
= exe_ctx
.target
36 if isinstance(target
, lldb
.SBTarget
) and target
.IsValid():
38 triple
= self
.target
.triple
40 self
.arch
= triple
.split("-")[0]
41 self
.dbg
= target
.GetDebugger()
42 if isinstance(args
, lldb
.SBStructuredData
) and args
.IsValid():
45 self
.loaded_images
= []
47 self
.capabilities
= {}
50 def get_capabilities(self
):
51 """Get a dictionary containing the process capabilities.
54 Dict[str:bool]: The dictionary of capability, with the capability
55 name as the key and a boolean flag as the value.
56 The dictionary can be empty.
58 return self
.capabilities
60 def get_memory_region_containing_address(self
, addr
):
61 """Get the memory region for the scripted process, containing a
65 addr (int): Address to look for in the scripted process memory
69 lldb.SBMemoryRegionInfo: The memory region containing the address.
70 None if out of bounds.
74 def get_threads_info(self
):
75 """Get the dictionary describing the process' Scripted Threads.
78 Dict: The dictionary of threads, with the thread ID as the key and
79 a Scripted Thread instance as the value.
80 The dictionary can be empty.
85 def read_memory_at_address(self
, addr
, size
, error
):
86 """Get a memory buffer from the scripted process at a certain address,
90 addr (int): Address from which we should start reading.
91 size (int): Size of the memory to read.
92 error (lldb.SBError): Error object.
95 lldb.SBData: An `lldb.SBData` buffer with the target byte size and
96 byte order storing the memory read.
100 def write_memory_at_address(self
, addr
, data
, error
):
101 """Write a buffer to the scripted process memory.
104 addr (int): Address from which we should start reading.
105 data (lldb.SBData): An `lldb.SBData` buffer to write to the process
107 error (lldb.SBError): Error object.
110 size (int): Size of the memory to read.
112 error
.SetErrorString(
113 "%s doesn't support memory writes." % self
.__class
__.__name
__
117 def get_loaded_images(self
):
118 """Get the list of loaded images for the scripted process.
120 .. code-block:: python
123 uuid = "c6ea2b64-f77c-3d27-9528-74f507b9078b",
124 path = "/usr/lib/dyld"
125 load_addr = 0xbadc0ffee
129 List[scripted_image]: A list of `scripted_image` dictionaries
130 containing for each entry the library UUID or its file path
131 and its load address.
132 None if the list is empty.
134 return self
.loaded_images
136 def get_process_id(self
):
137 """Get the scripted process identifier.
140 int: The scripted process identifier.
145 """Simulate the scripted process launch.
148 lldb.SBError: An `lldb.SBError` with error code 0.
150 return lldb
.SBError()
152 def attach(self
, attach_info
):
153 """Simulate the scripted process attach.
156 attach_info (lldb.SBAttachInfo): The information related to the
157 process we're attaching to.
160 lldb.SBError: An `lldb.SBError` with error code 0.
162 return lldb
.SBError()
164 def resume(self
, should_stop
=True):
165 """Simulate the scripted process resume.
168 should_stop (bool): If True, resume will also force the process
169 state to stopped after running it.
172 lldb.SBError: An `lldb.SBError` with error code 0.
174 process
= self
.target
.GetProcess()
176 error
= lldb
.SBError()
177 error
.SetErrorString("Invalid process.")
180 process
.ForceScriptedState(lldb
.eStateRunning
)
182 process
.ForceScriptedState(lldb
.eStateStopped
)
183 return lldb
.SBError()
187 """Check if the scripted process is alive.
190 bool: True if scripted process is alive. False otherwise.
195 def get_scripted_thread_plugin(self
):
196 """Get scripted thread plugin name.
199 str: Name of the scripted thread plugin.
203 def get_process_metadata(self
):
204 """Get some metadata for the scripted process.
207 Dict: A dictionary containing metadata for the scripted process.
208 None if the process as no metadata.
212 def create_breakpoint(self
, addr
, error
):
213 """Create a breakpoint in the scripted process from an address.
214 This is mainly used with interactive scripted process debugging.
217 addr (int): Address at which the breakpoint should be set.
218 error (lldb.SBError): Error object.
221 SBBreakpoint: A valid breakpoint object that was created a the specified
222 address. None if the breakpoint creation failed.
224 error
.SetErrorString(
225 "%s doesn't support creating breakpoints." % self
.__class
__.__name
__
230 class ScriptedThread(metaclass
=ABCMeta
):
232 The base class for a scripted thread.
234 Most of the base class methods are `@abstractmethod` that need to be
235 overwritten by the inheriting class.
239 def __init__(self
, process
, args
):
240 """Construct a scripted thread.
243 process (ScriptedProcess/lldb.SBProcess): The process owning this thread.
244 args (lldb.SBStructuredData): A Dictionary holding arbitrary
245 key/value pairs used by the scripted thread.
248 self
.originating_process
= None
257 self
.stop_reason
= None
258 self
.register_info
= None
259 self
.register_ctx
= {}
261 self
.extended_info
= []
264 isinstance(process
, ScriptedProcess
)
265 or isinstance(process
, lldb
.SBProcess
)
266 and process
.IsValid()
268 self
.target
= process
.target
269 self
.originating_process
= process
270 self
.process
= self
.target
.GetProcess()
271 self
.get_register_info()
273 def get_thread_idx(self
):
274 """Get the scripted thread index.
277 int: The index of the scripted thread in the scripted process.
281 def get_thread_id(self
):
282 """Get the scripted thread identifier.
285 int: The identifier of the scripted thread.
290 """Get the scripted thread name.
293 str: The name of the scripted thread.
298 """Get the scripted thread state type.
300 .. code-block:: python
302 eStateStopped, ///< Process or thread is stopped and can be examined.
303 eStateRunning, ///< Process or thread is running and can't be examined.
304 eStateStepping, ///< Process or thread is in the process of stepping and
305 /// can not be examined.
306 eStateCrashed, ///< Process or thread has crashed and can be examined.
309 int: The state type of the scripted thread.
310 Returns lldb.eStateStopped by default.
312 return lldb
.eStateStopped
315 """Get the scripted thread associated queue name.
316 This method is optional.
319 str: The queue name associated with the scripted thread.
324 def get_stop_reason(self
):
325 """Get the dictionary describing the stop reason type with some data.
326 This method is optional.
329 Dict: The dictionary holding the stop reason type and the possibly
330 the stop reason data.
334 def get_stackframes(self
):
335 """Get the list of stack frames for the scripted thread.
337 .. code-block:: python
345 List[scripted_frame]: A list of `scripted_frame` dictionaries
346 containing at least for each entry, the frame index and
347 the program counter value for that frame.
348 The list can be empty.
352 def get_register_info(self
):
353 if self
.register_info
is None:
354 self
.register_info
= dict()
355 if self
.originating_process
.arch
== "x86_64":
356 self
.register_info
["sets"] = ["General Purpose Registers"]
357 self
.register_info
["registers"] = INTEL64_GPR
359 "arm64" in self
.originating_process
.arch
360 or self
.originating_process
.arch
== "aarch64"
362 self
.register_info
["sets"] = ["General Purpose Registers"]
363 self
.register_info
["registers"] = ARM64_GPR
365 raise ValueError("Unknown architecture", self
.originating_process
.arch
)
366 return self
.register_info
369 def get_register_context(self
):
370 """Get the scripted thread register context
373 str: A byte representing all register's value.
377 def get_extended_info(self
):
378 """Get scripted thread extended information.
381 List: A list containing the extended information for the scripted process.
382 None if the thread as no extended information.
384 return self
.extended_info
387 class PassthroughScriptedProcess(ScriptedProcess
):
388 driving_target
= None
389 driving_process
= None
391 def __init__(self
, exe_ctx
, args
, launched_driving_process
=True):
392 super().__init
__(exe_ctx
, args
)
394 self
.driving_target
= None
395 self
.driving_process
= None
397 self
.driving_target_idx
= args
.GetValueForKey("driving_target_idx")
398 if self
.driving_target_idx
and self
.driving_target_idx
.IsValid():
399 idx
= self
.driving_target_idx
.GetUnsignedIntegerValue(42)
400 self
.driving_target
= self
.target
.GetDebugger().GetTargetAtIndex(idx
)
402 if launched_driving_process
:
403 self
.driving_process
= self
.driving_target
.GetProcess()
404 for driving_thread
in self
.driving_process
:
405 structured_data
= lldb
.SBStructuredData()
406 structured_data
.SetFromJSON(
409 "driving_target_idx": idx
,
410 "thread_idx": driving_thread
.GetIndexID(),
415 self
.threads
[driving_thread
.GetThreadID()] = (
416 PassthroughScriptedThread(self
, structured_data
)
419 for module
in self
.driving_target
.modules
:
420 path
= module
.file.fullpath
421 load_addr
= module
.GetObjectFileHeaderAddress().GetLoadAddress(
424 self
.loaded_images
.append({"path": path
, "load_addr": load_addr
})
426 def get_memory_region_containing_address(self
, addr
):
427 mem_region
= lldb
.SBMemoryRegionInfo()
428 error
= self
.driving_process
.GetMemoryRegionInfo(addr
, mem_region
)
433 def read_memory_at_address(self
, addr
, size
, error
):
435 bytes_read
= self
.driving_process
.ReadMemory(addr
, size
, error
)
440 data
.SetDataWithOwnership(
443 self
.driving_target
.GetByteOrder(),
444 self
.driving_target
.GetAddressByteSize(),
449 def write_memory_at_address(self
, addr
, data
, error
):
450 return self
.driving_process
.WriteMemory(
451 addr
, bytearray(data
.uint8
.all()), error
454 def get_process_id(self
):
455 return self
.driving_process
.GetProcessID()
460 def get_scripted_thread_plugin(self
):
461 return f
"{PassthroughScriptedThread.__module__}.{PassthroughScriptedThread.__name__}"
464 class PassthroughScriptedThread(ScriptedThread
):
465 def __init__(self
, process
, args
):
466 super().__init
__(process
, args
)
467 driving_target_idx
= args
.GetValueForKey("driving_target_idx")
468 thread_idx
= args
.GetValueForKey("thread_idx")
470 # TODO: Change to Walrus operator (:=) with oneline if assignment
471 # Requires python 3.8
472 val
= thread_idx
.GetUnsignedIntegerValue()
476 self
.driving_target
= None
477 self
.driving_process
= None
478 self
.driving_thread
= None
480 # TODO: Change to Walrus operator (:=) with oneline if assignment
481 # Requires python 3.8
482 val
= driving_target_idx
.GetUnsignedIntegerValue()
484 self
.driving_target
= self
.target
.GetDebugger().GetTargetAtIndex(val
)
485 self
.driving_process
= self
.driving_target
.GetProcess()
486 self
.driving_thread
= self
.driving_process
.GetThreadByIndexID(self
.idx
)
488 if self
.driving_thread
:
489 self
.id = self
.driving_thread
.GetThreadID()
491 def get_thread_id(self
):
495 return f
"{PassthroughScriptedThread.__name__}.thread-{self.idx}"
497 def get_stop_reason(self
):
498 stop_reason
= {"type": lldb
.eStopReasonInvalid
, "data": {}}
502 and self
.driving_thread
.IsValid()
503 and self
.get_thread_id() == self
.driving_thread
.GetThreadID()
505 stop_reason
["type"] = lldb
.eStopReasonNone
507 # TODO: Passthrough stop reason from driving process
508 if self
.driving_thread
.GetStopReason() != lldb
.eStopReasonNone
:
509 if "arm64" in self
.originating_process
.arch
:
510 stop_reason
["type"] = lldb
.eStopReasonException
511 stop_reason
["data"]["desc"] = (
512 self
.driving_thread
.GetStopDescription(100)
514 elif self
.originating_process
.arch
== "x86_64":
515 stop_reason
["type"] = lldb
.eStopReasonSignal
516 stop_reason
["data"]["signal"] = signal
.SIGTRAP
518 stop_reason
["type"] = self
.driving_thread
.GetStopReason()
522 def get_register_context(self
):
523 if not self
.driving_thread
or self
.driving_thread
.GetNumFrames() == 0:
525 frame
= self
.driving_thread
.GetFrameAtIndex(0)
528 registerSet
= frame
.registers
# Returns an SBValueList.
529 for regs
in registerSet
:
530 if "general purpose" in regs
.name
.lower():
538 self
.register_ctx
[reg
.name
] = int(reg
.value
, base
=16)
540 return struct
.pack(f
"{len(self.register_ctx)}Q", *self
.register_ctx
.values())
1107 "alt-name": "flags",
1699 "alt-name": "flags",