1 from abc
import ABCMeta
, abstractmethod
4 import json
, struct
, signal
7 class ScriptedProcess(metaclass
=ABCMeta
):
10 The base class for a scripted process.
12 Most of the base class methods are `@abstractmethod` that need to be
13 overwritten by the inheriting class.
15 DISCLAIMER: THIS INTERFACE IS STILL UNDER DEVELOPMENT AND NOT STABLE.
16 THE METHODS EXPOSED MIGHT CHANGE IN THE FUTURE.
26 def __init__(self
, exe_ctx
, args
):
27 """Construct a scripted process.
30 exe_ctx (lldb.SBExecutionContext): The execution context for the scripted process.
31 args (lldb.SBStructuredData): A Dictionary holding arbitrary
32 key/value pairs used by the scripted process.
38 if isinstance(exe_ctx
, lldb
.SBExecutionContext
):
39 target
= exe_ctx
.target
40 if isinstance(target
, lldb
.SBTarget
) and target
.IsValid():
42 triple
= self
.target
.triple
44 self
.arch
= triple
.split("-")[0]
45 self
.dbg
= target
.GetDebugger()
46 if isinstance(args
, lldb
.SBStructuredData
) and args
.IsValid():
49 self
.loaded_images
= []
51 self
.capabilities
= {}
54 def get_capabilities(self
):
55 """Get a dictionary containing the process capabilities.
58 Dict[str:bool]: The dictionary of capability, with the capability
59 name as the key and a boolean flag as the value.
60 The dictionary can be empty.
62 return self
.capabilities
64 def get_memory_region_containing_address(self
, addr
):
65 """Get the memory region for the scripted process, containing a
69 addr (int): Address to look for in the scripted process memory
73 lldb.SBMemoryRegionInfo: The memory region containing the address.
74 None if out of bounds.
78 def get_threads_info(self
):
79 """Get the dictionary describing the process' Scripted Threads.
82 Dict: The dictionary of threads, with the thread ID as the key and
83 a Scripted Thread instance as the value.
84 The dictionary can be empty.
89 def read_memory_at_address(self
, addr
, size
, error
):
90 """Get a memory buffer from the scripted process at a certain address,
94 addr (int): Address from which we should start reading.
95 size (int): Size of the memory to read.
96 error (lldb.SBError): Error object.
99 lldb.SBData: An `lldb.SBData` buffer with the target byte size and
100 byte order storing the memory read.
104 def write_memory_at_address(self
, addr
, data
, error
):
105 """Write a buffer to the scripted process memory.
108 addr (int): Address from which we should start reading.
109 data (lldb.SBData): An `lldb.SBData` buffer to write to the
111 error (lldb.SBError): Error object.
114 size (int): Size of the memory to read.
116 error
.SetErrorString(
117 "%s doesn't support memory writes." % self
.__class
__.__name
__
121 def get_loaded_images(self
):
122 """Get the list of loaded images for the scripted process.
126 uuid = "c6ea2b64-f77c-3d27-9528-74f507b9078b",
127 path = "/usr/lib/dyld"
128 load_addr = 0xbadc0ffee
133 List[scripted_image]: A list of `scripted_image` dictionaries
134 containing for each entry the library UUID or its file path
135 and its load address.
136 None if the list is empty.
138 return self
.loaded_images
140 def get_process_id(self
):
141 """Get the scripted process identifier.
144 int: The scripted process identifier.
149 """Simulate the scripted process launch.
152 lldb.SBError: An `lldb.SBError` with error code 0.
154 return lldb
.SBError()
156 def attach(self
, attach_info
):
157 """Simulate the scripted process attach.
160 attach_info (lldb.SBAttachInfo): The information related to the
161 process we're attaching to.
164 lldb.SBError: An `lldb.SBError` with error code 0.
166 return lldb
.SBError()
168 def resume(self
, should_stop
=True):
169 """Simulate the scripted process resume.
172 should_stop (bool): If True, resume will also force the process
173 state to stopped after running it.
176 lldb.SBError: An `lldb.SBError` with error code 0.
178 process
= self
.target
.GetProcess()
180 error
= lldb
.SBError()
181 error
.SetErrorString("Invalid process.")
184 process
.ForceScriptedState(lldb
.eStateRunning
)
186 process
.ForceScriptedState(lldb
.eStateStopped
)
187 return lldb
.SBError()
191 """Check if the scripted process is alive.
194 bool: True if scripted process is alive. False otherwise.
199 def get_scripted_thread_plugin(self
):
200 """Get scripted thread plugin name.
203 str: Name of the scripted thread plugin.
207 def get_process_metadata(self
):
208 """Get some metadata for the scripted process.
211 Dict: A dictionary containing metadata for the scripted process.
212 None if the process as no metadata.
216 def create_breakpoint(self
, addr
, error
):
217 """Create a breakpoint in the scripted process from an address.
218 This is mainly used with interactive scripted process debugging.
221 addr (int): Address at which the breakpoint should be set.
222 error (lldb.SBError): Error object.
225 SBBreakpoint: A valid breakpoint object that was created a the specified
226 address. None if the breakpoint creation failed.
228 error
.SetErrorString(
229 "%s doesn't support creating breakpoints." % self
.__class
__.__name
__
234 class ScriptedThread(metaclass
=ABCMeta
):
237 The base class for a scripted thread.
239 Most of the base class methods are `@abstractmethod` that need to be
240 overwritten by the inheriting class.
242 DISCLAIMER: THIS INTERFACE IS STILL UNDER DEVELOPMENT AND NOT STABLE.
243 THE METHODS EXPOSED MIGHT CHANGE IN THE FUTURE.
247 def __init__(self
, process
, args
):
248 """Construct a scripted thread.
251 process (ScriptedProcess/lldb.SBProcess): The process owning this thread.
252 args (lldb.SBStructuredData): A Dictionary holding arbitrary
253 key/value pairs used by the scripted thread.
256 self
.originating_process
= None
265 self
.stop_reason
= None
266 self
.register_info
= None
267 self
.register_ctx
= {}
269 self
.extended_info
= []
272 isinstance(process
, ScriptedProcess
)
273 or isinstance(process
, lldb
.SBProcess
)
274 and process
.IsValid()
276 self
.target
= process
.target
277 self
.originating_process
= process
278 self
.process
= self
.target
.GetProcess()
279 self
.get_register_info()
281 def get_thread_idx(self
):
282 """Get the scripted thread index.
285 int: The index of the scripted thread in the scripted process.
289 def get_thread_id(self
):
290 """Get the scripted thread identifier.
293 int: The identifier of the scripted thread.
298 """Get the scripted thread name.
301 str: The name of the scripted thread.
306 """Get the scripted thread state type.
308 eStateStopped, ///< Process or thread is stopped and can be examined.
309 eStateRunning, ///< Process or thread is running and can't be examined.
310 eStateStepping, ///< Process or thread is in the process of stepping and can
312 eStateCrashed, ///< Process or thread has crashed and can be examined.
315 int: The state type of the scripted thread.
316 Returns lldb.eStateStopped by default.
318 return lldb
.eStateStopped
321 """Get the scripted thread associated queue name.
322 This method is optional.
325 str: The queue name associated with the scripted thread.
330 def get_stop_reason(self
):
331 """Get the dictionary describing the stop reason type with some data.
332 This method is optional.
335 Dict: The dictionary holding the stop reason type and the possibly
336 the stop reason data.
340 def get_stackframes(self
):
341 """Get the list of stack frames for the scripted thread.
351 List[scripted_frame]: A list of `scripted_frame` dictionaries
352 containing at least for each entry, the frame index and
353 the program counter value for that frame.
354 The list can be empty.
358 def get_register_info(self
):
359 if self
.register_info
is None:
360 self
.register_info
= dict()
361 if self
.originating_process
.arch
== "x86_64":
362 self
.register_info
["sets"] = ["General Purpose Registers"]
363 self
.register_info
["registers"] = INTEL64_GPR
364 elif "arm64" in self
.originating_process
.arch
:
365 self
.register_info
["sets"] = ["General Purpose Registers"]
366 self
.register_info
["registers"] = ARM64_GPR
368 raise ValueError("Unknown architecture", self
.originating_process
.arch
)
369 return self
.register_info
372 def get_register_context(self
):
373 """Get the scripted thread register context
376 str: A byte representing all register's value.
380 def get_extended_info(self
):
381 """Get scripted thread extended information.
384 List: A list containing the extended information for the scripted process.
385 None if the thread as no extended information.
387 return self
.extended_info
390 class PassthroughScriptedProcess(ScriptedProcess
):
391 driving_target
= None
392 driving_process
= None
394 def __init__(self
, exe_ctx
, args
, launched_driving_process
=True):
395 super().__init
__(exe_ctx
, args
)
397 self
.driving_target
= None
398 self
.driving_process
= None
400 self
.driving_target_idx
= args
.GetValueForKey("driving_target_idx")
401 if self
.driving_target_idx
and self
.driving_target_idx
.IsValid():
402 idx
= self
.driving_target_idx
.GetUnsignedIntegerValue(42)
403 self
.driving_target
= self
.target
.GetDebugger().GetTargetAtIndex(idx
)
405 if launched_driving_process
:
406 self
.driving_process
= self
.driving_target
.GetProcess()
407 for driving_thread
in self
.driving_process
:
408 structured_data
= lldb
.SBStructuredData()
409 structured_data
.SetFromJSON(
412 "driving_target_idx": idx
,
413 "thread_idx": driving_thread
.GetIndexID(),
419 driving_thread
.GetThreadID()
420 ] = PassthroughScriptedThread(self
, structured_data
)
422 for module
in self
.driving_target
.modules
:
423 path
= module
.file.fullpath
424 load_addr
= module
.GetObjectFileHeaderAddress().GetLoadAddress(
427 self
.loaded_images
.append({"path": path
, "load_addr": load_addr
})
429 def get_memory_region_containing_address(self
, addr
):
430 mem_region
= lldb
.SBMemoryRegionInfo()
431 error
= self
.driving_process
.GetMemoryRegionInfo(addr
, mem_region
)
436 def read_memory_at_address(self
, addr
, size
, error
):
438 bytes_read
= self
.driving_process
.ReadMemory(addr
, size
, error
)
443 data
.SetDataWithOwnership(
446 self
.driving_target
.GetByteOrder(),
447 self
.driving_target
.GetAddressByteSize(),
452 def write_memory_at_address(self
, addr
, data
, error
):
453 return self
.driving_process
.WriteMemory(
454 addr
, bytearray(data
.uint8
.all()), error
457 def get_process_id(self
):
458 return self
.driving_process
.GetProcessID()
463 def get_scripted_thread_plugin(self
):
464 return f
"{PassthroughScriptedThread.__module__}.{PassthroughScriptedThread.__name__}"
467 class PassthroughScriptedThread(ScriptedThread
):
468 def __init__(self
, process
, args
):
469 super().__init
__(process
, args
)
470 driving_target_idx
= args
.GetValueForKey("driving_target_idx")
471 thread_idx
= args
.GetValueForKey("thread_idx")
473 # TODO: Change to Walrus operator (:=) with oneline if assignment
474 # Requires python 3.8
475 val
= thread_idx
.GetUnsignedIntegerValue()
479 self
.driving_target
= None
480 self
.driving_process
= None
481 self
.driving_thread
= None
483 # TODO: Change to Walrus operator (:=) with oneline if assignment
484 # Requires python 3.8
485 val
= driving_target_idx
.GetUnsignedIntegerValue()
487 self
.driving_target
= self
.target
.GetDebugger().GetTargetAtIndex(val
)
488 self
.driving_process
= self
.driving_target
.GetProcess()
489 self
.driving_thread
= self
.driving_process
.GetThreadByIndexID(self
.idx
)
491 if self
.driving_thread
:
492 self
.id = self
.driving_thread
.GetThreadID()
494 def get_thread_id(self
):
498 return f
"{PassthroughScriptedThread.__name__}.thread-{self.idx}"
500 def get_stop_reason(self
):
501 stop_reason
= {"type": lldb
.eStopReasonInvalid
, "data": {}}
505 and self
.driving_thread
.IsValid()
506 and self
.get_thread_id() == self
.driving_thread
.GetThreadID()
508 stop_reason
["type"] = lldb
.eStopReasonNone
510 # TODO: Passthrough stop reason from driving process
511 if self
.driving_thread
.GetStopReason() != lldb
.eStopReasonNone
:
512 if "arm64" in self
.originating_process
.arch
:
513 stop_reason
["type"] = lldb
.eStopReasonException
516 ] = self
.driving_thread
.GetStopDescription(100)
517 elif self
.originating_process
.arch
== "x86_64":
518 stop_reason
["type"] = lldb
.eStopReasonSignal
519 stop_reason
["data"]["signal"] = signal
.SIGTRAP
521 stop_reason
["type"] = self
.driving_thread
.GetStopReason()
525 def get_register_context(self
):
526 if not self
.driving_thread
or self
.driving_thread
.GetNumFrames() == 0:
528 frame
= self
.driving_thread
.GetFrameAtIndex(0)
531 registerSet
= frame
.registers
# Returns an SBValueList.
532 for regs
in registerSet
:
533 if "general purpose" in regs
.name
.lower():
541 self
.register_ctx
[reg
.name
] = int(reg
.value
, base
=16)
543 return struct
.pack(f
"{len(self.register_ctx)}Q", *self
.register_ctx
.values())
1110 "alt-name": "flags",
1702 "alt-name": "flags",