This patch changes the ScriptedProcess test to use a stack-only skinny corefile as a backing store. The corefile is saved as a temporary file at the beginning of the test, and a second target is created for the ScriptedProcess. To do so, we use the SBAPI from the ScriptedProcess' python script to interact with the corefile process. This patch also makes some small adjustments to the other ScriptedProcess scripts to resolve some inconsistencies and removes the raw memory dump that was previously checked in. Differential Revision: https://reviews.llvm.org/D112047 Signed-off-by: Med Ismail Bennani <medismail.bennani@gmail.com>
331 lines
12 KiB
Python
331 lines
12 KiB
Python
from abc import ABCMeta, abstractmethod
|
|
import six
|
|
|
|
import lldb
|
|
|
|
@six.add_metaclass(ABCMeta)
|
|
class ScriptedProcess:
|
|
|
|
"""
|
|
The base class for a scripted process.
|
|
|
|
Most of the base class methods are `@abstractmethod` that need to be
|
|
overwritten by the inheriting class.
|
|
|
|
DISCLAIMER: THIS INTERFACE IS STILL UNDER DEVELOPMENT AND NOT STABLE.
|
|
THE METHODS EXPOSED MIGHT CHANGE IN THE FUTURE.
|
|
"""
|
|
|
|
memory_regions = None
|
|
stack_memory_dump = None
|
|
loaded_images = None
|
|
|
|
@abstractmethod
|
|
def __init__(self, target, args):
|
|
""" Construct a scripted process.
|
|
|
|
Args:
|
|
target (lldb.SBTarget): The target launching the scripted process.
|
|
args (lldb.SBStructuredData): A Dictionary holding arbitrary
|
|
key/value pairs used by the scripted process.
|
|
"""
|
|
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
|
|
|
|
@abstractmethod
|
|
def get_memory_region_containing_address(self, addr):
|
|
""" Get the memory region for the scripted process, containing a
|
|
specific address.
|
|
|
|
Args:
|
|
addr (int): Address to look for in the scripted process memory
|
|
regions.
|
|
|
|
Returns:
|
|
lldb.SBMemoryRegionInfo: The memory region containing the address.
|
|
None if out of bounds.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_thread_with_id(self, tid):
|
|
""" Get the scripted process thread with a specific ID.
|
|
|
|
Args:
|
|
tid (int): Thread ID to look for in the scripted process.
|
|
|
|
Returns:
|
|
Dict: The thread represented as a dictionary, withr the
|
|
tid thread ID. None if tid doesn't match any of the scripted
|
|
process threads.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_registers_for_thread(self, tid):
|
|
""" Get the register context dictionary for a certain thread of
|
|
the scripted process.
|
|
|
|
Args:
|
|
tid (int): Thread ID for the thread's register context.
|
|
|
|
Returns:
|
|
Dict: The register context represented as a dictionary, for the
|
|
tid thread. None if tid doesn't match any of the scripted
|
|
process threads.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def read_memory_at_address(self, addr, size):
|
|
""" Get a memory buffer from the scripted process at a certain address,
|
|
of a certain size.
|
|
|
|
Args:
|
|
addr (int): Address from which we should start reading.
|
|
size (int): Size of the memory to read.
|
|
|
|
Returns:
|
|
lldb.SBData: An `lldb.SBData` buffer with the target byte size and
|
|
byte order storing the memory read.
|
|
"""
|
|
pass
|
|
|
|
def get_loaded_images(self):
|
|
""" Get the list of loaded images for the scripted process.
|
|
|
|
```
|
|
class ScriptedProcessImage:
|
|
def __init__(name, file_spec, uuid, load_address):
|
|
self.name = name
|
|
self.file_spec = file_spec
|
|
self.uuid = uuid
|
|
self.load_address = load_address
|
|
```
|
|
|
|
Returns:
|
|
List[ScriptedProcessImage]: A list of `ScriptedProcessImage`
|
|
containing for each entry, the name of the library, a UUID,
|
|
an `lldb.SBFileSpec` and a load address.
|
|
None if the list is empty.
|
|
"""
|
|
return self.loaded_images
|
|
|
|
def get_process_id(self):
|
|
""" Get the scripted process identifier.
|
|
|
|
Returns:
|
|
int: The scripted process identifier.
|
|
"""
|
|
return 0
|
|
|
|
|
|
def launch(self):
|
|
""" Simulate the scripted process launch.
|
|
|
|
Returns:
|
|
lldb.SBError: An `lldb.SBError` with error code 0.
|
|
"""
|
|
return lldb.SBError()
|
|
|
|
def resume(self):
|
|
""" Simulate the scripted process resume.
|
|
|
|
Returns:
|
|
lldb.SBError: An `lldb.SBError` with error code 0.
|
|
"""
|
|
return lldb.SBError()
|
|
|
|
@abstractmethod
|
|
def should_stop(self):
|
|
""" Check if the scripted process plugin should produce the stop event.
|
|
|
|
Returns:
|
|
bool: True if scripted process should broadcast a stop event.
|
|
False otherwise.
|
|
"""
|
|
pass
|
|
|
|
def stop(self):
|
|
""" Trigger the scripted process stop.
|
|
|
|
Returns:
|
|
lldb.SBError: An `lldb.SBError` with error code 0.
|
|
"""
|
|
return lldb.SBError()
|
|
|
|
@abstractmethod
|
|
def is_alive(self):
|
|
""" Check if the scripted process is alive.
|
|
|
|
Returns:
|
|
bool: True if scripted process is alive. False otherwise.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_scripted_thread_plugin(self):
|
|
""" Get scripted thread plugin name.
|
|
|
|
Returns:
|
|
str: Name of the scripted thread plugin.
|
|
"""
|
|
return None
|
|
|
|
@six.add_metaclass(ABCMeta)
|
|
class ScriptedThread:
|
|
|
|
"""
|
|
The base class for a scripted thread.
|
|
|
|
Most of the base class methods are `@abstractmethod` that need to be
|
|
overwritten by the inheriting class.
|
|
|
|
DISCLAIMER: THIS INTERFACE IS STILL UNDER DEVELOPMENT AND NOT STABLE.
|
|
THE METHODS EXPOSED MIGHT CHANGE IN THE FUTURE.
|
|
"""
|
|
|
|
@abstractmethod
|
|
def __init__(self, process, args):
|
|
""" Construct a scripted thread.
|
|
|
|
Args:
|
|
process (lldb.SBProcess): The scripted process owning this thread.
|
|
args (lldb.SBStructuredData): A Dictionary holding arbitrary
|
|
key/value pairs used by the scripted thread.
|
|
"""
|
|
self.target = None
|
|
self.process = None
|
|
self.args = None
|
|
if isinstance(process, lldb.SBProcess) and process.IsValid():
|
|
self.process = process
|
|
self.target = process.GetTarget()
|
|
|
|
self.id = None
|
|
self.name = None
|
|
self.queue = None
|
|
self.state = None
|
|
self.stop_reason = None
|
|
self.register_info = None
|
|
self.register_ctx = {}
|
|
self.frames = []
|
|
|
|
@abstractmethod
|
|
def get_thread_id(self):
|
|
""" Get the scripted thread identifier.
|
|
|
|
Returns:
|
|
int: The identifier of the scripted thread.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_name(self):
|
|
""" Get the scripted thread name.
|
|
|
|
Returns:
|
|
str: The name of the scripted thread.
|
|
"""
|
|
pass
|
|
|
|
def get_state(self):
|
|
""" Get the scripted thread state type.
|
|
|
|
eStateStopped, ///< Process or thread is stopped and can be examined.
|
|
eStateRunning, ///< Process or thread is running and can't be examined.
|
|
eStateStepping, ///< Process or thread is in the process of stepping and can
|
|
/// not be examined.
|
|
|
|
Returns:
|
|
int: The state type of the scripted thread.
|
|
Returns lldb.eStateStopped by default.
|
|
"""
|
|
return lldb.eStateStopped
|
|
|
|
def get_queue(self):
|
|
""" Get the scripted thread associated queue name.
|
|
This method is optional.
|
|
|
|
Returns:
|
|
str: The queue name associated with the scripted thread.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_stop_reason(self):
|
|
""" Get the dictionary describing the stop reason type with some data.
|
|
This method is optional.
|
|
|
|
Returns:
|
|
Dict: The dictionary holding the stop reason type and the possibly
|
|
the stop reason data.
|
|
"""
|
|
pass
|
|
|
|
def get_stackframes(self):
|
|
""" Get the list of stack frames for the scripted thread.
|
|
|
|
```
|
|
class ScriptedStackFrame:
|
|
def __init__(idx, cfa, pc, symbol_ctx):
|
|
self.idx = idx
|
|
self.cfa = cfa
|
|
self.pc = pc
|
|
self.symbol_ctx = symbol_ctx
|
|
```
|
|
|
|
Returns:
|
|
List[ScriptedFrame]: A list of `ScriptedStackFrame`
|
|
containing for each entry, the frame index, the canonical
|
|
frame address, the program counter value for that frame
|
|
and a symbol context.
|
|
None if the list is empty.
|
|
"""
|
|
return 0
|
|
|
|
def get_register_info(self):
|
|
if self.register_info is None:
|
|
self.register_info = dict()
|
|
triple = self.target.triple
|
|
if triple:
|
|
arch = triple.split('-')[0]
|
|
if arch == 'x86_64':
|
|
self.register_info['sets'] = ['General Purpose Registers']
|
|
self.register_info['registers'] = [
|
|
{'name': 'rax', 'bitsize': 64, 'offset': 0, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 0, 'dwarf': 0},
|
|
{'name': 'rbx', 'bitsize': 64, 'offset': 8, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 3, 'dwarf': 3},
|
|
{'name': 'rcx', 'bitsize': 64, 'offset': 16, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 2, 'dwarf': 2, 'generic': 'arg4', 'alt-name': 'arg4', },
|
|
{'name': 'rdx', 'bitsize': 64, 'offset': 24, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 1, 'dwarf': 1, 'generic': 'arg3', 'alt-name': 'arg3', },
|
|
{'name': 'rdi', 'bitsize': 64, 'offset': 32, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 5, 'dwarf': 5, 'generic': 'arg1', 'alt-name': 'arg1', },
|
|
{'name': 'rsi', 'bitsize': 64, 'offset': 40, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 4, 'dwarf': 4, 'generic': 'arg2', 'alt-name': 'arg2', },
|
|
{'name': 'rbp', 'bitsize': 64, 'offset': 48, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 6, 'dwarf': 6, 'generic': 'fp', 'alt-name': 'fp', },
|
|
{'name': 'rsp', 'bitsize': 64, 'offset': 56, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 7, 'dwarf': 7, 'generic': 'sp', 'alt-name': 'sp', },
|
|
{'name': 'r8', 'bitsize': 64, 'offset': 64, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 8, 'dwarf': 8, 'generic': 'arg5', 'alt-name': 'arg5', },
|
|
{'name': 'r9', 'bitsize': 64, 'offset': 72, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 9, 'dwarf': 9, 'generic': 'arg6', 'alt-name': 'arg6', },
|
|
{'name': 'r10', 'bitsize': 64, 'offset': 80, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 10, 'dwarf': 10},
|
|
{'name': 'r11', 'bitsize': 64, 'offset': 88, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 11, 'dwarf': 11},
|
|
{'name': 'r12', 'bitsize': 64, 'offset': 96, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 12, 'dwarf': 12},
|
|
{'name': 'r13', 'bitsize': 64, 'offset': 104, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 13, 'dwarf': 13},
|
|
{'name': 'r14', 'bitsize': 64, 'offset': 112, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 14, 'dwarf': 14},
|
|
{'name': 'r15', 'bitsize': 64, 'offset': 120, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 15, 'dwarf': 15},
|
|
{'name': 'rip', 'bitsize': 64, 'offset': 128, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 16, 'dwarf': 16, 'generic': 'pc', 'alt-name': 'pc'},
|
|
{'name': 'rflags', 'bitsize': 64, 'offset': 136, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'generic': 'flags', 'alt-name': 'flags'},
|
|
{'name': 'cs', 'bitsize': 64, 'offset': 144, 'encoding': 'uint', 'format': 'hex', 'set': 0},
|
|
{'name': 'fs', 'bitsize': 64, 'offset': 152, 'encoding': 'uint', 'format': 'hex', 'set': 0},
|
|
{'name': 'gs', 'bitsize': 64, 'offset': 160, 'encoding': 'uint', 'format': 'hex', 'set': 0},
|
|
]
|
|
return self.register_info
|
|
|
|
@abstractmethod
|
|
def get_register_context(self):
|
|
""" Get the scripted thread register context
|
|
|
|
Returns:
|
|
str: A byte representing all register's value.
|
|
"""
|
|
pass
|