mirror of
https://github.com/avatartwo/avatar2.git
synced 2026-10-12 02:11:18 +00:00
INTFowarder & HWRunner introduction (#125)
* WIP: Getting coresight to work * Fixing Coresight protocol and interrupt forwarding TO QEmu * WIP interrupts fixing my own mistakes * Added interrupt state transfer * Reactivated interrupt forwarding, works with timer but the alarm runs out of sync so it only works for a couple of seconds * Fixing crash due to protocol shutdown order interrupt protocol depends on memory * Fixed issues with memory protocols * Fixed IVT setup in interrupt protocol * WIP: Added interrupt recording plugin and protocol, not complete * WIP: Fixed the stub to include an end of buffer flag * Interrupt recording protocol and plugin v1 * Interrupt recording protocol cleanup * Added rudimentary support for ARM Cortex M4 (without FPU registers) * Interrupt recording without manipulating main loop control flow * WIP: USB rehosting * Updated interrupt messages to include the IRQ-Address * IRQ fixing * IRQ fixing; Reordering of setup code and removal of IRQ init because it might not complete before the first IRQ fires * IRQ message passing fixes * Allow plugins to get configuration parameters at load time * Switch to trace approach for interrupt rehosting, to capture quickly firing interrupts * Allow avatar peripherals to know on what target they got called * WIP: Prevent interrupt interleaving * Fixed wrong program counter * WIP: picow-blink rehosting, runs much further but crashes because it runs a re-init of the CYW43 * Allow OpenOCD target to have a gdb binary for symbols * WIP HAL calling * WIP HAL calling step 2 * WIP HAL calling step 2- THIS IS BROKEN AF * Finally a working version * First working HAL implementation for send-uart * Make HALCaller config more expressive * Made HAL calls more generic, allow for up more arguments, allow void function returns, allow pointer/mem bocks for function return * OpenOCD protocol, rework raw memory read/write for higher performance in memory transfers * Trigger HAL Exit after Hardware reset is complete * Make protocol usable as a signaled target continue protocol * WIP: PICOW rehosting * WIP: Software interrupt rehosting support by using the hardware in the loop as the only source of interrupts * WIP: USB rehosting, cleanup of software interrupt stuff * Cleanup interrupt recording * HAL only deal with interrupt protocol if present * HALCaller rename to HWRunner * HWRunner OpenOCD fix; WiFi experiments final * HWRunner naming cleanup * Interrupt Recorder naming cleanup * Interrupt Forwarding naming cleanup * Cleanup and fixup of refactorings * INTFowrader & HWRunner summary * Final cleanup * Remove union types for type hint Union in type hint is introduced with PEP 604 in python 3.10. However, we aim to support python 3.6. * Remove crashing log * Remove crashing log line when using pypanda --------- Co-authored-by: Florian Albrecht <[email protected]> Co-authored-by: rawsample <[email protected]>
This commit is contained in:
co-authored by
Florian Albrecht
rawsample
parent
a64a8b6775
commit
e29b338b8b
@@ -44,8 +44,6 @@ class ARM_CORTEX_M3(ARM):
|
||||
qemu_name = 'arm'
|
||||
gdb_name = 'arm'
|
||||
|
||||
capstone_arch = CS_ARCH_ARM
|
||||
keystone_arch = KS_ARCH_ARM
|
||||
capstone_mode = CS_MODE_LITTLE_ENDIAN | CS_MODE_THUMB | CS_MODE_MCLASS
|
||||
keystone_arch = KS_ARCH_ARM
|
||||
keystone_mode = KS_MODE_LITTLE_ENDIAN | KS_MODE_THUMB
|
||||
@@ -86,7 +84,8 @@ class ARM_CORTEX_M3(ARM):
|
||||
pass
|
||||
|
||||
|
||||
ARMV7M = ARM_CORTEX_M3
|
||||
|
||||
ARMV7M = [ARM_CORTEX_M3]
|
||||
|
||||
|
||||
class ARMBE(ARM):
|
||||
|
||||
+33
-33
@@ -36,7 +36,7 @@ class Avatar(Thread):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, arch=ARM, cpu_model=None, output_directory=None, log_to_stdout=True, configure_logging=True
|
||||
self, arch=ARM, cpu_model=None, output_directory=None, log_to_stdout=True, configure_logging=True
|
||||
):
|
||||
super(Avatar, self).__init__()
|
||||
|
||||
@@ -70,7 +70,7 @@ class Avatar(Thread):
|
||||
makedirs(self.output_directory)
|
||||
|
||||
self.log = logging.getLogger("avatar")
|
||||
|
||||
|
||||
if configure_logging:
|
||||
format = "%(asctime)s | %(name)s.%(levelname)s | %(message)s"
|
||||
|
||||
@@ -130,8 +130,8 @@ class Avatar(Thread):
|
||||
if not tname in self.targets:
|
||||
raise Exception(
|
||||
(
|
||||
"Requested target %s not found in config. "
|
||||
"Aborting." % tname
|
||||
"Requested target %s not found in config. "
|
||||
"Aborting." % tname
|
||||
)
|
||||
)
|
||||
mr["forwarded_to"] = self.targets[tname]
|
||||
@@ -183,7 +183,7 @@ class Avatar(Thread):
|
||||
self.log.info("Avatar Received SIGINT")
|
||||
self.sigint_handler()
|
||||
|
||||
def load_plugin(self, name, local=False):
|
||||
def load_plugin(self, name, local=False, *args, **kwargs):
|
||||
if local is True:
|
||||
plugin = __import__(name, fromlist=["."])
|
||||
else:
|
||||
@@ -191,7 +191,7 @@ class Avatar(Thread):
|
||||
"avatar2.plugins.%s" % name, fromlist=["avatar2.plugins"]
|
||||
)
|
||||
|
||||
plugin.load_plugin(self)
|
||||
plugin.load_plugin(self, *args, **kwargs)
|
||||
self.loaded_plugins += [name]
|
||||
|
||||
@watch("AddTarget")
|
||||
@@ -233,21 +233,21 @@ class Avatar(Thread):
|
||||
t[1].init()
|
||||
|
||||
def add_memory_range(
|
||||
self,
|
||||
address,
|
||||
size,
|
||||
name=None,
|
||||
permissions="rwx",
|
||||
file=None,
|
||||
file_offset=None,
|
||||
file_bytes=None,
|
||||
forwarded=False,
|
||||
forwarded_to=None,
|
||||
emulate=None,
|
||||
interval_tree=None,
|
||||
inline=False,
|
||||
overwrite=False,
|
||||
**kwargs
|
||||
self,
|
||||
address,
|
||||
size,
|
||||
name=None,
|
||||
permissions="rwx",
|
||||
file=None,
|
||||
file_offset=None,
|
||||
file_bytes=None,
|
||||
forwarded=False,
|
||||
forwarded_to=None,
|
||||
emulate=None,
|
||||
interval_tree=None,
|
||||
inline=False,
|
||||
overwrite=False,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Adds a memory range to avatar
|
||||
@@ -294,7 +294,7 @@ class Avatar(Thread):
|
||||
**kwargs
|
||||
)
|
||||
|
||||
mr_set = memory_ranges[address : address + size]
|
||||
mr_set = memory_ranges[address: address + size]
|
||||
if overwrite is True and len(mr_set) > 0:
|
||||
start = min(mr_set, key=lambda x: x.begin).begin
|
||||
end = max(mr_set, key=lambda x: x.end).end
|
||||
@@ -312,7 +312,7 @@ class Avatar(Thread):
|
||||
interval.data.size,
|
||||
)
|
||||
|
||||
memory_ranges[address : address + size] = m
|
||||
memory_ranges[address: address + size] = m
|
||||
|
||||
return m
|
||||
|
||||
@@ -354,8 +354,8 @@ class Avatar(Thread):
|
||||
"""
|
||||
|
||||
if (
|
||||
from_target.state & TargetStates.STOPPED == 0
|
||||
or to_target.state & TargetStates.STOPPED == 0
|
||||
from_target.state & TargetStates.STOPPED == 0
|
||||
or to_target.state & TargetStates.STOPPED == 0
|
||||
):
|
||||
raise Exception(
|
||||
"Targets must be stopped for State Transfer, \
|
||||
@@ -403,6 +403,7 @@ class Avatar(Thread):
|
||||
def _handle_breakpoint_hit_message(self, message):
|
||||
self.log.info("Breakpoint hit for Target: %s" % message.origin.name)
|
||||
self._handle_update_state_message(message)
|
||||
|
||||
# Breakpoints are two stages: SYNCING | STOPPED -> HandleBreakpoint -> STOPPED
|
||||
# This makes sure that all handlers are complete before stopping and breaking wait()
|
||||
|
||||
@@ -446,8 +447,8 @@ class Avatar(Thread):
|
||||
try:
|
||||
kwargs = {"num_words": message.num_words, "raw": message.raw}
|
||||
if (
|
||||
hasattr(range.forwarded_to, "read_supports_pc")
|
||||
and range.forwarded_to.read_supports_pc is True
|
||||
hasattr(range.forwarded_to, "read_supports_pc")
|
||||
and range.forwarded_to.read_supports_pc is True
|
||||
):
|
||||
kwargs["pc"] = message.pc
|
||||
|
||||
@@ -457,13 +458,13 @@ class Avatar(Thread):
|
||||
if not message.raw and message.num_words == 1 and not isinstance(mem, int):
|
||||
raise Exception(
|
||||
(
|
||||
"Forwarded read returned data of type %s "
|
||||
"(expected: int)" % type(mem)
|
||||
"Forwarded read returned data of type %s "
|
||||
"(expected: int)" % type(mem)
|
||||
)
|
||||
)
|
||||
success = True
|
||||
except Exception as e:
|
||||
self.log.exception("RemoteMemoryRead failed: %s" % e)
|
||||
self.log.exception("RemoteMemoryRead from %s failed: %s" % (range.forwarded_to, e))
|
||||
mem = -1
|
||||
success = False
|
||||
|
||||
@@ -484,8 +485,8 @@ class Avatar(Thread):
|
||||
|
||||
kwargs = {}
|
||||
if (
|
||||
hasattr(mem_range.forwarded_to, "write_supports_pc")
|
||||
and mem_range.forwarded_to.write_supports_pc is True
|
||||
hasattr(mem_range.forwarded_to, "write_supports_pc")
|
||||
and mem_range.forwarded_to.write_supports_pc is True
|
||||
):
|
||||
kwargs["pc"] = message.pc
|
||||
|
||||
@@ -515,7 +516,6 @@ class Avatar(Thread):
|
||||
"Avatar received %s. Queue-Status: %d/%d"
|
||||
% (message, self.queue.qsize(), self.fast_queue.qsize())
|
||||
)
|
||||
|
||||
handler = self.message_handlers.get(message.__class__, None)
|
||||
if handler is None:
|
||||
raise Exception("No handler for Avatar-message %s registered" % message)
|
||||
|
||||
@@ -33,7 +33,7 @@ class MemoryRange(object):
|
||||
self.name = (
|
||||
name
|
||||
if name is not None else
|
||||
"mem_range_0x{:08x}_0x{:08x}".format(address, address+size))
|
||||
"mem_range_0x{:08x}_0x{:08x}".format(address, address + size))
|
||||
self.permissions = permissions
|
||||
self.file = abspath(file) if file is not None else None
|
||||
self.file_offset = file_offset if file_offset is not None else None
|
||||
@@ -44,23 +44,25 @@ class MemoryRange(object):
|
||||
self.forwarded_to = forwarded_to
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
|
||||
def dictify(self):
|
||||
"""
|
||||
Returns the memory range as *printable* dictionary
|
||||
"""
|
||||
from .avatar2 import Avatar # we cannot do this import at top-level
|
||||
from .avatar2 import Avatar # we cannot do this import at top-level
|
||||
# Assumption: dicts saved in mrs are of primitive types only
|
||||
expected_types = (str, bool, int, dict, AvatarPeripheral, Avatar, list)
|
||||
if version_info < (3, 0): expected_types += (unicode, )
|
||||
if version_info < (3, 0): expected_types += (unicode,)
|
||||
|
||||
tmp_dict = dict(self.__dict__)
|
||||
mr_dict = {}
|
||||
while tmp_dict != {}:
|
||||
k, v = tmp_dict.popitem()
|
||||
if v is None or False: continue
|
||||
elif k == 'forwarded_to': v = v.name
|
||||
# TODO handle emulate
|
||||
if v is None or False:
|
||||
continue
|
||||
elif k == 'forwarded_to':
|
||||
v = v.name
|
||||
if k == 'emulate_config': # Skip config for emulated peripheral
|
||||
continue
|
||||
if not isinstance(v, expected_types):
|
||||
raise Exception(
|
||||
"Unsupported type %s for dictifying %s for mem_range at 0x%x"
|
||||
@@ -71,5 +73,3 @@ class MemoryRange(object):
|
||||
v = v.__class__.__name__
|
||||
mr_dict[k] = v
|
||||
return mr_dict
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from .plugins.arm.hal import HWFunction
|
||||
|
||||
|
||||
class AvatarMessage(object):
|
||||
@@ -23,6 +24,7 @@ class BreakpointHitMessage(UpdateStateMessage):
|
||||
self.breakpoint_number = breakpoint_number
|
||||
self.address = address
|
||||
|
||||
|
||||
class SyscallCatchedMessage(BreakpointHitMessage):
|
||||
def __init__(self, origin, breakpoint_number, address, type='entry'):
|
||||
super(self.__class__, self).__init__(origin, breakpoint_number, address)
|
||||
@@ -40,6 +42,7 @@ class RemoteMemoryReadMessage(AvatarMessage):
|
||||
self.num_words = 1
|
||||
self.raw = False
|
||||
|
||||
|
||||
class RemoteMemoryWriteMessage(AvatarMessage):
|
||||
def __init__(self, origin, id, pc, address, value, size, dst=None):
|
||||
super(self.__class__, self).__init__(origin)
|
||||
@@ -50,12 +53,14 @@ class RemoteMemoryWriteMessage(AvatarMessage):
|
||||
self.size = size
|
||||
self.dst = dst
|
||||
|
||||
|
||||
class RemoteInterruptEnterMessage(AvatarMessage):
|
||||
def __init__(self, origin, id, interrupt_num):
|
||||
super(self.__class__, self).__init__(origin)
|
||||
self.id = id
|
||||
self.interrupt_num = interrupt_num
|
||||
|
||||
|
||||
class RemoteInterruptExitMessage(AvatarMessage):
|
||||
def __init__(self, origin, id, transition_type, interrupt_num):
|
||||
super(self.__class__, self).__init__(origin)
|
||||
@@ -64,4 +69,47 @@ class RemoteInterruptExitMessage(AvatarMessage):
|
||||
self.interrupt_num = interrupt_num
|
||||
|
||||
|
||||
class TargetInterruptEnterMessage(AvatarMessage):
|
||||
def __init__(self, origin, id, interrupt_num, isr_addr):
|
||||
super(self.__class__, self).__init__(origin)
|
||||
self.id = id
|
||||
self.interrupt_num = interrupt_num
|
||||
self.isr_addr = isr_addr
|
||||
|
||||
|
||||
class TargetInterruptExitMessage(AvatarMessage):
|
||||
def __init__(self, origin, id, interrupt_num, isr_addr):
|
||||
super(self.__class__, self).__init__(origin)
|
||||
self.id = id
|
||||
self.interrupt_num = interrupt_num
|
||||
self.isr_addr = isr_addr
|
||||
|
||||
|
||||
class HWEnterMessage(AvatarMessage):
|
||||
def __init__(self, origin, function: HWFunction, return_address: int):
|
||||
super(self.__class__, self).__init__(origin)
|
||||
self.function = function
|
||||
self.return_address = return_address
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.__class__.__name__} from {self.origin.name} returning to 0x{self.return_address:x}"
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
||||
class HWExitMessage(AvatarMessage):
|
||||
def __init__(self, origin, function: HWFunction, return_val: int, return_address: int):
|
||||
super(self.__class__, self).__init__(origin)
|
||||
self.function = function
|
||||
self.return_val = return_val
|
||||
self.return_address = return_address
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.__class__.__name__} from {self.origin.name} to 0x{self.return_address:x} with return_value 0x{self.return_val:x}"
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
||||
from .targets.target import TargetStates
|
||||
|
||||
@@ -9,7 +9,6 @@ else:
|
||||
from cached_property import cached_property
|
||||
|
||||
|
||||
|
||||
class AvatarPeripheral(object):
|
||||
def __init__(self, name, address, size, **kwargs):
|
||||
self.name = name if name else "%s_%x" % (self.__class__.__name__, address)
|
||||
@@ -39,7 +38,7 @@ class AvatarPeripheral(object):
|
||||
"""
|
||||
pass
|
||||
|
||||
def write_memory(self, address, size, value, num_words=1, raw=False, pc=0):
|
||||
def write_memory(self, address, size, value, num_words=1, raw=False, origin=None, pc=0):
|
||||
|
||||
if num_words != 1 or raw is True:
|
||||
raise Exception(
|
||||
@@ -48,7 +47,7 @@ class AvatarPeripheral(object):
|
||||
)
|
||||
|
||||
offset = address - self.address
|
||||
intervals = self.write_handler[offset : offset + size]
|
||||
intervals = self.write_handler[offset: offset + size]
|
||||
if intervals == set():
|
||||
raise Exception(
|
||||
"No write handler for peripheral %s at offset %d \
|
||||
@@ -62,10 +61,12 @@ class AvatarPeripheral(object):
|
||||
% (self.name, offset)
|
||||
)
|
||||
|
||||
kwargs = {} if self.write_supports_pc is False else {"pc": pc}
|
||||
kwargs = {'origin': origin}
|
||||
if self.write_supports_pc is True:
|
||||
kwargs["pc"] = pc
|
||||
return intervals.pop().data(offset, size, value, **kwargs)
|
||||
|
||||
def read_memory(self, address, size, num_words=1, raw=False, pc=0):
|
||||
def read_memory(self, address, size, num_words=1, raw=False, origin=None, pc=0):
|
||||
if num_words != 1 or raw is True:
|
||||
raise Exception(
|
||||
"read_memory for AvatarPeripheral does not support \
|
||||
@@ -73,7 +74,7 @@ class AvatarPeripheral(object):
|
||||
)
|
||||
|
||||
offset = address - self.address
|
||||
intervals = self.read_handler[offset : offset + size]
|
||||
intervals = self.read_handler[offset: offset + size]
|
||||
|
||||
if intervals == set():
|
||||
raise Exception(
|
||||
@@ -87,5 +88,7 @@ class AvatarPeripheral(object):
|
||||
at offset %d"
|
||||
% (self.name, offset)
|
||||
)
|
||||
kwargs = {} if self.write_supports_pc is False else {"pc": pc}
|
||||
kwargs = {'origin': origin}
|
||||
if self.write_supports_pc is True:
|
||||
kwargs["pc"] = pc
|
||||
return intervals.pop().data(offset, size, **kwargs)
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
from avatar2.peripherals import AvatarPeripheral
|
||||
import logging
|
||||
|
||||
|
||||
class InspectionPeripheral(AvatarPeripheral):
|
||||
"""AvatarPeripheral to inspect all accesses to its memory region"""
|
||||
|
||||
def __init__(self, name, address, size):
|
||||
super().__init__(name, address, size)
|
||||
self.read_handler[0:size] = self.dispatch_read
|
||||
self.write_handler[0:size] = self.dispatch_write
|
||||
self.log = logging.getLogger('emulated')
|
||||
|
||||
def dispatch_read(self, offset, size, *args, **kwargs):
|
||||
self.log.debug(
|
||||
f"Memory read at 0x{self.address:x} 0x{offset:x} with size {size} from {kwargs['origin'].__class__.__name__}")
|
||||
return kwargs['origin'].protocols.memory.read_memory(self.address + offset, size)
|
||||
|
||||
def dispatch_write(self, offset, size, value, *args, **kwargs):
|
||||
self.log.debug(
|
||||
f"Memory write at 0x{self.address:x} 0x{offset:x} with size {size} and value 0x{value:x} from {kwargs['origin'].__class__.__name__}")
|
||||
return kwargs['origin'].protocols.memory.write_memory(self.address + offset, size, value)
|
||||
|
||||
|
||||
class PartialForwardingPeripheral(AvatarPeripheral):
|
||||
"""AvatarPeripheral to forward all accesses except some to its memory region"""
|
||||
read_supports_pc = True
|
||||
write_supports_pc = True
|
||||
|
||||
def __init__(self, name, address, size, emulate_config):
|
||||
super().__init__(name, address, size)
|
||||
self.read_handler[0:size] = self.dispatch_read
|
||||
self.write_handler[0:size] = self.dispatch_write
|
||||
self.log = logging.getLogger('emulated')
|
||||
|
||||
self.forward_to = None if 'forward_to' not in emulate_config else emulate_config['forward_to']
|
||||
self.ignore_write_offsets = [] if 'ignore_forward_write_offsets' not in emulate_config else emulate_config[
|
||||
'ignore_forward_write_offsets']
|
||||
self.ignore_read_offsets = [] if 'ignore_forward_read_offsets' not in emulate_config else emulate_config[
|
||||
'ignore_forward_read_offsets']
|
||||
self.ignore = [] if 'ignore' not in emulate_config else emulate_config['ignore']
|
||||
|
||||
def dispatch_read(self, offset, size, pc, *args, **kwargs):
|
||||
if offset in self.ignore:
|
||||
self.log.warning(f"DROPPING read to 0x{self.address + offset:x}")
|
||||
return 0
|
||||
elif offset in self.ignore_read_offsets:
|
||||
self.log.debug(
|
||||
f"pc=0x{pc:x} NOT forwarded memory read at 0x{self.address + offset:x} with size {size} of {kwargs['origin'].__class__.__name__}")
|
||||
return kwargs['origin'].protocols.memory.read_memory(self.address + offset, size)
|
||||
else:
|
||||
self.log.debug(f"pc=0x{pc:x} Forwarding read at {hex(self.address + offset)} with size {size}")
|
||||
return self.forward_to.protocols.memory.read_memory(self.address + offset, size)
|
||||
|
||||
def dispatch_write(self, offset, size, value, pc, *args, **kwargs):
|
||||
if offset in self.ignore:
|
||||
self.log.warning(f"DROPPING write to 0x{self.address + offset:x} with {value} (0x{value:x})")
|
||||
return (True, True)
|
||||
elif offset in self.ignore_write_offsets:
|
||||
self.log.debug(
|
||||
f"pc=0x{pc:x} NOT forwarded memory write at 0x{self.address + offset:x} with size {size} and value {value} (0x{value:x}) of {kwargs['origin'].__class__.__name__}")
|
||||
return kwargs['origin'].protocols.memory.write_memory(self.address + offset, size, value)
|
||||
else:
|
||||
self.log.debug(
|
||||
f"pc=0x{pc:x} Forwarding write at {hex(self.address + offset)} with value {value} (0x{value:x})")
|
||||
return self.forward_to.protocols.memory.write_memory(self.address + offset, size,
|
||||
value), True # True to signal QEmu to not process this register write further
|
||||
|
||||
|
||||
class PeripheralTracePeripheral(AvatarPeripheral):
|
||||
"""AvatarPeripheral to forward all accesses except some to its memory region"""
|
||||
|
||||
read_supports_pc = True
|
||||
write_supports_pc = True
|
||||
|
||||
def __init__(self, name, address, size, emulate_config):
|
||||
super().__init__(name, address, size)
|
||||
self.read_handler[0:size] = self.dispatch_read
|
||||
self.write_handler[0:size] = self.dispatch_write
|
||||
self.log = logging.getLogger('emulated')
|
||||
|
||||
self.forward_to = None if 'forward_to' not in emulate_config else emulate_config['forward_to']
|
||||
self.peripheral_register = {} if 'peripheral_register' not in emulate_config else emulate_config[
|
||||
'peripheral_register']
|
||||
self.register_offsets = self.peripheral_register.keys()
|
||||
self.trace = []
|
||||
|
||||
def dispatch_read(self, offset, size, *args, **kwargs):
|
||||
reg = self.peripheral_register[offset] if offset in self.peripheral_register else "UNKNOWN"
|
||||
value = self.forward_to.protocols.memory.read_memory(self.address + offset, size)
|
||||
self.log.debug(
|
||||
f"pc=0x{kwargs['pc']:x} : Register read of {reg} at 0x{offset:04x} with value {value} (0x{value:x})")
|
||||
self.trace.append(('read', offset, reg, value))
|
||||
return value
|
||||
|
||||
def dispatch_write(self, offset, size, value, *args, **kwargs):
|
||||
reg = self.peripheral_register[offset] if offset in self.peripheral_register else "UNKNOWN"
|
||||
self.log.debug(
|
||||
f"pc=0x{kwargs['pc']:x} : Register write of {reg} at 0x{offset:04x} with value {value} (0x{value:x})")
|
||||
self.trace.append(('write', offset, reg, value))
|
||||
return self.forward_to.protocols.memory.write_memory(self.address + offset, size, value)
|
||||
@@ -0,0 +1,103 @@
|
||||
import logging
|
||||
from types import MethodType
|
||||
|
||||
import avatar2
|
||||
from avatar2 import QemuTarget
|
||||
from avatar2.archs import ARMV7M
|
||||
from avatar2.plugins.arm.hal import RegisterFuncArg
|
||||
from avatar2.protocols.armv7_HWRunner import ARMv7MHWRunnerProtocol
|
||||
from avatar2.protocols.qemu_HWRunner import QemuARMv7MHWRunnerProtocol
|
||||
from avatar2.targets import OpenOCDTarget
|
||||
from avatar2.watchmen import AFTER
|
||||
|
||||
from avatar2.message import HWExitMessage, HWEnterMessage
|
||||
|
||||
from avatar2.watchmen import watch
|
||||
|
||||
|
||||
class HWRunnerPlugin:
|
||||
|
||||
def __init__(self, avatar, config):
|
||||
self.avatar = avatar
|
||||
self.hardware_target = None
|
||||
self.virtual_target = None
|
||||
self.functions = config['functions']
|
||||
self.log = logging.getLogger(f'{avatar.log.name}.plugins.{self.__class__.__name__}')
|
||||
|
||||
@watch('HWEnter')
|
||||
def func_enter(self, message: HWEnterMessage):
|
||||
self.log.warning(f"func_enter called with {message}")
|
||||
for arg in message.function.args:
|
||||
if isinstance(arg, RegisterFuncArg):
|
||||
arg.value = self.virtual_target.read_register(arg.register)
|
||||
if arg.needs_transfer:
|
||||
self.log.info(f"Transferring argument of size {arg.size} at address 0x{arg.value:x}")
|
||||
arg_data = self.virtual_target.read_memory(arg.value, size=1, num_words=arg.size)
|
||||
self.hardware_target.write_memory(arg.value, size=1, value=arg_data, num_words=arg.size, raw=True)
|
||||
for field in message.function.context_transfers:
|
||||
self.log.warning(f"Transferring context field of size {field.size} at address 0x{field.value:x}")
|
||||
field_data = self.virtual_target.read_memory(field.value, size=1, num_words=field.size)
|
||||
self.hardware_target.write_memory(field.value, size=1, value=field_data, num_words=field.size, raw=True)
|
||||
|
||||
if getattr(self.hardware_target.protocols, 'interrupts', None) is not None:
|
||||
self.hardware_target.protocols.interrupts.pause()
|
||||
self.hardware_target.protocols.hal.func_call(message.function, message.return_address)
|
||||
|
||||
@watch('HWExit')
|
||||
def func_exit(self, message: HWExitMessage):
|
||||
self.log.warning(f"func_exit called with return val {message.return_val} to 0x{message.return_address:x}")
|
||||
if message.function.return_args is not None:
|
||||
for arg in message.function.return_args:
|
||||
if arg is None or not arg.needs_transfer: # Return value is handled in r0 (if None -> void function)
|
||||
continue
|
||||
self.log.info(f"Transferring return-argument of size {arg.size} at address 0x{arg.value:x}")
|
||||
arg_data = self.hardware_target.read_memory(arg.value, size=1, num_words=arg.size, raw=True)
|
||||
self.virtual_target.write_memory(arg.value, size=1, value=arg_data, num_words=arg.size)
|
||||
|
||||
self.hardware_target.protocols.hal.continue_after_hal(message)
|
||||
self.virtual_target.protocols.hal.handle_func_return(message)
|
||||
if getattr(self.hardware_target.protocols, 'interrupts', None) is not None:
|
||||
self.hardware_target.protocols.interrupts.resume()
|
||||
|
||||
def enable_func_calling(self):
|
||||
assert isinstance(self.hardware_target, OpenOCDTarget), "HAL-Caller `hardware_target` must be OpenOCDTarget"
|
||||
assert isinstance(self.virtual_target, QemuTarget), "HAL-Caller `virtual_target` must be QemuTarget"
|
||||
|
||||
# We need OpenOCD as the memory protocol to perform memory access while the target is running
|
||||
self.hardware_target.protocols.memory = self.hardware_target.protocols.monitor
|
||||
|
||||
self.hardware_target.protocols.hal.enable()
|
||||
self.virtual_target.protocols.hal.enable(self.functions)
|
||||
|
||||
self.avatar.message_handlers.update({
|
||||
HWEnterMessage: lambda m: None, # Handled in the fast queue, just ignore in the main message queue
|
||||
HWExitMessage: lambda m: None, # Handled in the fast queue, just ignore in the main message queue
|
||||
})
|
||||
self.avatar.fast_queue_listener.message_handlers.update({
|
||||
HWEnterMessage: self.func_enter,
|
||||
HWExitMessage: self.func_exit,
|
||||
})
|
||||
|
||||
|
||||
def add_protocols(self: avatar2.Avatar, **kwargs):
|
||||
target = kwargs['watched_target']
|
||||
logging.getLogger("avatar").info(f"Attaching ARMv7 HWRunner protocol to {target}")
|
||||
if isinstance(target, OpenOCDTarget):
|
||||
target.protocols.hal = ARMv7MHWRunnerProtocol(target.avatar, target)
|
||||
self._plugin_hal_caller.hardware_target = target
|
||||
|
||||
elif isinstance(target, QemuTarget):
|
||||
target.protocols.hal = QemuARMv7MHWRunnerProtocol(target.avatar, target)
|
||||
self._plugin_hal_caller.virtual_target = target
|
||||
else:
|
||||
logging.getLogger("avatar").warning(f"Unsupported target {target}")
|
||||
|
||||
|
||||
def load_plugin(avatar: avatar2.Avatar, config):
|
||||
if avatar.arch not in ARMV7M:
|
||||
avatar.log.error("Tried to load armv7-m hal-caller plugin " +
|
||||
"with mismatching architecture")
|
||||
avatar._plugin_hal_caller = HWRunnerPlugin(avatar, config)
|
||||
avatar.enable_hal_calling = MethodType(HWRunnerPlugin.enable_func_calling, avatar._plugin_hal_caller)
|
||||
|
||||
avatar.watchmen.add_watchman('TargetInit', when=AFTER, callback=add_protocols)
|
||||
@@ -0,0 +1,208 @@
|
||||
import logging
|
||||
import pprint
|
||||
from time import sleep
|
||||
from types import MethodType
|
||||
|
||||
from avatar2 import Avatar, TargetStates
|
||||
from avatar2.archs import ARMV7M
|
||||
from avatar2.protocols.armv7_INTForwarder import ARMV7INTForwarderProtocol
|
||||
from avatar2.protocols.coresight import CoreSightProtocol
|
||||
from avatar2.protocols.qemu_armv7m_interrupt import QEmuARMV7MInterruptProtocol
|
||||
from avatar2.targets import OpenOCDTarget, QemuTarget
|
||||
from avatar2.watchmen import AFTER
|
||||
|
||||
from avatar2.message import RemoteInterruptEnterMessage, TargetInterruptEnterMessage, TargetInterruptExitMessage
|
||||
from avatar2.message import RemoteInterruptExitMessage
|
||||
from avatar2.message import RemoteMemoryWriteMessage
|
||||
|
||||
from avatar2.watchmen import watch
|
||||
|
||||
|
||||
def add_protocols(self: Avatar, **kwargs):
|
||||
target = kwargs['watched_target']
|
||||
logging.getLogger("avatar").info(f"Attaching ARMv7 Interrupts protocol to {target}")
|
||||
if isinstance(target, OpenOCDTarget):
|
||||
target.protocols.interrupts = ARMV7INTForwarderProtocol(target.avatar, target)
|
||||
|
||||
# We want to remove the decorators around the read_memory function of
|
||||
# this target, to allow reading while it is running (thanks oocd)
|
||||
target.read_memory = MethodType(lambda t, *args, **kwargs:
|
||||
t.protocols.memory.read_memory(
|
||||
*args, **kwargs), target
|
||||
)
|
||||
|
||||
if isinstance(target, QemuTarget):
|
||||
target.protocols.interrupts = QEmuARMV7MInterruptProtocol(
|
||||
target, self.v7m_irq_rx_queue_name, self.v7m_irq_tx_queue_name
|
||||
)
|
||||
if getattr(target.avatar, 'irq_pair', None) is None:
|
||||
target.avatar.irq_pair = [target, ]
|
||||
else:
|
||||
target.avatar.irq_pair.append(target)
|
||||
assert len(target.avatar.irq_pair) <= 2, "Interrupts only work with two targets"
|
||||
|
||||
|
||||
def enable_interrupt_forwarding(self, from_target, to_target=None):
|
||||
"""
|
||||
Semi forwarding is a special mode developed for pretender.
|
||||
It allows that irqs are taken from from_target and external calls to
|
||||
inject_interrupt. However, no information about to_targets irq-state is
|
||||
given back to from_target. Nevertheless, memory requests from to_target to
|
||||
from_target are forwarded.
|
||||
Confused yet? So are we, this is a huge hack.
|
||||
"""
|
||||
self._irq_src = from_target
|
||||
self._irq_dst = to_target
|
||||
self._hardware_target = from_target if isinstance(from_target, OpenOCDTarget) else to_target
|
||||
self._virtual_target = to_target if not isinstance(to_target, OpenOCDTarget) else from_target
|
||||
|
||||
# Also, let's use openocd as protocol for register and memory
|
||||
if self._hardware_target:
|
||||
self._hardware_target.protocols.memory = self._hardware_target.protocols.monitor
|
||||
self._hardware_target.protocols.registers = self._hardware_target.protocols.monitor
|
||||
|
||||
self._hardware_target.protocols.interrupts.enable_interrupts()
|
||||
isr_addr = self._hardware_target.protocols.interrupts._monitor_stub_isr - 1
|
||||
self.log.info("ISR is at %#08x" % isr_addr)
|
||||
# NOTE: This won't work on many targets, eg cortex m0 can not have HW breakpoints in RAM
|
||||
# from_target.set_breakpoint(isr_addr, hardware=True)
|
||||
|
||||
if self._virtual_target:
|
||||
self._virtual_target.protocols.interrupts.enable_interrupts()
|
||||
|
||||
self._remote_interrupt_enter = MethodType(_remote_interrupt_enter, self)
|
||||
self._remote_interrupt_exit = MethodType(_remote_interrupt_exit, self)
|
||||
self._remote_memory_write_nvic = MethodType(_remote_memory_write_nvic, self)
|
||||
|
||||
self.message_handlers.update({
|
||||
RemoteMemoryWriteMessage: self._remote_memory_write_nvic}
|
||||
)
|
||||
|
||||
# OpenOCDProtocol does not emit breakpointhitmessages currently,
|
||||
# So we listen on state-updates and figure out the rest on our own
|
||||
self._interrupt_enter_handler = MethodType(forward_interrupt, self)
|
||||
self.fast_queue_listener.message_handlers.update({
|
||||
RemoteInterruptEnterMessage: self._remote_interrupt_enter,
|
||||
RemoteInterruptExitMessage: self._remote_interrupt_exit,
|
||||
TargetInterruptEnterMessage: self._interrupt_enter_handler,
|
||||
})
|
||||
|
||||
|
||||
def transfer_interrupt_state(self, to_target, from_target):
|
||||
self._hardware_target = from_target if isinstance(from_target, OpenOCDTarget) else to_target
|
||||
self._virtual_target = to_target if not isinstance(to_target, OpenOCDTarget) else from_target
|
||||
assert getattr(self, '_hardware_target', None) is not None, "Missing hardware target"
|
||||
assert getattr(self, '_virtual_target', None) is not None, "Missing virtual target"
|
||||
|
||||
hw_irq_p: ARMV7INTForwarderProtocol = self._hardware_target.protocols.interrupts
|
||||
vm_irq_p: QEmuARMV7MInterruptProtocol = self._virtual_target.protocols.interrupts
|
||||
|
||||
# Transfer the vector table location
|
||||
vtor_loc = hw_irq_p.get_vtor()
|
||||
vm_irq_p.set_vector_table_base(vtor_loc)
|
||||
# Transfer which interrupts are enabled
|
||||
enabled_interrupts = hw_irq_p.get_enabled_interrupts()
|
||||
vm_irq_p.set_enabled_interrupts(enabled_interrupts)
|
||||
|
||||
|
||||
@watch("TargetInterruptEnter")
|
||||
def forward_interrupt(self, message: TargetInterruptEnterMessage):
|
||||
origin = message.origin
|
||||
assert origin is self._hardware_target, "Origin is not the hardware target"
|
||||
self.log.info(f"forwarding interrupt {message.interrupt_num}")
|
||||
|
||||
if self._virtual_target.state != TargetStates.RUNNING:
|
||||
self.log.critical(f"Interrupt destination not running, pushing irq={message.interrupt_num} onto queue")
|
||||
self._hardware_target.protocols.interrupts.queue_irq(message.interrupt_num, message.isr_addr)
|
||||
return False
|
||||
if self._plugins_armv7m_interrupts_injected_irq is not None:
|
||||
self.log.critical(f"Interrupt nesting not supported, pushing irq={message.interrupt_num} onto queue")
|
||||
self._hardware_target.protocols.interrupts.queue_irq(message.interrupt_num, message.isr_addr)
|
||||
return False
|
||||
|
||||
# self.queue.put(message)
|
||||
|
||||
irq_num = message.interrupt_num
|
||||
self.log.info("Injecting IRQ 0x%x" % irq_num)
|
||||
# State update MUST be before signaling the protocol due to async processing
|
||||
self._plugins_armv7m_interrupts_injected_irq = irq_num
|
||||
self._plugins_armv7m_interrupts_from_hardware = True
|
||||
self.log.warning(f"forward_interrupt {self._plugins_armv7m_interrupts_from_hardware})")
|
||||
destination = self._virtual_target
|
||||
destination.protocols.interrupts.inject_interrupt(irq_num)
|
||||
return True
|
||||
|
||||
|
||||
@watch('RemoteInterruptEnter')
|
||||
def _remote_interrupt_enter(self, message):
|
||||
self.log.warning(
|
||||
f"_handle_remote_interrupt_enter_message {self._plugins_armv7m_interrupts_from_hardware})")
|
||||
self._plugins_armv7m_interrupts_injected_irq = message.interrupt_num
|
||||
|
||||
if not self._plugins_armv7m_interrupts_from_hardware:
|
||||
self._plugins_armv7m_interrupts_from_hardware = True
|
||||
self._hardware_target.protocols.interrupts.inject_interrupt(message.interrupt_num)
|
||||
|
||||
self._irq_dst.protocols.interrupts.send_interrupt_enter_response(message.id,
|
||||
True)
|
||||
|
||||
|
||||
@watch('RemoteInterruptExit')
|
||||
def _remote_interrupt_exit(self, message: RemoteInterruptExitMessage):
|
||||
"""
|
||||
Handle an interrupt exiting properly
|
||||
If the interrupt was trigged by the hardware, we need to tell the
|
||||
interrupt that we satisified it
|
||||
:param self:
|
||||
:param message:
|
||||
:return:
|
||||
"""
|
||||
self.log.warning(f"_handle_remote_interrupt_exit_message {message})")
|
||||
|
||||
origin = message.origin
|
||||
if origin is self._virtual_target and self._plugins_armv7m_interrupts_injected_irq is not None:
|
||||
if self._plugins_armv7m_interrupts_from_hardware:
|
||||
self._hardware_target.protocols.interrupts.inject_exc_return()
|
||||
self._plugins_armv7m_interrupts_from_hardware = False
|
||||
self._plugins_armv7m_interrupts_injected_irq = None
|
||||
|
||||
# Always ack the exit message
|
||||
self._irq_dst.protocols.interrupts.send_interrupt_exit_response(message.id, True)
|
||||
|
||||
|
||||
def _remote_memory_write_nvic(self, message: RemoteMemoryWriteMessage):
|
||||
# NVIC address according to coresight manual
|
||||
if message.address < 0xe000e000 or message.address > 0xe000f000 or self._irq_src is None:
|
||||
return self._handle_remote_memory_write_message(message)
|
||||
|
||||
# Discard writes to the vector table offset registers
|
||||
# TODO add other blacklists
|
||||
if message.address == 0xE000ED08:
|
||||
success = True
|
||||
else:
|
||||
success = self._irq_src.write_memory(message.address,
|
||||
message.size,
|
||||
message.value)
|
||||
drop_further_processing = 0
|
||||
if isinstance(success, tuple):
|
||||
success, drop_further_processing = success
|
||||
|
||||
message.origin.protocols.remote_memory.send_response(message.id, drop_further_processing, success)
|
||||
|
||||
return message.id, message.value, success
|
||||
|
||||
|
||||
def load_plugin(avatar, config={}):
|
||||
if avatar.arch not in ARMV7M:
|
||||
avatar.log.error("Tried to load armv7-m interrupt plugin " +
|
||||
"with mismatching architecture")
|
||||
|
||||
avatar.v7m_irq_rx_queue_name = '/avatar_v7m_irq_rx_queue'
|
||||
avatar.v7m_irq_tx_queue_name = '/avatar_v7m_irq_tx_queue'
|
||||
avatar.enable_interrupt_forwarding = MethodType(enable_interrupt_forwarding, avatar)
|
||||
avatar.transfer_interrupt_state = MethodType(transfer_interrupt_state, avatar)
|
||||
avatar._plugins_armv7m_interrupts_injected_irq = None
|
||||
avatar._plugins_armv7m_interrupts_from_hardware = False
|
||||
avatar._plugins_armv7m_interrupts_config = config
|
||||
|
||||
avatar.watchmen.add_watchman('TargetInit', when=AFTER, callback=add_protocols)
|
||||
@@ -0,0 +1,78 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from types import MethodType
|
||||
|
||||
import avatar2
|
||||
from avatar2.archs import ARMV7M
|
||||
from avatar2.protocols.armv7_interrupt_recording import ARMv7MInterruptRecordingProtocol
|
||||
from avatar2.targets import OpenOCDTarget
|
||||
from avatar2.watchmen import AFTER
|
||||
|
||||
from avatar2.message import TargetInterruptEnterMessage, TargetInterruptExitMessage
|
||||
|
||||
from avatar2.watchmen import watch
|
||||
|
||||
|
||||
class InterruptRecorderPlugin:
|
||||
|
||||
def __init__(self, avatar):
|
||||
self.avatar = avatar
|
||||
self.hardware_target = None
|
||||
self.trace = []
|
||||
self.log = logging.getLogger(f'{avatar.log.name}.plugins.{self.__class__.__name__}')
|
||||
|
||||
@watch('TargetInterruptEnter')
|
||||
def _handle_interrupt_enter(self, message: TargetInterruptEnterMessage):
|
||||
interrupt_num = message.interrupt_num
|
||||
self.trace.append(
|
||||
{'id': message.id, 'event': 'enter', 'interrupt_num': interrupt_num,
|
||||
'timestamp': datetime.now().isoformat()})
|
||||
|
||||
@watch('TargetInterruptExit')
|
||||
def _handle_interrupt_exit(self, message: TargetInterruptExitMessage):
|
||||
interrupt_num = message.interrupt_num
|
||||
self.trace.append(
|
||||
{'id': message.id, 'event': 'exit', 'interrupt_num': interrupt_num,
|
||||
'timestamp': datetime.now().isoformat()})
|
||||
|
||||
def enable_interrupt_recording(self):
|
||||
assert self.hardware_target is not None, "Interrupt-Recorder can only be enabled after a hardware target is set"
|
||||
# Also, let's use openocd as protocol for register and memory
|
||||
self.hardware_target.protocols.memory = self.hardware_target.protocols.monitor
|
||||
self.hardware_target.protocols.registers = self.hardware_target.protocols.monitor
|
||||
|
||||
self.hardware_target.protocols.interrupts.enable_interrupt_recording()
|
||||
|
||||
self.avatar.fast_queue_listener.message_handlers.update({
|
||||
TargetInterruptEnterMessage: self._handle_interrupt_enter,
|
||||
TargetInterruptExitMessage: self._handle_interrupt_exit,
|
||||
})
|
||||
|
||||
|
||||
def add_protocols(self: avatar2.Avatar, **kwargs):
|
||||
target = kwargs['watched_target']
|
||||
if not isinstance(target, OpenOCDTarget):
|
||||
logging.getLogger('avatar').warning(f"Interrupt-Recorder only works with OpenOCDTarget but got {target}")
|
||||
return
|
||||
logging.getLogger("avatar").info(f"Attaching ARMv7 Interrupt-Recorder protocol to {target}")
|
||||
|
||||
target.protocols.interrupts = ARMv7MInterruptRecordingProtocol(target.avatar, target)
|
||||
self._plugin_interrupt_recorder.hardware_target = target
|
||||
|
||||
# We want to remove the decorators around the read_memory function of
|
||||
# this target, to allow reading while it is running (thanks OpenOCD)
|
||||
target.read_memory = MethodType(lambda t, *args, **kwargs:
|
||||
t.protocols.memory.read_memory(
|
||||
*args, **kwargs), target
|
||||
)
|
||||
|
||||
|
||||
def load_plugin(avatar: avatar2.Avatar):
|
||||
if avatar.arch not in ARMV7M:
|
||||
avatar.log.error("Tried to load armv7-m interrupt-recorder plugin " +
|
||||
"with mismatching architecture")
|
||||
avatar._plugin_interrupt_recorder = InterruptRecorderPlugin(avatar)
|
||||
avatar.enable_interrupt_recording = MethodType(InterruptRecorderPlugin.enable_interrupt_recording,
|
||||
avatar._plugin_interrupt_recorder)
|
||||
|
||||
avatar.watchmen.add_watchman('TargetInit', when=AFTER, callback=add_protocols)
|
||||
@@ -0,0 +1,88 @@
|
||||
from typing import Union, List
|
||||
|
||||
|
||||
class FuncArg:
|
||||
"""
|
||||
Represents a function argument that is written to the hardware target.
|
||||
By default, this is a constant value, for argument transfer at runtime use RegisterFuncArg.
|
||||
"""
|
||||
|
||||
def __init__(self, value, needs_transfer=False, size=None):
|
||||
assert value is None or isinstance(value, int), "Address must be an integer or None for read from register"
|
||||
if needs_transfer:
|
||||
assert isinstance(size, int), "Size must be specified if needs_transfer is True"
|
||||
self.value: int = value
|
||||
self.needs_transfer: bool = needs_transfer
|
||||
self.size: int = size
|
||||
|
||||
def __str__(self):
|
||||
if self.needs_transfer:
|
||||
return f"FuncArg(value=0x{self.value:x}, needs_transfer=True, size={self.size})"
|
||||
else:
|
||||
return f"FuncArg(value=0x{self.value:x})"
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
|
||||
class RegisterFuncArg(FuncArg):
|
||||
"""Represents a function argument that is passed in a register and dynamically transferred to the hardware target"""
|
||||
|
||||
def __init__(self, register: str, needs_transfer=False, size=None):
|
||||
super().__init__(None, needs_transfer=needs_transfer, size=size)
|
||||
self.register = register
|
||||
|
||||
def __str__(self):
|
||||
if self.needs_transfer:
|
||||
return f"RegisterFuncArg(register={self.register}, value={self.value}, needs_transfer=True, size={self.size})"
|
||||
else:
|
||||
return f"RegisterFuncArg(register={self.register}, value={self.value})"
|
||||
|
||||
|
||||
class FuncReturnArg(FuncArg):
|
||||
def __init__(self, value: int, needs_transfer=True, size=None):
|
||||
super().__init__(value, needs_transfer=needs_transfer, size=size)
|
||||
|
||||
def __str__(self):
|
||||
if self.needs_transfer:
|
||||
return f"FuncReturnArg(value=0x{self.value:x}, needs_transfer=True, size={self.size})"
|
||||
else:
|
||||
return f"FuncReturnArg(value=0x{self.value:x})"
|
||||
|
||||
|
||||
class ContextTransferArg(FuncArg):
|
||||
def __init__(self, address: int, size=None):
|
||||
super().__init__(address, needs_transfer=True, size=size)
|
||||
|
||||
def __str__(self):
|
||||
if self.needs_transfer:
|
||||
return f"ContextTransferArg(value=0x{self.value:x}, needs_transfer=True, size={self.size})"
|
||||
else:
|
||||
return f"ContextTransferArg(value=0x{self.value:x})"
|
||||
|
||||
|
||||
class HWFunction:
|
||||
"""
|
||||
Represents a function that is called on the hardware target.
|
||||
|
||||
@param address: The address of the function
|
||||
@param args: The arguments of the function
|
||||
@param return_args: The return arguments of the function; if [None] -> void function ;
|
||||
otherwise it assumes the return value in r0
|
||||
"""
|
||||
|
||||
VOID = [None]
|
||||
|
||||
def __init__(self, address: int, args: [FuncArg], context_transfers: [FuncArg] = [],
|
||||
return_args: Union[List[Union[FuncReturnArg, None]], None] = None):
|
||||
self.address = address
|
||||
self.args = args
|
||||
self.context_transfers = context_transfers
|
||||
self.return_args = return_args
|
||||
|
||||
def __str__(self):
|
||||
return (f"HALFunction(address=0x{self.address:x}, args={self.args}, " +
|
||||
f"context_transfers={self.context_transfers}, returnArgs={self.return_args})")
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
@@ -1,10 +1,10 @@
|
||||
import logging
|
||||
from types import MethodType
|
||||
|
||||
from keystone import *
|
||||
|
||||
|
||||
def assemble(self, asmstr, addr=None,
|
||||
arch=None, mode=None):
|
||||
def assemble(self, asmstr, addr=None, arch=None, mode=None):
|
||||
"""
|
||||
Main purpose of the assembler plugin, it's used to assemble
|
||||
instructions
|
||||
@@ -25,19 +25,34 @@ def assemble(self, asmstr, addr=None,
|
||||
bytes_raw = bytes(bytelist)
|
||||
return bytes_raw
|
||||
|
||||
def inject_asm(self, asmstr, addr=None, arch=None, mode=None):
|
||||
|
||||
def inject_asm(self, asmstr, addr=None, arch=None, mode=None, patch=None):
|
||||
"""
|
||||
Assemble the string, and inject it into the target)
|
||||
Assemble the string, and inject it into the targets' memory.
|
||||
|
||||
:param asmstr: The assembly string to be assembled
|
||||
:param addr: Optional address at which the assembly should be injected, defaults to `pc`
|
||||
:param arch: Optional keystone-architecture to be used, defaults to architecture of target
|
||||
:param mode: Optional keystone-mode to be used, defaults to mode of target
|
||||
:param patch: Optional dictionary of address->bytes patches to be replaced in the assembled code
|
||||
(eg. `patch={0x20001000: b'\xef\xf3\x05\x85'}`)
|
||||
|
||||
:returns: True if the injection was successful, False otherwise
|
||||
"""
|
||||
arch = self._arch.keystone_arch if not arch else arch
|
||||
mode = self._arch.keystone_mode if not mode else mode
|
||||
addr = self.regs.pc if not addr else addr
|
||||
logging.getLogger('avatar').debug(f"Injecting assembly into address 0x{addr:8x}")
|
||||
|
||||
md = Ks(arch, mode)
|
||||
bytelist = md.asm(asmstr, addr)[0]
|
||||
bytes_raw = bytes(bytelist)
|
||||
if patch is not None:
|
||||
for key in patch.keys():
|
||||
bytes_raw = bytes_raw[:key] + patch[key] + bytes_raw[key + len(patch[key]):]
|
||||
return self.write_memory(addr, 1, bytes_raw, len(bytes_raw), raw=True)
|
||||
|
||||
|
||||
def target_added_callback(avatar, *args, **kwargs):
|
||||
target = kwargs['watched_return']
|
||||
target.assemble = MethodType(assemble, target)
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
import queue
|
||||
from threading import Thread, Event
|
||||
import logging
|
||||
|
||||
from avatar2 import TargetStates
|
||||
from avatar2.message import BreakpointHitMessage, HWExitMessage
|
||||
from avatar2.plugins.arm.hal import HWFunction
|
||||
from avatar2.watchmen import AFTER
|
||||
|
||||
CMD_HAL_CALL = 0
|
||||
CMD_CONT = 1
|
||||
|
||||
|
||||
class ARMv7MHWRunnerProtocol(Thread):
|
||||
|
||||
def __init__(self, avatar, origin):
|
||||
self.avatar = avatar
|
||||
self._avatar_fast_queue = avatar.fast_queue
|
||||
self._close = Event()
|
||||
self._closed = Event()
|
||||
self.target = origin
|
||||
self.command_queue = queue.Queue()
|
||||
|
||||
self._stub_base = None
|
||||
self._stub_func_ptr = None
|
||||
self._stub_entry = None
|
||||
self._stub_end = None
|
||||
|
||||
self.current_hal_call = None
|
||||
self.return_after_hal = None
|
||||
|
||||
self.log = logging.getLogger(f'{avatar.log.name}.protocols.{self.__class__.__name__}')
|
||||
Thread.__init__(self, daemon=True, name=f"Thread-{self.__class__.__name__}")
|
||||
self.log.info(f"ARMv7MHWRunnerProtocol initialized")
|
||||
|
||||
def __del__(self):
|
||||
self.shutdown()
|
||||
|
||||
def shutdown(self):
|
||||
if self.is_alive() is True:
|
||||
self.stop()
|
||||
|
||||
def connect(self):
|
||||
pass
|
||||
|
||||
def enable(self):
|
||||
try:
|
||||
self.log.info(f"Enabling ARMv7 HAL catching")
|
||||
|
||||
self.inject_monitor_stub()
|
||||
# self._end_of_stub_bkpt = self.target.set_breakpoint(self._stub_end)
|
||||
|
||||
self.avatar.watchmen.add_watchman('BreakpointHit', AFTER, self._do_func_return)
|
||||
|
||||
self.log.info(f"Starting ARMv7 HAL catching thread")
|
||||
self.start()
|
||||
except:
|
||||
self.log.exception("Error starting ARMv7MHWRunnerProtocol")
|
||||
|
||||
# TODO what this stub does
|
||||
MONITOR_STUB = ("" +
|
||||
# Data
|
||||
"func_addr: .word 0x00000000\n" +
|
||||
|
||||
# Load and call the actual function
|
||||
"ldr r4, =func_addr\n" +
|
||||
"ldr r4, [r4]\n" +
|
||||
"blx r4\n" + # r0 holds return value now
|
||||
"bkpt\n" +
|
||||
"nop\n"
|
||||
# Return to previous point of execution, leaves r12 modified
|
||||
)
|
||||
|
||||
def inject_monitor_stub(self, addr=0x20012000):
|
||||
"""
|
||||
Injects a safe monitoring stub.
|
||||
This has the following effects:
|
||||
0. Pivot the VTOR to someplace sane
|
||||
1. Insert an infinite loop at addr
|
||||
2. Set the PC to addr
|
||||
3. set up logic for the injection of interrupt returns.
|
||||
Write to return_code_register to trigger an IRET
|
||||
4.
|
||||
:return:
|
||||
"""
|
||||
self.log.warning(f"Injecting HAL caller stub into {self.target.name} at address 0x{addr:x}.")
|
||||
|
||||
self._stub_base = addr
|
||||
self.log.info(f"_stub_base = 0x{self._stub_base:08x}")
|
||||
self._stub_func_ptr = self._stub_base
|
||||
self.log.info(f"_stub_func_ptr = 0x{self._stub_func_ptr:08x}")
|
||||
self._stub_entry = self._stub_base + 4
|
||||
self.log.info(f"_stub_entry = 0x{self._stub_base:08x}")
|
||||
self._stub_end = self._stub_entry + 3 * 2
|
||||
self.log.info(f"_stub_end = 0x{self._stub_end:08x}")
|
||||
|
||||
# Inject the stub
|
||||
self.log.info(f"Injecting the stub ...")
|
||||
self.target.inject_asm(self.MONITOR_STUB, self._stub_base)
|
||||
|
||||
def func_call(self, function: HWFunction, return_address: int):
|
||||
self.command_queue.put((CMD_HAL_CALL, function, return_address))
|
||||
|
||||
def _do_func_return(self, avatar, message: BreakpointHitMessage, *args,
|
||||
**kwargs): # avatar, self, message: BreakpointHitMessage,
|
||||
self.log.debug(f"_do_hal_return got additional {args}, {kwargs}")
|
||||
if message.origin != self.target:
|
||||
return
|
||||
if message.address != self._stub_end:
|
||||
return
|
||||
current_func: HWFunction = self.current_hal_call[0]
|
||||
return_address = self.current_hal_call[1]
|
||||
|
||||
self.target.regs.r0 = self.restore_regs_r0
|
||||
self.target.regs.r1 = self.restore_regs_r1
|
||||
self.target.regs.r2 = self.restore_regs_r2
|
||||
self.target.regs.r3 = self.restore_regs_r3
|
||||
self.target.regs.r4 = self.restore_regs_r4
|
||||
self.target.regs.sp = self.restore_regs_sp
|
||||
self.target.regs.lr = self.restore_regs_lr
|
||||
self.target.regs.pc = self.return_after_hal
|
||||
self.return_after_hal = None
|
||||
|
||||
self._dispatch_message(HWExitMessage(self.target, current_func, return_val=self.target.regs.r0,
|
||||
return_address=return_address))
|
||||
self.current_hal_call = None
|
||||
|
||||
def _do_func_call(self, function: HWFunction):
|
||||
assert self._stub_entry is not None, "Stub not injected yet"
|
||||
self.log.warning(f"_do_hal_call (func=0x{function.address:x}, args = {function.args})...")
|
||||
if self.target.state == TargetStates.RUNNING:
|
||||
self.target.stop()
|
||||
self.return_after_hal = self.target.regs.pc
|
||||
self.restore_regs_lr = self.target.regs.lr
|
||||
self.restore_regs_sp = self.target.regs.sp
|
||||
self.restore_regs_r0 = self.target.regs.r0
|
||||
self.restore_regs_r1 = self.target.regs.r1
|
||||
self.restore_regs_r2 = self.target.regs.r2
|
||||
self.restore_regs_r3 = self.target.regs.r3
|
||||
self.restore_regs_r4 = self.target.regs.r4
|
||||
self.target.write_memory(self._stub_func_ptr, size=4, value=function.address | 0x01)
|
||||
|
||||
# TODO stack allocated objects
|
||||
if len(function.args) >= 1:
|
||||
self.target.regs.r0 = function.args[0].value
|
||||
if len(function.args) >= 2:
|
||||
self.target.regs.r1 = function.args[1].value
|
||||
if len(function.args) >= 3:
|
||||
self.target.regs.r2 = function.args[2].value
|
||||
if len(function.args) >= 4:
|
||||
self.target.regs.r3 = function.args[3].value
|
||||
if len(function.args) >= 5:
|
||||
# Push the stack parameters
|
||||
for i in range(4, len(function.args)):
|
||||
self.target.write_memory(self.target.regs.sp - 4 * (i - 4), size=4, value=function.args[i].value)
|
||||
self.target.regs.sp = self.target.regs.sp - 4 * (len(function.args) - 4)
|
||||
|
||||
self.target.regs.pc = self._stub_entry
|
||||
self.target.cont()
|
||||
|
||||
def continue_after_hal(self, message: HWExitMessage):
|
||||
self.command_queue.put((CMD_CONT,))
|
||||
|
||||
def run(self):
|
||||
self.log.info("Starting ARMv7MHWRunnerProtocol thread")
|
||||
|
||||
try:
|
||||
while not (self.avatar._close.is_set() or self._close.is_set()):
|
||||
try:
|
||||
command = self.command_queue.get(timeout=1.0)
|
||||
if command[0] == CMD_HAL_CALL:
|
||||
function = command[1]
|
||||
return_address = command[2]
|
||||
assert self.current_hal_call is None, "Already in HAL call"
|
||||
|
||||
self.current_hal_call = (function, return_address)
|
||||
self._do_func_call(function)
|
||||
elif command[0] == CMD_CONT:
|
||||
self.target.cont()
|
||||
else:
|
||||
self.log.error(f"Unknown command {command[0]}")
|
||||
self.command_queue.task_done()
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
except:
|
||||
self.log.exception("Error processing ARMv7MHWRunnerProtocol thread")
|
||||
self._closed.set()
|
||||
self.log.debug("ARMv7MHWRunnerProtocol thread exiting...")
|
||||
self._closed.set()
|
||||
|
||||
def _dispatch_message(self, message):
|
||||
self._avatar_fast_queue.put(message)
|
||||
|
||||
def stop(self):
|
||||
"""Stops the listening thread. Useful for teardown of the target"""
|
||||
self._close.set()
|
||||
self._closed.wait()
|
||||
@@ -0,0 +1,361 @@
|
||||
import queue
|
||||
import sys
|
||||
from enum import Enum
|
||||
from threading import Thread, Event, Condition
|
||||
import logging
|
||||
import re
|
||||
from time import sleep
|
||||
|
||||
from bitstring import BitStream, ReadError
|
||||
|
||||
from avatar2 import watch, OpenOCDTarget
|
||||
from avatar2.archs.arm import ARM
|
||||
from avatar2.targets import TargetStates
|
||||
from avatar2.message import AvatarMessage, UpdateStateMessage, \
|
||||
BreakpointHitMessage, RemoteInterruptEnterMessage, TargetInterruptEnterMessage, TargetInterruptExitMessage
|
||||
from avatar2.protocols.openocd import OpenOCDProtocol
|
||||
|
||||
# ARM System Control Block
|
||||
SCB_CPUID = 0xe000ed00 # What is it
|
||||
SCB_STIR = 0xe000ef00 # Send interrupts here
|
||||
SCB_VTOR = 0xe000ed08 # Vector Table offset register
|
||||
|
||||
# NVIC stuff
|
||||
NVIC_ISER0 = 0xe000e100
|
||||
|
||||
# ARMV7InterruptProtocol Constant Addresses
|
||||
RCC_APB2ENR = 0x40021018
|
||||
AFIO_MAPR = 0x40010004
|
||||
DBGMCU_CR = 0xe0042004
|
||||
COREDEBUG_DEMCR = 0xe000edfc
|
||||
TPI_ACPR = 0xe0040010
|
||||
TPI_SPPR = 0xe00400f0
|
||||
TPI_FFCR = 0xe0040304
|
||||
DWT_CTRL = 0xe0001000
|
||||
ITM_LAR = 0xe0000fb0
|
||||
ITM_TCR = 0xe0000e80
|
||||
ITM_TER = 0xe0000e00
|
||||
ETM_LAR = 0xe0041fb0
|
||||
ETM_CR = 0xe0041000
|
||||
ETM_TRACEIDR = 0xe0041200
|
||||
ETM_TECR1 = 0xe0041024
|
||||
ETM_FFRR = 0xe0041028
|
||||
ETM_FFLR = 0xe004102c
|
||||
|
||||
|
||||
class UniqueQueue(queue.Queue):
|
||||
def __contains__(self, item):
|
||||
with self.mutex:
|
||||
return item in self.queue
|
||||
|
||||
def _init(self, maxsize):
|
||||
self.queue = list()
|
||||
|
||||
def _put(self, item):
|
||||
if item not in self.queue:
|
||||
self.queue.append(item)
|
||||
|
||||
def _get(self):
|
||||
return self.queue.pop(0)
|
||||
|
||||
|
||||
class ARMV7INTForwarderProtocol(Thread):
|
||||
def __init__(self, avatar, target: OpenOCDTarget):
|
||||
self.avatar = avatar
|
||||
self._avatar_fast_queue = avatar.fast_queue
|
||||
self.target: OpenOCDTarget = target
|
||||
self._close = Event()
|
||||
self._closed = Event()
|
||||
|
||||
self._monitor_stub_addr = None
|
||||
self._monitor_stub_base = None
|
||||
self._monitor_stub_isr = None
|
||||
self._monitor_stub_loop = None
|
||||
self._monitor_stub_ctrl = None
|
||||
self._original_vtor = None
|
||||
self.original_vt = None
|
||||
|
||||
self.msg_counter = 0
|
||||
self._current_isr_num = 0
|
||||
|
||||
self._internal_irq_queue = UniqueQueue()
|
||||
self._paused = Event()
|
||||
|
||||
self.log = logging.getLogger(f'{avatar.log.name}.protocols.{self.__class__.__name__}')
|
||||
Thread.__init__(self, daemon=True, name=f"Thread-{self.__class__.__name__}")
|
||||
self.log.info(f"ARMV7InterruptProtocol initialized")
|
||||
|
||||
def __del__(self):
|
||||
self.shutdown()
|
||||
|
||||
def inject_interrupt(self, interrupt_number, cpu_number=0):
|
||||
self.log.critical(f"Injecting interrupt {interrupt_number}")
|
||||
# Set an interrupt using the STIR
|
||||
self.target.write_memory(SCB_STIR, 4, interrupt_number)
|
||||
|
||||
def enable_interrupt(self, interrupt_number):
|
||||
"""
|
||||
Enables an interrupt (e.g., in the NIVC)
|
||||
:param interrupt_number:
|
||||
:return:
|
||||
"""
|
||||
self.log.critical(f"Enabling interrupt {interrupt_number}")
|
||||
assert (0 < interrupt_number < 256)
|
||||
iser_num = interrupt_number // 32 # 32 interrupts per ISER register
|
||||
iser_addr = NVIC_ISER0 + (iser_num * 4) # Calculate ISER_X address
|
||||
iser_val = 1 << (interrupt_number % 32) # Set the corresponding bit for the interrupt to 1
|
||||
self.target.write_memory(iser_addr, 4, iser_val)
|
||||
|
||||
def get_enabled_interrupts(self, iser_num: int = 0):
|
||||
enabled_interrupts = self.target.read_memory(NVIC_ISER0 + iser_num * 4, size=4)
|
||||
return enabled_interrupts
|
||||
|
||||
def set_enabled_interrupts(self, enabled_interrupts_bitfield: int, iser_num: int = 0):
|
||||
self.target.write_memory(NVIC_ISER0 + iser_num * 4, size=4, value=enabled_interrupts_bitfield)
|
||||
|
||||
def get_vtor(self):
|
||||
return self.target.read_memory(SCB_VTOR, 4)
|
||||
|
||||
def get_ivt_addr(self):
|
||||
if getattr(self.target, 'ivt_address', None) is not None:
|
||||
return self.target.ivt_address
|
||||
else:
|
||||
return self.get_vtor()
|
||||
|
||||
def set_vtor(self, addr):
|
||||
self.log.warning(f"Changing VTOR location to 0x{addr:x}")
|
||||
res = self.target.write_memory(SCB_VTOR, 4, addr)
|
||||
if res:
|
||||
self.target.ivt_address = addr
|
||||
return res
|
||||
|
||||
def get_isr(self, interrupt_num):
|
||||
return self.target.read_memory(
|
||||
self.get_ivt_addr() + (interrupt_num * 4), 4)
|
||||
|
||||
def set_isr(self, interrupt_num, addr):
|
||||
base = self.get_ivt_addr()
|
||||
ivt_addr = base + (interrupt_num * 4)
|
||||
return self.target.write_memory(ivt_addr, 4, addr)
|
||||
|
||||
def shutdown(self):
|
||||
if self.is_alive() is True:
|
||||
self.stop()
|
||||
|
||||
def connect(self):
|
||||
if not isinstance(self.target.protocols.monitor, OpenOCDProtocol):
|
||||
raise Exception("ARMV7InterruptProtocol requires OpenOCDProtocol to be present.")
|
||||
|
||||
def enable_interrupts(self):
|
||||
try:
|
||||
self.log.info(f"Enabling interrupts")
|
||||
if not isinstance(self.target.protocols.monitor, OpenOCDProtocol):
|
||||
raise Exception(
|
||||
"ARMV7InterruptProtocol requires OpenOCDProtocol to be present.")
|
||||
|
||||
self.inject_monitor_stub()
|
||||
|
||||
self.log.info(f"Starting interrupt thread")
|
||||
self.start()
|
||||
except:
|
||||
self.log.exception("Error starting ARMV7InterruptProtocol")
|
||||
|
||||
"""
|
||||
What this does:
|
||||
Hang in a loop at `loop`
|
||||
When an interrupt comes, go to `stub`
|
||||
At `stub`, load `writeme`, if it's not zero, reset it, and jump to the written value.
|
||||
This lets us inject exc_return values into the running program
|
||||
"""
|
||||
MONITOR_STUB = ("" +
|
||||
"irq_buffer_ptr: .word 0x00000000\n" +
|
||||
"writeme: .word 0x00000000\n" +
|
||||
|
||||
"init:\n" + # Load the addresses for later access
|
||||
"loop: b loop\n" + # Wait for something to happen
|
||||
"nop\n"
|
||||
|
||||
"stub:\n" +
|
||||
"push {r4, r5}\n" +
|
||||
# # "mrs r0, IPSR\n" + # Get the interrupt number
|
||||
"nop\nnop\n" + # Placeholder to be replaced with `mrs r5, IPSR` due to keystone error
|
||||
"ldr r1, =irq_buffer_ptr\n" +
|
||||
"ldr r2, =mtb_0\n" +
|
||||
"ldr r3, [r1]\n" + # Load the buffer pointer
|
||||
"mov r4, r3\n" +
|
||||
"add r4, r4, r2\n" + # Calculate the address of the buffer pointer
|
||||
|
||||
# Ensure end of buffer flag is set
|
||||
"adds r3, r3, #1\n" + # Increment the buffer pointer
|
||||
# NOTE: We need to use `movs` otherwise keystone will use `mov.w` which will crash a cortex m0+
|
||||
"movs r5, #255\n" + # For anding to implement wrap around
|
||||
"ands r3, r3, r5\n" + # Wrap around the buffer
|
||||
"strb r3, [r1]\n" + # Store updated buffer pointer
|
||||
"strb r5, [r2, r3]\n" + # Set the end of buffer flag
|
||||
"strb r0, [r4]\n" + # Save the interrupt number
|
||||
|
||||
"intloop:\n" + # Hang in a loop until `writeme` is not 0
|
||||
"ldr r3, =writeme\n" +
|
||||
"ldr r4, [r3]\n" +
|
||||
"cmp r4, #0\n" +
|
||||
"beq intloop\n"
|
||||
|
||||
"movs r4, #0\n" +
|
||||
"str r4, [r3]\n" + # Reset `writeme`
|
||||
"pop {r4, r5}\n" +
|
||||
|
||||
"bx lr\n" # Return from the interrupt, set by the interrupt calling convention
|
||||
)
|
||||
|
||||
def _get_stub(self, mtb_size=256):
|
||||
mtb_declaration = [f"mtb_{i}: .hword 0x0000" for i in range(mtb_size)]
|
||||
mtb_declaration[0] = "mtb_0: .hword 0x00ff"
|
||||
mtb_declaration = "\n".join(mtb_declaration)
|
||||
return mtb_declaration + self.MONITOR_STUB
|
||||
|
||||
def inject_monitor_stub(self, addr=0x20010000, vtor=0x20011000, num_isr=48):
|
||||
"""
|
||||
Injects a safe monitoring stub.
|
||||
This has the following effects:
|
||||
0. Pivot the VTOR to someplace sane
|
||||
1. Insert an infinite loop at addr
|
||||
2. Set the PC to addr
|
||||
3. set up logic for the injection of interrupt returns.
|
||||
Write to return_code_register to trigger an IRET
|
||||
4.
|
||||
:return:
|
||||
"""
|
||||
self.log.warning(
|
||||
f"Injecting monitor stub into {self.target.name}. (IVT: 0x{self.get_ivt_addr():08x}, 0x{self.get_vtor():08x}, 0x{vtor:08x})")
|
||||
MTB_SIZE = 256
|
||||
|
||||
self._monitor_stub_addr = addr
|
||||
self.log.info(f"_monitor_stub_addr = 0x{self._monitor_stub_addr:08x}")
|
||||
self._monitor_stub_base = self._monitor_stub_addr + MTB_SIZE * 2
|
||||
self.log.info(f"_monitor_stub_base = 0x{self._monitor_stub_base:08x}")
|
||||
self._monitor_stub_loop = self._monitor_stub_base + 4 * 2
|
||||
self.log.info(f"_monitor_stub_loop = 0x{self._monitor_stub_loop:08x}")
|
||||
self._monitor_stub_isr = self._monitor_stub_loop + 4
|
||||
self.log.info(f"_monitor_stub_isr = 0x{self._monitor_stub_isr:08x}")
|
||||
self._monitor_stub_ctrl = self._monitor_stub_base + 4
|
||||
self.log.info(f"_monitor_stub_writeme = 0x{self._monitor_stub_ctrl:08x}")
|
||||
|
||||
# Pivot VTOR, if needed
|
||||
# On CM0, you can't, so don't.
|
||||
self._original_vtor = self.get_vtor()
|
||||
assert self._original_vtor != vtor, "VTOR is already set to the desired value."
|
||||
|
||||
self.set_vtor(vtor)
|
||||
self.log.info(f"Validate new VTOR address 0x{self.get_vtor():8x}")
|
||||
|
||||
# Sometimes, we need to gain access to the IVT (make it writable). Do that here.
|
||||
if getattr(self.target, 'ivt_unlock', None) is not None:
|
||||
unlock_addr, unlock_val = self.target.ivt_unlock
|
||||
self.target.write_memory(unlock_addr, 4, unlock_val)
|
||||
|
||||
self.log.info(f"Inserting the stub ...")
|
||||
# Inject the stub
|
||||
isr_offset = self._monitor_stub_isr - self._monitor_stub_addr + 2 # +2 for push instruction
|
||||
self.target.inject_asm(self._get_stub(MTB_SIZE), self._monitor_stub_addr,
|
||||
patch={isr_offset: b'\xef\xf3\x05\x80'})
|
||||
|
||||
self.log.info(f"Setting up IVT...")
|
||||
self.original_vt = self.target.read_memory(self._original_vtor, size=4, num_words=num_isr)
|
||||
# Set the IVT to our stub but DON'T wipe out the 0'th position.
|
||||
self.target.write_memory(vtor, value=self.target.read_memory(self._original_vtor, size=4), size=4)
|
||||
for interrupt_num in range(1, num_isr):
|
||||
self.set_isr(interrupt_num, self._monitor_stub_isr + 1) # +1 for thumb mode
|
||||
|
||||
if self.target.state != TargetStates.STOPPED:
|
||||
self.log.critical(
|
||||
"Not setting PC to the monitor stub; Target not stopped")
|
||||
else:
|
||||
self.target.write_register('pc', self._monitor_stub_loop)
|
||||
self.log.warning(f"Updated PC to 0x{self.target.regs.pc:8x}")
|
||||
|
||||
def inject_exc_return(self):
|
||||
if not self._monitor_stub_base:
|
||||
self.log.error(
|
||||
"You need to inject the monitor stub before you can inject exc_returns")
|
||||
return False
|
||||
int_num = self._current_isr_num
|
||||
self._current_isr_num = None
|
||||
self.log.info(f"Returning from interrupt {int_num}.")
|
||||
# We can just BX LR for now.
|
||||
return self.target.write_memory(address=self._monitor_stub_ctrl, size=4, value=1)
|
||||
|
||||
def queue_irq(self, interrupt_num, isr_addr):
|
||||
self._internal_irq_queue.put((interrupt_num, isr_addr))
|
||||
|
||||
def pause(self):
|
||||
self._paused.set()
|
||||
self.log.warning("IRQ protocol paused")
|
||||
|
||||
def resume(self):
|
||||
self.log.warning("IRQ protocol resuming...")
|
||||
self._paused.clear()
|
||||
|
||||
def _dispatch_exception_packet(self, int_num):
|
||||
self._current_isr_num = int_num
|
||||
|
||||
self.log.debug(f"Dispatching exception for interrupt number {int_num}")
|
||||
|
||||
msg = TargetInterruptEnterMessage(self.target, self.msg_counter, interrupt_num=int_num,
|
||||
isr_addr=self.original_vt[int_num])
|
||||
self.msg_counter += 1
|
||||
self._avatar_fast_queue.put(msg)
|
||||
|
||||
def _panic_exec(self):
|
||||
self.log.critical(f"Received hard-fault exception, stopping target")
|
||||
if self.target.state == TargetStates.RUNNING:
|
||||
self.target.stop()
|
||||
self._close.set()
|
||||
|
||||
def run(self):
|
||||
TICK_DELAY = 0.0001
|
||||
self.log.info("Starting ARMV7InterruptProtocol thread")
|
||||
|
||||
# Wait for init
|
||||
while self._monitor_stub_addr is None:
|
||||
sleep(TICK_DELAY)
|
||||
|
||||
mtb_pos = 0
|
||||
try:
|
||||
while not (self.avatar._close.is_set() or self._close.is_set()):
|
||||
try:
|
||||
if not self._paused.is_set() and self._internal_irq_queue.qsize() != 0:
|
||||
irq_num, _ = self._internal_irq_queue.get_nowait()
|
||||
self.log.info(f"IRQ event from queue {irq_num} q={self._internal_irq_queue}")
|
||||
self._dispatch_exception_packet(int_num=irq_num)
|
||||
self._internal_irq_queue.task_done()
|
||||
if irq_num == 0x3: # Hard-fault
|
||||
self._panic_exec()
|
||||
sleep(TICK_DELAY)
|
||||
continue
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
mtb_val = self.target.read_memory(self._monitor_stub_addr + mtb_pos, size=1)
|
||||
if mtb_val == 0xff:
|
||||
sleep(TICK_DELAY)
|
||||
continue
|
||||
|
||||
mtb_pos = (mtb_pos + 1) & 0xff
|
||||
|
||||
self.log.info(f"IRQ event {mtb_val}")
|
||||
if self._paused.is_set():
|
||||
self.queue_irq(mtb_val, self.original_vt[mtb_val])
|
||||
self.inject_exc_return() # TODO: This has a lot of side effects
|
||||
else:
|
||||
self._dispatch_exception_packet(int_num=mtb_val)
|
||||
if mtb_val == 0x3: # Hard-fault
|
||||
self._panic_exec()
|
||||
except:
|
||||
self.log.exception("Error processing trace")
|
||||
self.log.info("Interrupt thread exiting...")
|
||||
self._closed.set()
|
||||
|
||||
def stop(self):
|
||||
"""Stops the listening thread. Useful for teardown of the target"""
|
||||
self._close.set()
|
||||
self._closed.wait()
|
||||
@@ -0,0 +1,250 @@
|
||||
from enum import Enum
|
||||
from threading import Thread, Event
|
||||
import logging
|
||||
from time import sleep
|
||||
|
||||
from avatar2.targets import TargetStates
|
||||
from avatar2.message import TargetInterruptEnterMessage, TargetInterruptExitMessage
|
||||
from avatar2.protocols.openocd import OpenOCDProtocol
|
||||
|
||||
# ARM System Control Block
|
||||
SCB_VTOR = 0xe000ed08 # Vector Table offset register
|
||||
# NVIC stuff
|
||||
NVIC_ISER0 = 0xe000e100
|
||||
|
||||
|
||||
class ARMv7MInterruptRecordingProtocol(Thread):
|
||||
def __init__(self, avatar, target):
|
||||
self._original_vtor = None
|
||||
self.avatar = avatar
|
||||
self._avatar_fast_queue = avatar.fast_queue
|
||||
self.target = target
|
||||
self._close = Event()
|
||||
self._closed = Event()
|
||||
self._monitor_stub_base = None
|
||||
self._monitor_stub_isr = None
|
||||
self._monitor_stub_vt_buffer = None
|
||||
self._monitor_stub_trace_buffer = None
|
||||
self._monitor_stub_mtb = None
|
||||
self.msg_counter = 0
|
||||
self.original_vt = None
|
||||
self.log = logging.getLogger(f'{avatar.log.name}.protocols.{self.__class__.__name__}')
|
||||
Thread.__init__(self, daemon=True)
|
||||
self.log.info(f"ARMV7InterruptRecordingProtocol initialized")
|
||||
|
||||
def __del__(self):
|
||||
self.shutdown()
|
||||
|
||||
def get_vtor(self):
|
||||
return self.target.read_memory(SCB_VTOR, 4)
|
||||
|
||||
def get_ivt_addr(self):
|
||||
if getattr(self.target, 'ivt_address', None) is not None:
|
||||
return self.target.ivt_address
|
||||
else:
|
||||
return self.get_vtor()
|
||||
|
||||
def set_vtor(self, addr):
|
||||
self.log.warning(f"Changing VTOR location to 0x{addr:x}")
|
||||
res = self.target.write_memory(SCB_VTOR, 4, addr)
|
||||
if res:
|
||||
self.target.ivt_address = addr
|
||||
return res
|
||||
|
||||
def shutdown(self):
|
||||
if self.is_alive() is True:
|
||||
self.stop()
|
||||
|
||||
def connect(self):
|
||||
if not isinstance(self.target.protocols.monitor, OpenOCDProtocol):
|
||||
raise Exception("ARMV7InterruptRecordingProtocol requires OpenOCDProtocol to be present.")
|
||||
|
||||
def enable_interrupt_recording(self):
|
||||
try:
|
||||
self.log.info(f"Enabling interrupt recording")
|
||||
if not isinstance(self.target.protocols.monitor, OpenOCDProtocol):
|
||||
raise Exception(
|
||||
"ARMV7InterruptRecordingProtocol requires OpenOCDProtocol to be present.")
|
||||
|
||||
self.inject_monitor_stub()
|
||||
|
||||
self.log.info(f"Starting interrupt thread")
|
||||
self.start()
|
||||
except:
|
||||
self.log.exception("Error starting ARMV7InterruptRecordingProtocol")
|
||||
|
||||
# TODO what this stub does
|
||||
MONITOR_STUB = ("" +
|
||||
# Data
|
||||
# vt_buffer_X: .word 0x00000000 # Buffer holding the original vector table
|
||||
# irq_buffer_X: .hword 0x0000 # MTB ring buffer of interrupt events
|
||||
"irq_buffer_ptr: .word 0xdeafbeef\n" +
|
||||
|
||||
# "stub: \n" +
|
||||
"push {r4, r5, r6, r7}\n" +
|
||||
# "mrs r0, IPSR\n" + # Get the interrupt number
|
||||
"nop\nnop\n" + # Placeholder to be replaced with `mrs r5, IPSR` due to keystone error
|
||||
"ldr r1, =irq_buffer_ptr\n" +
|
||||
"ldr r2, =irq_buffer_0\n" +
|
||||
"ldr r3, [r1]\n" + # Load the buffer pointer
|
||||
"mov r4, r3\n" +
|
||||
"add r4, r4, r2\n" +
|
||||
|
||||
# Ensure end of buffer flag is set
|
||||
"adds r3, r3, #1\n" + # Increment the buffer pointer
|
||||
"movs r5, #255\n" + # For anding to implement wrap around
|
||||
"ands r3, r3, r5\n" + # Wrap around the buffer
|
||||
"strb r5, [r2, r3]\n" +
|
||||
|
||||
# Save the interrupt number
|
||||
"strb r0, [r4]\n" +
|
||||
|
||||
# Setup jump to interrupt handler
|
||||
"ldr r2, =vt_buffer_0\n" +
|
||||
"mov r6, r0\n" +
|
||||
"lsls r6, #2\n" + # Calculate interrupt offset
|
||||
"adds r6, r6, r2\n" +
|
||||
"ldr r6, [r6]\n" + # Load the interrupt handler address
|
||||
|
||||
# Call the interrupt handler
|
||||
"mov r7, lr\n" +
|
||||
"push {r0, r1, r2, r3, r4, r5, r6, r7}\n"
|
||||
"blx r6\n" + # Jump to interrupt handler
|
||||
"pop {r0, r1, r2, r3, r4, r5, r6, r7}\n"
|
||||
"mov lr, r7\n" +
|
||||
|
||||
# Store the interrupt return
|
||||
"ldr r2, =irq_buffer_0\n" +
|
||||
"mov r4, r3\n" +
|
||||
"add r4, r4, r2\n" +
|
||||
|
||||
# Ensure end of buffer flag is set
|
||||
"adds r3, r3, #1\n" + # Increment the buffer pointer
|
||||
"movs r5, #255\n" + # For anding to implement wrap around
|
||||
"ands r3, r3, r5\n" + # Wrap around the buffer
|
||||
"strb r3, [r1]\n" + # Save the buffer pointer
|
||||
"strb r5, [r2, r3]\n" +
|
||||
|
||||
"movs r5, #128\n" + # For oring to signal interrupt exit
|
||||
"orrs r5, r5, r0\n" + # Flip 8th bit
|
||||
"strb r5, [r4]\n" + # Save the interrupt number with exit flag (highest bit)
|
||||
|
||||
# Restore registers and return
|
||||
"pop {r4, r5, r6, r7}\n" +
|
||||
"bx lr\n" # Return from the interrupt, set by the interrupt calling convention
|
||||
)
|
||||
|
||||
def _get_stub(self, vt_size=48, irq_buffer_size=256):
|
||||
vt_declaration = [f"vt_buffer_{i}: .word 0x00000000" for i in range(vt_size)]
|
||||
vt_declaration = "\n".join(vt_declaration)
|
||||
buffer_declaration = [f"irq_buffer_{i}: .hword 0x0000" for i in range(irq_buffer_size)]
|
||||
buffer_declaration = "\n".join(buffer_declaration)
|
||||
return vt_declaration + buffer_declaration + self.MONITOR_STUB
|
||||
|
||||
def set_isr(self, interrupt_num, addr):
|
||||
base = self.get_ivt_addr()
|
||||
ivt_addr = base + (interrupt_num * 4)
|
||||
return self.target.write_memory(ivt_addr, 4, addr)
|
||||
|
||||
def inject_monitor_stub(self, addr=0x20010000, vtor=0x20011000, num_isr=48):
|
||||
"""
|
||||
Injects a safe monitoring stub.
|
||||
This has the following effects:
|
||||
0. Pivot the VTOR to someplace sane
|
||||
1. Insert an infinite loop at addr
|
||||
2. Set the PC to addr
|
||||
3. set up logic for the injection of interrupt returns.
|
||||
Write to return_code_register to trigger an IRET
|
||||
4.
|
||||
:return:
|
||||
"""
|
||||
self.log.warning(
|
||||
f"Injecting monitor stub into {self.target.name}. (IVT: 0x{self.get_ivt_addr():08x}, 0x{self.get_vtor():08x}, 0x{vtor:08x})")
|
||||
|
||||
self._monitor_stub_base = addr
|
||||
self.log.info(f"_monitor_stub_base = 0x{self._monitor_stub_base:08x}")
|
||||
self._monitor_stub_trace_buffer = addr + num_isr * 4
|
||||
self.log.info(f"_monitor_stub_trace_buffer = 0x{self._monitor_stub_trace_buffer:08x}")
|
||||
self._monitor_stub_vt_buffer = addr
|
||||
self.log.info(f"_monitor_stub_vt_buffer = 0x{self._monitor_stub_vt_buffer:08x}")
|
||||
self._monitor_stub_mtb = addr + num_isr * 4
|
||||
self.log.info(f"_monitor_stub_mtb = 0x{self._monitor_stub_mtb:08x}")
|
||||
self._monitor_stub_isr = addr + num_isr * 4 + 256 * 2 + 4
|
||||
self.log.info(f"_monitor_stub_isr = 0x{self._monitor_stub_isr:08x}")
|
||||
|
||||
# Pivot VTOR, if needed
|
||||
# On CM0, you can't, so don't.
|
||||
self._original_vtor = self.get_vtor()
|
||||
assert self._original_vtor != vtor, "VTOR is already set to the desired value."
|
||||
|
||||
self.set_vtor(vtor)
|
||||
self.log.info(f"Validate new VTOR address 0x{self.get_vtor():8x}")
|
||||
|
||||
# Sometimes, we need to gain access to the IVT (make it writable). Do that here.
|
||||
if getattr(self.target, 'ivt_unlock', None) is not None:
|
||||
unlock_addr, unlock_val = self.target.ivt_unlock
|
||||
self.target.write_memory(unlock_addr, 4, unlock_val)
|
||||
|
||||
self.log.info(f"Inserting the stub ...")
|
||||
# Inject the stub
|
||||
stub_offset = self._monitor_stub_isr - self._monitor_stub_base + 2
|
||||
self.target.inject_asm(self._get_stub(), self._monitor_stub_base, patch={stub_offset: b'\xef\xf3\x05\x80'})
|
||||
self.target.write_memory(self._monitor_stub_isr - 4, size=4, value=0x00) # set irq_buffer_ptr to 0
|
||||
self.target.write_memory(self._monitor_stub_mtb, size=1, value=0xff) # Ensure end of buffer flag
|
||||
|
||||
self.log.info(f"Setting up IVT buffer...")
|
||||
# Copy the vector table to our buffer
|
||||
self.original_vt = self.target.read_memory(self._original_vtor, size=4, num_words=num_isr)
|
||||
self.target.write_memory(self._monitor_stub_vt_buffer, value=self.original_vt, size=4, num_words=num_isr)
|
||||
|
||||
self.log.info(f"Setting up IVT...")
|
||||
# Set the IVT to our stub but DON'T wipe out the 0'th position.
|
||||
self.target.write_memory(vtor, value=self.target.read_memory(self._original_vtor, size=4), size=4)
|
||||
for interrupt_num in range(1, num_isr):
|
||||
self.set_isr(interrupt_num, self._monitor_stub_isr + 1) # +1 for thumb mode
|
||||
|
||||
def _dispatch_message(self, message):
|
||||
self._avatar_fast_queue.put(message)
|
||||
|
||||
def run(self):
|
||||
TICK_DELAY = 0.0001
|
||||
self.log.info("Starting ARMV7InterruptRecordingProtocol thread")
|
||||
|
||||
# Wait for init
|
||||
while self._monitor_stub_base is None:
|
||||
sleep(TICK_DELAY)
|
||||
|
||||
buffer_pos = 0
|
||||
try:
|
||||
while not (self.avatar._close.is_set() or self._close.is_set()):
|
||||
curr_isr = self.target.read_memory(address=self._monitor_stub_trace_buffer + buffer_pos, size=1)
|
||||
if curr_isr == 0xff:
|
||||
sleep(TICK_DELAY)
|
||||
continue
|
||||
|
||||
self.msg_counter += 1
|
||||
buffer_pos = (buffer_pos + 1) & 0xff
|
||||
|
||||
if curr_isr > 0x80:
|
||||
curr_isr = curr_isr & 0x7f
|
||||
addr = self.original_vt[curr_isr]
|
||||
self._dispatch_message(
|
||||
TargetInterruptExitMessage(self.target, self.msg_counter, interrupt_num=curr_isr,
|
||||
isr_addr=addr))
|
||||
else:
|
||||
addr = self.original_vt[curr_isr]
|
||||
self._dispatch_message(
|
||||
TargetInterruptEnterMessage(self.target, self.msg_counter, interrupt_num=curr_isr,
|
||||
isr_addr=addr))
|
||||
|
||||
|
||||
except:
|
||||
self.log.exception("Error processing trace")
|
||||
self._closed.set()
|
||||
self.log.debug("Interrupt thread exiting...")
|
||||
self._closed.set()
|
||||
|
||||
def stop(self):
|
||||
"""Stops the listening thread. Useful for teardown of the target"""
|
||||
self._close.set()
|
||||
self._closed.wait()
|
||||
+35
-39
@@ -9,6 +9,7 @@ import re
|
||||
import pygdbmi.gdbcontroller
|
||||
|
||||
import parse
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
import Queue as queue
|
||||
# __class__ = instance.__class__
|
||||
@@ -33,13 +34,13 @@ class GDBResponseListener(Thread):
|
||||
"""
|
||||
|
||||
def __init__(self, gdb_protocol, gdb_controller, avatar_queue,
|
||||
avatar_fast_queue, origin=None):
|
||||
avatar_fast_queue, origin=None):
|
||||
super(GDBResponseListener, self).__init__()
|
||||
self._protocol = gdb_protocol
|
||||
self._token = -1
|
||||
self._async_responses = queue.Queue() if avatar_queue is None \
|
||||
else avatar_queue
|
||||
self._async_fast_responses = queue.Queue() if avatar_fast_queue is None\
|
||||
self._async_fast_responses = queue.Queue() if avatar_fast_queue is None \
|
||||
else avatar_fast_queue
|
||||
self._sync_responses = {}
|
||||
self._gdb_controller = gdb_controller
|
||||
@@ -128,10 +129,10 @@ class GDBResponseListener(Thread):
|
||||
self._origin, TargetStates.STOPPED)
|
||||
elif payload.get('reason') == 'syscall-entry':
|
||||
avatar_msg = SyscallCatchedMessage(self._origin, int(payload['bkptno']),
|
||||
int(payload['frame']['addr'], 16), 'entry')
|
||||
int(payload['frame']['addr'], 16), 'entry')
|
||||
elif payload.get('reason') == 'syscall-return':
|
||||
avatar_msg = SyscallCatchedMessage(self._origin, int(payload['bkptno']),
|
||||
int(payload['frame']['addr'], 16), 'return')
|
||||
int(payload['frame']['addr'], 16), 'return')
|
||||
elif payload.get('reason') is not None:
|
||||
self.log.critical("Target stopped with unknown reason: %s" %
|
||||
payload['reason'])
|
||||
@@ -233,6 +234,7 @@ class GDBResponseListener(Thread):
|
||||
self._console_output += '\n'
|
||||
self._console_output += msg['payload']
|
||||
|
||||
|
||||
class GDBProtocol(object):
|
||||
"""Main class for the gdb communication protocol
|
||||
:ivar gdb_executable: the path to the gdb which should be executed
|
||||
@@ -468,10 +470,9 @@ class GDBProtocol(object):
|
||||
"""
|
||||
if hasattr(self._origin, 'regs'):
|
||||
regs = self.get_register_names()
|
||||
regs_dict = dict([(r,i) for i, r in enumerate(regs) if r != ''])
|
||||
regs_dict = dict([(r, i) for i, r in enumerate(regs) if r != ''])
|
||||
self._origin.regs._update(regs_dict)
|
||||
|
||||
|
||||
def set_breakpoint(self, line,
|
||||
hardware=False,
|
||||
temporary=False,
|
||||
@@ -583,9 +584,6 @@ class GDBProtocol(object):
|
||||
return True
|
||||
return int(expected_bp_num)
|
||||
|
||||
|
||||
|
||||
|
||||
def remove_breakpoint(self, bkpt):
|
||||
"""Deletes a breakpoint"""
|
||||
ret, resp = self._sync_request(
|
||||
@@ -596,13 +594,13 @@ class GDBProtocol(object):
|
||||
resp)
|
||||
return ret
|
||||
|
||||
def write_memory(self, address, wordsize, val, num_words=1, raw=False):
|
||||
def write_memory(self, address, size, value, num_words=1, raw=False):
|
||||
"""Writes memory
|
||||
|
||||
:param address: Address to write to
|
||||
:param wordsize: the size of the write (1, 2, 4 or 8)
|
||||
:param val: the written value
|
||||
:type val: int if num_words == 1 and raw == False
|
||||
:param size: the size of the write (1, 2, 4 or 8)
|
||||
:param value: the written value
|
||||
:type value: int if num_words == 1 and raw == False
|
||||
list if num_words > 1 and raw == False
|
||||
str or byte if raw == True
|
||||
:param num_words: The amount of words to read
|
||||
@@ -614,34 +612,36 @@ class GDBProtocol(object):
|
||||
max_write_size = 0x100
|
||||
|
||||
if raw:
|
||||
if not len(val):
|
||||
if not len(value):
|
||||
raise ValueError("val had zero length")
|
||||
for i in range(0, len(val), max_write_size):
|
||||
write_val = encode(val[i:max_write_size + i], 'hex_codec').decode('ascii')
|
||||
for i in range(0, len(value), max_write_size):
|
||||
write_val = encode(value[i:max_write_size + i], 'hex_codec').decode('ascii')
|
||||
ret, resp = self._sync_request(
|
||||
["-data-write-memory-bytes", str(address + i), write_val],
|
||||
GDB_PROT_DONE)
|
||||
|
||||
else:
|
||||
fmt = '<%d%s' % (num_words, num2fmt[wordsize])
|
||||
fmt = '<%d%s' % (num_words, num2fmt[size])
|
||||
if num_words == 1:
|
||||
contents = pack(fmt, val)
|
||||
contents = pack(fmt, value)
|
||||
else:
|
||||
contents = pack(fmt, *val)
|
||||
contents = pack(fmt, *value)
|
||||
|
||||
hex_contents = encode(contents, 'hex_codec').decode('ascii')
|
||||
ret, resp = self._sync_request(
|
||||
["-data-write-memory-bytes", str(address), hex_contents],
|
||||
GDB_PROT_DONE)
|
||||
|
||||
self.log.debug("Attempted to write memory. Received response: %s" % resp)
|
||||
if 'message' in resp and resp['message'] == 'error':
|
||||
self.log.error("Attempted to write memory. Received error response: %s" % resp)
|
||||
else:
|
||||
self.log.debug("Attempted to write memory. Received response [%s]: %s" % (ret, resp))
|
||||
return ret
|
||||
|
||||
def read_memory(self, address, wordsize=4, num_words=1, raw=False):
|
||||
def read_memory(self, address, size=4, num_words=1, raw=False):
|
||||
"""reads memory
|
||||
|
||||
:param address: Address to read from
|
||||
:param wordsize: the size of a read word (1, 2, 4 or 8)
|
||||
:param size: the size of a read word (1, 2, 4 or 8)
|
||||
:param num_words: the amount of read words
|
||||
:param raw: Whether the read memory should be returned unprocessed
|
||||
:return: The read memory
|
||||
@@ -651,9 +651,9 @@ class GDBProtocol(object):
|
||||
|
||||
max_read_size = 0x100
|
||||
raw_mem = b''
|
||||
for i in range(0, wordsize * num_words, max_read_size):
|
||||
to_read = max_read_size if wordsize * num_words > i + max_read_size - 1 else \
|
||||
wordsize * num_words % max_read_size
|
||||
for i in range(0, size * num_words, max_read_size):
|
||||
to_read = max_read_size if size * num_words > i + max_read_size - 1 else \
|
||||
size * num_words % max_read_size
|
||||
res, resp = self._sync_request(["-data-read-memory-bytes", str(address + i),
|
||||
str(to_read)],
|
||||
GDB_PROT_DONE)
|
||||
@@ -661,7 +661,7 @@ class GDBProtocol(object):
|
||||
self.log.debug("Attempted to read memory. Received response: %s" % resp)
|
||||
|
||||
if not res:
|
||||
raise Exception("Failed to read memory!")
|
||||
raise Exception("Failed to read memory! Response: %s" % resp)
|
||||
|
||||
# the indirection over the bytearray is needed for legacy python support
|
||||
read_mem = bytearray.fromhex(resp['payload']['memory'][0]['contents'])
|
||||
@@ -671,7 +671,7 @@ class GDBProtocol(object):
|
||||
return raw_mem
|
||||
else:
|
||||
# Todo: Endianness support
|
||||
fmt = '<%d%s' % (num_words, num2fmt[wordsize])
|
||||
fmt = '<%d%s' % (num_words, num2fmt[size])
|
||||
mem = list(unpack(fmt, raw_mem))
|
||||
|
||||
if num_words == 1:
|
||||
@@ -699,8 +699,8 @@ class GDBProtocol(object):
|
||||
|
||||
ret, resp = self._sync_request(
|
||||
["-data-evaluate-expression", "%s" %
|
||||
self._arch.special_registers[reg]['gdb_expression']],
|
||||
GDB_PROT_DONE)
|
||||
self._arch.special_registers[reg]['gdb_expression']],
|
||||
GDB_PROT_DONE)
|
||||
fmt = self._arch.special_registers[reg]['format']
|
||||
res = parse.parse(fmt, resp['payload']['value'])
|
||||
if res is None:
|
||||
@@ -710,9 +710,6 @@ class GDBProtocol(object):
|
||||
raise Exception("Couldn't parse special register")
|
||||
return list(res)
|
||||
|
||||
|
||||
|
||||
|
||||
def read_register_from_nr(self, reg_num):
|
||||
"""Gets the value of a single register
|
||||
|
||||
@@ -737,12 +734,12 @@ class GDBProtocol(object):
|
||||
if reg in self._arch.special_registers:
|
||||
|
||||
fmt = "{:s}=" \
|
||||
+ self._arch.special_registers[reg]['format'].replace(' ','')
|
||||
+ self._arch.special_registers[reg]['format'].replace(' ', '')
|
||||
|
||||
ret, resp = self._sync_request(
|
||||
["-data-evaluate-expression", fmt.format(
|
||||
self._arch.special_registers[reg]['gdb_expression'], *value)
|
||||
], GDB_PROT_DONE
|
||||
self._arch.special_registers[reg]['gdb_expression'], *value)
|
||||
], GDB_PROT_DONE
|
||||
)
|
||||
else:
|
||||
ret, resp = self._sync_request(
|
||||
@@ -777,9 +774,8 @@ class GDBProtocol(object):
|
||||
:returns: True on success"""
|
||||
ret, resp = self._sync_request(["-exec-continue"], GDB_PROT_RUN)
|
||||
|
||||
self.log.debug(
|
||||
"Attempted to continue execution on the target. Received response: %s" %
|
||||
resp)
|
||||
self.log.info(
|
||||
"Attempted to continue execution on the target. Received response: %s, returning %s" % (resp, ret))
|
||||
return ret
|
||||
|
||||
def stop(self):
|
||||
|
||||
@@ -10,6 +10,7 @@ from struct import pack, unpack
|
||||
from time import sleep
|
||||
import re
|
||||
from os.path import abspath
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
import Queue as queue
|
||||
else:
|
||||
@@ -24,6 +25,7 @@ END_OF_MSG = u'\x1a'
|
||||
class OpenOCDProtocol(Thread):
|
||||
"""
|
||||
This class implements the openocd protocol.
|
||||
__IMPORTANT:__ This protocol requries the mem_helper.tcl script to be included in the OpenOCD config!
|
||||
|
||||
:param openocd_script: The openocd scripts to be executed.
|
||||
:type openocd_script: str or list
|
||||
@@ -57,7 +59,7 @@ class OpenOCDProtocol(Thread):
|
||||
else:
|
||||
raise TypeError("Wrong type for OpenOCD configuration files")
|
||||
self.log = logging.getLogger('%s.%s' % (origin.log.name, self.__class__.__name__)) if origin else \
|
||||
logging.getLogger(self.__class__.__name__)
|
||||
logging.getLogger(self.__class__.__name__)
|
||||
self._tcl_port = tcl_port
|
||||
self._gdb_port = gdb_port
|
||||
self._host = host
|
||||
@@ -82,7 +84,7 @@ class OpenOCDProtocol(Thread):
|
||||
in [['-f', abspath(f)] for f in self.openocd_files]
|
||||
for e in l]
|
||||
self._cmd_line += ['--command', 'tcl_port %d' % self._tcl_port,
|
||||
'--command', 'gdb_port %d' % self._gdb_port]
|
||||
'--command', 'gdb_port %d' % self._gdb_port]
|
||||
self._cmd_line += additional_args
|
||||
|
||||
self._openocd = None
|
||||
@@ -91,24 +93,22 @@ class OpenOCDProtocol(Thread):
|
||||
open("%s/openocd_err.txt" % output_directory, "wb") as err:
|
||||
self.log.debug("Starting OpenOCD with command line: %s" % (" ".join(self._cmd_line)))
|
||||
self._openocd = subprocess.Popen(self._cmd_line,
|
||||
stdout=out, stderr=err)#, shell=True)
|
||||
stdout=out, stderr=err) # , shell=True)
|
||||
Thread.__init__(self)
|
||||
self.daemon = True
|
||||
|
||||
|
||||
def connect(self):
|
||||
"""
|
||||
Connects to OpenOCDs TCL Server for all subsequent communication
|
||||
returns: True on success, else False
|
||||
"""
|
||||
sleep(1)
|
||||
|
||||
|
||||
if self._openocd.poll() is not None:
|
||||
raise RuntimeError(("Openocd errored! Please check "
|
||||
"%s/openocd_err.txt for details" %
|
||||
self.output_directory))
|
||||
|
||||
|
||||
self.log.debug("Connecting to OpenOCD on %s:%s" % (self._host, self._tcl_port))
|
||||
try:
|
||||
self.telnet = telnetlib.Telnet(self._host, self._tcl_port)
|
||||
@@ -136,8 +136,8 @@ class OpenOCDProtocol(Thread):
|
||||
:return:
|
||||
"""
|
||||
self.log.debug("Enabling tracing...")
|
||||
resp = self.execute_command("ocd_tcl_trace on")
|
||||
if 'is enabled' in resp:
|
||||
resp = self.execute_command("tcl_trace all")
|
||||
if resp == '':
|
||||
self.trace_enabled.set()
|
||||
return True
|
||||
else:
|
||||
@@ -187,7 +187,7 @@ class OpenOCDProtocol(Thread):
|
||||
else:
|
||||
self.log.warning("Weird target state %s" % state)
|
||||
elif mevent:
|
||||
#TODO handle these
|
||||
# TODO handle these
|
||||
event = mevent.group(1)
|
||||
self.log.debug("Target event: %s " % event)
|
||||
# TODO handle these
|
||||
@@ -201,7 +201,6 @@ class OpenOCDProtocol(Thread):
|
||||
else:
|
||||
self.log.warning("Unhandled event message %s" % str)
|
||||
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Resets the target
|
||||
@@ -221,7 +220,7 @@ class OpenOCDProtocol(Thread):
|
||||
Shuts down OpenOCD
|
||||
returns: True on success, else False
|
||||
"""
|
||||
#self.execute_command('ocd_shutdown')
|
||||
# self.execute_command('ocd_shutdown')
|
||||
self._close.set()
|
||||
if self.telnet:
|
||||
self.telnet.close()
|
||||
@@ -250,7 +249,7 @@ class OpenOCDProtocol(Thread):
|
||||
while not self.avatar._close.is_set() and not self._close.is_set():
|
||||
if not self.in_queue.empty():
|
||||
cmd = self.in_queue.get()
|
||||
self.log.debug("Executing command %s" % cmd)
|
||||
self.log.debug("Executing command '%s'" % cmd)
|
||||
self.telnet.write((cmd + END_OF_MSG).encode('ascii'))
|
||||
try:
|
||||
line = self.read_response()
|
||||
@@ -259,7 +258,7 @@ class OpenOCDProtocol(Thread):
|
||||
self.shutdown()
|
||||
break
|
||||
if line is not None:
|
||||
#print line
|
||||
# print line
|
||||
line = line.rstrip(END_OF_MSG)
|
||||
# This is async target notification data. Don't return it normally
|
||||
if line.startswith("type"):
|
||||
@@ -275,18 +274,18 @@ class OpenOCDProtocol(Thread):
|
||||
# We didn't ask for it. Just debug it
|
||||
self.log.debug(line)
|
||||
else:
|
||||
self.log.debug("response --> " + line)
|
||||
self.log.debug("response --> '%s'" % line)
|
||||
self.out_queue.put(line)
|
||||
cmd = None
|
||||
sleep(.001) # Have a heart. Give other threads a chance
|
||||
sleep(.001) # Have a heart. Give other threads a chance
|
||||
except Exception as e:
|
||||
self.log.exception("OpenOCD Background thread died with an exception")
|
||||
self.log.debug("OpenOCD Background thread exiting")
|
||||
|
||||
def read_response(self):
|
||||
self.buf += self.telnet.read_eager().decode('ascii')
|
||||
#if buf is not '':
|
||||
#print(self.buf)
|
||||
# if buf is not '':
|
||||
# print(self.buf)
|
||||
if END_OF_MSG in self.buf:
|
||||
resp, self.buf = self.buf.split(END_OF_MSG, 1)
|
||||
return resp
|
||||
@@ -294,76 +293,83 @@ class OpenOCDProtocol(Thread):
|
||||
|
||||
### The Memory Protocol starts here
|
||||
|
||||
def write_memory(self, address, wordsize, val, num_words=1, raw=False):
|
||||
def write_memory(self, address, size, value, num_words=1, raw=False):
|
||||
"""Writes memory
|
||||
|
||||
:param address: Address to write to
|
||||
:param wordsize: the size of the write (1, 2, 4 or 8)
|
||||
:param val: the written value
|
||||
:type val: int if num_words == 1 and raw == False
|
||||
:param size: the size of the write (1, 2, 4)
|
||||
:param value: the written value
|
||||
:type value: int if num_words == 1 and raw == False
|
||||
list if num_words > 1 and raw == False
|
||||
str or byte if raw == True
|
||||
:param num_words: The amount of words to read
|
||||
:param num_words: The amount of words to write
|
||||
:param raw: Specifies whether to write in raw or word mode
|
||||
:returns: True on success else False
|
||||
"""
|
||||
#print "nucleo.write_memory(%s, %s, %s, %s, %s)" % (repr(address), repr(wordsize), repr(val), repr(num_words), repr(raw))
|
||||
if isinstance(val, str) and len(val) != num_words:
|
||||
self.log.debug("Setting num_words = %d" % (len(val) / wordsize))
|
||||
num_words = len(val) / wordsize
|
||||
for i in range(0, num_words, wordsize):
|
||||
if raw:
|
||||
write_val = '0x' + encode(val[i:i+wordsize], 'hex_codec').decode('ascii')
|
||||
elif isinstance(val, int) or isinstance(val, long):
|
||||
write_val = hex(val).rstrip("L")
|
||||
else:
|
||||
# A list of ints
|
||||
write_val = hex(val[i]).rstrip("L")
|
||||
write_addr = hex(address + i).rstrip("L")
|
||||
if wordsize == 1:
|
||||
self.execute_command('mwb %s %s' % (write_addr, write_val))
|
||||
elif wordsize == 2:
|
||||
self.execute_command('mwh %s %s' % (write_addr, write_val))
|
||||
else:
|
||||
self.execute_command('mww %s %s' % (write_addr, write_val))
|
||||
if raw:
|
||||
if not (isinstance(value, list) or isinstance(value, bytes) or isinstance(value, bytearray)):
|
||||
self.log.error("Raw write value must be a list of integers")
|
||||
return False
|
||||
write_val = '{' + ' '.join([hex(v).rstrip("L") for v in value]) + '}'
|
||||
width = size * 8
|
||||
self.execute_command("write_memory %s %d %s" % (hex(address).rstrip("L"), width, write_val))
|
||||
else:
|
||||
for i in range(0, num_words):
|
||||
if isinstance(value, int):
|
||||
write_val = hex(value).rstrip("L")
|
||||
else:
|
||||
# A list of ints
|
||||
write_val = hex(value[i]).rstrip("L")
|
||||
write_addr = hex(address + i * size).rstrip("L")
|
||||
if size == 1:
|
||||
self.execute_command(f'mwb {write_addr} {write_val}')
|
||||
elif size == 2:
|
||||
self.execute_command(f'mwh {write_addr} {write_val}')
|
||||
else:
|
||||
self.execute_command(f'mww {write_addr} {write_val}')
|
||||
|
||||
return True
|
||||
|
||||
def read_memory(self, address, wordsize=4, num_words=1, raw=False):
|
||||
def read_memory(self, address, size=4, num_words=1, raw=False):
|
||||
"""reads memory
|
||||
|
||||
:param address: Address to write to
|
||||
:param wordsize: the size of a read word (1, 2, 4 or 8)
|
||||
:param size: the size of a read word (1, 2, 4 or 8)
|
||||
:param num_words: the amount of read words
|
||||
:param raw: Whether the read memory should be returned unprocessed
|
||||
:return: The read memory
|
||||
"""
|
||||
num2fmt = {1: 'B', 2: 'H', 4: 'I', 8: 'Q'}
|
||||
raw_mem = b''
|
||||
words = []
|
||||
for i in range(0, num_words, wordsize):
|
||||
read_addr = hex(address + i).rstrip('L')
|
||||
if wordsize == 1:
|
||||
resp = self.execute_command('mrb %s' % read_addr)
|
||||
elif wordsize == 2:
|
||||
resp = self.execute_command("mrh %s" % read_addr)
|
||||
else:
|
||||
resp = self.execute_command('mrw %s' % read_addr)
|
||||
if resp:
|
||||
val = int(resp)
|
||||
raw_mem += binascii.unhexlify(hex(val)[2:].zfill(wordsize * 2))
|
||||
else:
|
||||
self.log.error("Could not read from address %s" % read_addr)
|
||||
return None
|
||||
# OCD flips the endianness
|
||||
raw_mem = raw_mem[::-1]
|
||||
if raw:
|
||||
self.log.debug("Read %s from %#08x" % (repr(raw), address))
|
||||
return raw_mem
|
||||
ocd_size = size * 8
|
||||
resp = self.execute_command("read_memory %s %d %d" % (hex(address).rstrip("L"), ocd_size, num_words))
|
||||
if resp:
|
||||
raw_mem = bytearray([int(v, 16) for v in resp.split(' ')])
|
||||
return raw_mem
|
||||
else:
|
||||
self.log.error("Could not read from address %s" % hex(address))
|
||||
return None
|
||||
else:
|
||||
for i in range(0, num_words):
|
||||
read_addr = hex(address + (i * size)).rstrip('L')
|
||||
if size == 1:
|
||||
resp = self.execute_command('mrb %s' % read_addr)
|
||||
elif size == 2:
|
||||
resp = self.execute_command("mrh %s" % read_addr)
|
||||
else:
|
||||
resp = self.execute_command('mrw %s' % read_addr)
|
||||
if resp:
|
||||
val = int(resp, 16)
|
||||
raw_mem += val.to_bytes(size, byteorder=sys.byteorder)
|
||||
else:
|
||||
self.log.error("Could not read from address %s" % read_addr)
|
||||
return None
|
||||
|
||||
# Todo: Endianness support
|
||||
fmt = '<%d%s' % (num_words, num2fmt[wordsize])
|
||||
fmt = '<%d%s' % (num_words, num2fmt[size])
|
||||
mem = list(unpack(fmt, raw_mem))
|
||||
|
||||
if num_words == 1:
|
||||
return mem[0]
|
||||
else:
|
||||
@@ -374,7 +380,7 @@ class OpenOCDProtocol(Thread):
|
||||
def read_register(self, reg):
|
||||
|
||||
try:
|
||||
resp = self.execute_command("ocd_reg %s" % reg)
|
||||
resp = self.execute_command("reg %s" % reg)
|
||||
val = int(resp.split(":")[1].strip(), 16)
|
||||
return val
|
||||
except:
|
||||
@@ -385,7 +391,7 @@ class OpenOCDProtocol(Thread):
|
||||
"""Set one register on the target
|
||||
:returns: True on success"""
|
||||
try:
|
||||
self.execute_command("ocd_reg %s %s" % (reg, hex(value)))
|
||||
self.execute_command("reg %s %s" % (reg, hex(value)))
|
||||
return True
|
||||
except:
|
||||
self.log.exception(("Error writing register %s" % reg))
|
||||
@@ -451,7 +457,7 @@ class OpenOCDProtocol(Thread):
|
||||
cmd.append("%#08x" % line)
|
||||
else:
|
||||
cmd.append(str(line))
|
||||
cmd.append("2") # TODO: This isn't platform-independent, but i have no idea what it does
|
||||
cmd.append("2") # TODO: This isn't platform-independent, but i have no idea what it does
|
||||
if hardware:
|
||||
cmd.append("hw")
|
||||
try:
|
||||
@@ -469,7 +475,7 @@ class OpenOCDProtocol(Thread):
|
||||
cmd.append("%#08x" % variable)
|
||||
else:
|
||||
cmd.append(str(variable))
|
||||
cmd.append("2") # TODO FIXME
|
||||
cmd.append("2") # TODO FIXME
|
||||
if read and write:
|
||||
cmd.append("a")
|
||||
elif read:
|
||||
@@ -497,4 +503,3 @@ class OpenOCDProtocol(Thread):
|
||||
return True
|
||||
except:
|
||||
self.log.exception("Error removing breakpoint")
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import logging
|
||||
import queue
|
||||
from threading import Thread, Event
|
||||
|
||||
from avatar2.message import BreakpointHitMessage, HWEnterMessage, HWExitMessage
|
||||
from avatar2.plugins.arm.hal import HWFunction
|
||||
from avatar2.watchmen import AFTER
|
||||
|
||||
CMD_CONT = 0
|
||||
|
||||
|
||||
class QemuARMv7MHWRunnerProtocol(Thread):
|
||||
def __init__(self, avatar, origin):
|
||||
self.avatar = avatar
|
||||
self._avatar_fast_queue = avatar.fast_queue
|
||||
self._close = Event()
|
||||
self._closed = Event()
|
||||
self.target = origin
|
||||
self.functions: [HWFunction] = []
|
||||
self.command_queue = queue.Queue()
|
||||
|
||||
self.log = logging.getLogger(f'{avatar.log.name}.protocols.{self.__class__.__name__}')
|
||||
Thread.__init__(self, daemon=True, name=f"Thread-{self.__class__.__name__}")
|
||||
self.log.info(f"QemuARMV7HALCallerProtocol initialized")
|
||||
|
||||
def __del__(self):
|
||||
self.shutdown()
|
||||
|
||||
def shutdown(self):
|
||||
if self.is_alive() is True:
|
||||
self.stop()
|
||||
|
||||
def connect(self):
|
||||
pass
|
||||
|
||||
def enable(self, functions: [HWFunction]):
|
||||
try:
|
||||
self.log.info(f"Enabling QEmu HAL catching")
|
||||
self.functions = functions
|
||||
for func in self.functions:
|
||||
self.log.info(f"Setting breakpoint at 0x{func.address:x}")
|
||||
self.target.set_breakpoint(func.address)
|
||||
|
||||
self.avatar.watchmen.add_watchman('BreakpointHit', AFTER, self._handle_breakpoint)
|
||||
|
||||
self.start()
|
||||
self.log.info(f"Starting QEmu HAL catching thread")
|
||||
except:
|
||||
self.log.exception("Error starting QemuARMV7HALCallerProtocol")
|
||||
|
||||
def _handle_breakpoint(self, avatar, message: BreakpointHitMessage, *args, **kwargs):
|
||||
if message.origin != self.target:
|
||||
return
|
||||
for function in self.functions:
|
||||
if message.address == function.address:
|
||||
self.log.info(f"Dispatching HWEnterMessage for function at 0x{function.address:x}")
|
||||
return_address = self.target.regs.lr
|
||||
self._dispatch_message(HWEnterMessage(self.target, function, return_address=return_address))
|
||||
return
|
||||
|
||||
def handle_func_return(self, message: HWExitMessage):
|
||||
self.log.info(
|
||||
f"Continuing QEmu, injecting return value {message.return_val} and continuing at 0x{message.return_address:x}")
|
||||
if message.function.return_args is None or message.function.return_args[0] is not None:
|
||||
self.target.regs.r0 = message.return_val
|
||||
else:
|
||||
self.log.warning(f"Return value of function is void, skipping return value injection")
|
||||
self.target.regs.pc = message.return_address
|
||||
self.continue_target()
|
||||
|
||||
def continue_target(self):
|
||||
self.command_queue.put((CMD_CONT,))
|
||||
|
||||
def run(self):
|
||||
self.log.info("Starting QemuARMV7HALCallerProtocol thread")
|
||||
|
||||
try:
|
||||
while not (self.avatar._close.is_set() or self._close.is_set()):
|
||||
try:
|
||||
command = self.command_queue.get(timeout=1.0)
|
||||
if command[0] == CMD_CONT:
|
||||
self.target.cont()
|
||||
else:
|
||||
self.log.error(f"Unknown command {command[0]}")
|
||||
self.command_queue.task_done()
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
except:
|
||||
self.log.exception("Error processing QemuARMV7HALCallerProtocol thread")
|
||||
self._closed.set()
|
||||
self.log.debug("QemuARMV7HALCallerProtocol thread exiting...")
|
||||
self._closed.set()
|
||||
|
||||
def _dispatch_message(self, message):
|
||||
self._avatar_fast_queue.put(message)
|
||||
|
||||
def stop(self):
|
||||
"""Stops the listening thread. Useful for teardown of the target"""
|
||||
self._close.set()
|
||||
self._closed.wait()
|
||||
@@ -0,0 +1,261 @@
|
||||
import logging
|
||||
|
||||
from os import O_WRONLY, O_RDONLY
|
||||
from threading import Thread, Event, Condition
|
||||
from ctypes import Structure, c_uint32, c_uint64
|
||||
from enum import Enum
|
||||
from posix_ipc import MessageQueue, ExistentialError
|
||||
|
||||
from avatar2.message import RemoteInterruptEnterMessage
|
||||
from avatar2.message import RemoteInterruptExitMessage
|
||||
from avatar2.targets import QemuTarget
|
||||
|
||||
# NVIC stuff
|
||||
NVIC_ISER0 = 0xe000e100
|
||||
|
||||
|
||||
class RINOperation(Enum):
|
||||
ENTER = 0
|
||||
EXIT = 1
|
||||
|
||||
|
||||
class V7MRemoteInterruptNotification(Structure):
|
||||
_fields_ = [
|
||||
('id', c_uint64),
|
||||
('num_irq', c_uint32),
|
||||
('operation', c_uint32),
|
||||
('type', c_uint32)
|
||||
]
|
||||
|
||||
def __reduce__(self):
|
||||
# Define the mapping of desired field names to actual field names
|
||||
field_mapping = {'num-irq': 'num_irq'}
|
||||
|
||||
# Create a dictionary to store the serialized state
|
||||
state = {}
|
||||
|
||||
# Populate the state dictionary with the desired field names and values
|
||||
for desired_name, actual_name in field_mapping.items():
|
||||
state[desired_name] = getattr(self, actual_name)
|
||||
|
||||
# Return a tuple of callable and arguments for object reconstruction
|
||||
return self.__class__, (state,)
|
||||
|
||||
@classmethod
|
||||
def from_state(cls, state):
|
||||
# Define the reverse mapping of desired field names to actual field names
|
||||
field_mapping = {'num-irq': 'num_irq'}
|
||||
|
||||
# Create an instance of the class
|
||||
instance = cls()
|
||||
|
||||
# Retrieve field values from the serialized state using the desired field names
|
||||
# and assign them to the corresponding actual field names in the class instance
|
||||
for desired_name, actual_name in field_mapping.items():
|
||||
setattr(instance, actual_name, state[desired_name])
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
class V7MInterruptNotificationAck(Structure):
|
||||
_fields_ = [
|
||||
('id', c_uint64),
|
||||
('success', c_uint32),
|
||||
('operation', c_uint32),
|
||||
]
|
||||
|
||||
|
||||
class QEmuARMV7MInterruptProtocol(Thread):
|
||||
"""
|
||||
This protocol has two purposes:
|
||||
a) injecting interrupts into an analysis target
|
||||
b) extracting interrupt exits and putting them into the avatar queue
|
||||
(b) is necessary in cases where two targets need to be synched on the
|
||||
interrupts.
|
||||
The way a v7m-nvic implements interrupt return is to put a magic value
|
||||
into $pc, and the hardware does the actual magic of popping from the
|
||||
interrupt stack and restoring the context.
|
||||
However, the magic value defines the type of the interrupt return,
|
||||
and is hence synchronized on interrupt exit, alongside with the
|
||||
interrupt number
|
||||
:param target: Reference to the Target utilizing this protocol
|
||||
:param rx_queue_name: Name of the queue for receiving
|
||||
:param tx_queue_name: Name of the queue for sending
|
||||
"""
|
||||
|
||||
def __init__(self, target, rx_queue_name, tx_queue_name):
|
||||
super(self.__class__, self).__init__()
|
||||
self._rx_queue_name = rx_queue_name
|
||||
self._tx_queue_name = tx_queue_name
|
||||
self._rx_queue = None
|
||||
self._tx_queue = None
|
||||
self._avatar_queue = target.avatar.fast_queue
|
||||
self.target = target
|
||||
self._close = Event()
|
||||
self._closed = Event()
|
||||
self._close.clear()
|
||||
self._closed.clear()
|
||||
self.log = logging.getLogger(f'{target.log.name}.protocols.{self.__class__.__name__}')
|
||||
|
||||
def run(self):
|
||||
while not self._close.is_set():
|
||||
|
||||
request = None
|
||||
try:
|
||||
request = self._rx_queue.receive(0.5)
|
||||
except:
|
||||
continue
|
||||
|
||||
req_struct = V7MRemoteInterruptNotification.from_buffer_copy(
|
||||
request[0])
|
||||
|
||||
if RINOperation(req_struct.operation) == RINOperation.ENTER:
|
||||
msg = RemoteInterruptEnterMessage(self.target, req_struct.id,
|
||||
req_struct.num_irq)
|
||||
self.log.warning(
|
||||
"Received an InterruptEnterRequest for irq %d (%x)" %
|
||||
(req_struct.num_irq, req_struct.type))
|
||||
elif RINOperation(req_struct.operation) == RINOperation.EXIT:
|
||||
msg = RemoteInterruptExitMessage(self.target, req_struct.id,
|
||||
req_struct.type,
|
||||
req_struct.num_irq)
|
||||
self.log.warning(
|
||||
"Received an InterruptExitRequest for irq %d (%x)" %
|
||||
(req_struct.num_irq, req_struct.type))
|
||||
|
||||
else:
|
||||
msg = None
|
||||
raise Exception(("Received V7MRemoteInterrupt Notification with"
|
||||
"unknown operation type %d") %
|
||||
req_struct.operation)
|
||||
|
||||
self._avatar_queue.put(msg)
|
||||
|
||||
self._closed.set()
|
||||
|
||||
def stop(self):
|
||||
self._close.set()
|
||||
self._closed.wait()
|
||||
|
||||
def enable_interrupts(self):
|
||||
if isinstance(self.target, QemuTarget):
|
||||
# TODO: Make this more clean, i.e., check for remote memory
|
||||
rmem_rx_qname = self.target.protocols.remote_memory.rx_queue_name
|
||||
rmem_tx_qname = self.target.protocols.remote_memory.tx_queue_name
|
||||
# the tx-queue for qemu is the rx-queue for avatar and vice versa
|
||||
self.target.protocols.monitor.execute_command(
|
||||
'avatar-armv7m-enable-irq',
|
||||
{'irq-rx-queue-name': self._tx_queue_name,
|
||||
'irq-tx-queue-name': self._rx_queue_name,
|
||||
'rmem-rx-queue-name': rmem_tx_qname,
|
||||
'rmem-tx-queue-name': rmem_rx_qname
|
||||
}
|
||||
)
|
||||
else:
|
||||
raise Exception("V7MInterruptProtocol is not implemented for %s" %
|
||||
self.target.__class__)
|
||||
|
||||
try:
|
||||
self._rx_queue = MessageQueue(self._rx_queue_name, flags=O_RDONLY,
|
||||
read=True, write=False)
|
||||
except Exception as e:
|
||||
self.log.error("Unable to create rx_queue (name=%s): %s" % (self._rx_queue_name, e))
|
||||
return False
|
||||
|
||||
try:
|
||||
self._tx_queue = MessageQueue(self._tx_queue_name, flags=O_WRONLY,
|
||||
read=False, write=True)
|
||||
except Exception as e:
|
||||
self.log.error("Unable to create tx_queue: %s (name=%s)" % (self._tx_queue_name, e))
|
||||
self._rx_queue.close()
|
||||
return False
|
||||
|
||||
self.daemon = True
|
||||
self.start()
|
||||
self.log.info("Enabled Interrupt Forwarding for %s" % self.target)
|
||||
return True
|
||||
|
||||
def ignore_interrupt_return(self, interrupt_number):
|
||||
if isinstance(self.target, QemuTarget):
|
||||
self.log.info(
|
||||
"Disable handling of irq return for %d" % interrupt_number)
|
||||
self.target.protocols.monitor.execute_command(
|
||||
'avatar-armv7m-ignore-irq-return',
|
||||
{'num-irq': interrupt_number}
|
||||
)
|
||||
|
||||
def unignore_interrupt_return(self, interrupt_number):
|
||||
if isinstance(self.target, QemuTarget):
|
||||
self.log.info(
|
||||
"Re-enable handling of irq return for %d" % interrupt_number)
|
||||
self.target.protocols.monitor.execute_command(
|
||||
'avatar-armv7m-unignore-irq-return',
|
||||
{'num-irq': interrupt_number}
|
||||
)
|
||||
|
||||
def inject_interrupt(self, interrupt_number, cpu_number=0):
|
||||
if isinstance(self.target, QemuTarget):
|
||||
self.log.info("Injecting interrupt %d" % interrupt_number)
|
||||
self.target.protocols.monitor.execute_command(
|
||||
'avatar-armv7m-inject-irq',
|
||||
{'num-irq': interrupt_number, 'num-cpu': cpu_number}
|
||||
)
|
||||
|
||||
def set_vector_table_base(self, base, cpu_number=0):
|
||||
if isinstance(self.target, QemuTarget):
|
||||
self.log.info("Setting vector table base to 0x%x" % base)
|
||||
self.target.protocols.monitor.execute_command(
|
||||
'avatar-armv7m-set-vector-table-base',
|
||||
{'base': base, 'num-cpu': cpu_number}
|
||||
)
|
||||
|
||||
def send_interrupt_exit_response(self, id, success):
|
||||
response = V7MInterruptNotificationAck(id, success,
|
||||
RINOperation.EXIT.value)
|
||||
|
||||
try:
|
||||
self._tx_queue.send(response)
|
||||
self.log.info("Send RemoteInterruptExitResponse with id %d" % id)
|
||||
return True
|
||||
except Exception as e:
|
||||
self.log.error("Unable to send response: %s" % e)
|
||||
return False
|
||||
|
||||
def send_interrupt_enter_response(self, id, success):
|
||||
response = V7MInterruptNotificationAck(id, success,
|
||||
RINOperation.ENTER.value)
|
||||
try:
|
||||
self._tx_queue.send(response)
|
||||
self.log.info("Send RemoteInterruptEnterResponse with id %d" % id)
|
||||
return True
|
||||
except Exception as e:
|
||||
self.log.error("Unable to send response: %s" % e)
|
||||
return False
|
||||
|
||||
def get_enabled_interrupts(self, iser_num: int = 0):
|
||||
enabled_interrupts = self.target.read_memory(NVIC_ISER0 + iser_num * 4, size=4)
|
||||
return enabled_interrupts
|
||||
|
||||
def set_enabled_interrupts(self, enabled_interrupts_bitfield: int, iser_num: int = 0):
|
||||
self.log.warning(f"Setting enabled interrupts to 0x{enabled_interrupts_bitfield:x}")
|
||||
self.target.write_memory(NVIC_ISER0 + iser_num * 4, size=4, value=enabled_interrupts_bitfield)
|
||||
|
||||
def __del__(self):
|
||||
self.shutdown()
|
||||
|
||||
def shutdown(self):
|
||||
self.stop()
|
||||
if self._rx_queue:
|
||||
try:
|
||||
self._rx_queue.unlink()
|
||||
self._rx_queue.close()
|
||||
self._rx_queue = None
|
||||
except ExistentialError:
|
||||
self.log.warning("Tried to close/unlink non existent rx_queue")
|
||||
if self._tx_queue:
|
||||
try:
|
||||
self._tx_queue.unlink()
|
||||
self._tx_queue.close()
|
||||
self._tx_queue = None
|
||||
except ExistentialError:
|
||||
self.log.warning("Tried to close/unlink non existent tx_queue")
|
||||
@@ -39,7 +39,9 @@ class QMPProtocol(object):
|
||||
if args:
|
||||
command['arguments'] = args
|
||||
command['id'] = self.id
|
||||
self._telnet.write(('%s\r\n' % json.dumps(command)).encode('ascii'))
|
||||
command_str = (json.dumps(command) + "\r\n").encode('ascii')
|
||||
self.log.info("Sending command: %s" % command_str)
|
||||
self._telnet.write(command_str)
|
||||
|
||||
while True:
|
||||
resp = self._telnet.read_until('\r\n'.encode('ascii'))
|
||||
|
||||
@@ -17,6 +17,7 @@ class OpenOCDTarget(Target):
|
||||
openocd_script=None, additional_args=None,
|
||||
tcl_port=6666,
|
||||
gdb_executable=None, gdb_additional_args=None, gdb_port=3333,
|
||||
binary=None,
|
||||
**kwargs
|
||||
):
|
||||
|
||||
@@ -33,6 +34,7 @@ class OpenOCDTarget(Target):
|
||||
self.tcl_port = tcl_port
|
||||
self.gdb_additional_args = gdb_additional_args if gdb_additional_args else []
|
||||
self.gdb_port = gdb_port
|
||||
self.binary = binary
|
||||
|
||||
@watch("TargetInit")
|
||||
def init(self):
|
||||
@@ -49,7 +51,7 @@ class OpenOCDTarget(Target):
|
||||
gdb = GDBProtocol(gdb_executable=self.gdb_executable,
|
||||
arch=self._arch,
|
||||
additional_args=self.gdb_additional_args,
|
||||
avatar=self.avatar, origin=self)
|
||||
avatar=self.avatar, origin=self, binary=self.binary)
|
||||
self.log.debug("Connecting to OpenOCD GDB port")
|
||||
gdb_connected = gdb.remote_connect(port=self.gdb_port)
|
||||
script_has_reset = False
|
||||
|
||||
@@ -4,6 +4,7 @@ from os.path import isfile, exists
|
||||
|
||||
from avatar2.protocols.gdb import GDBProtocol
|
||||
from avatar2.protocols.qmp import QMPProtocol
|
||||
|
||||
try:
|
||||
from avatar2.protocols.remote_memory import RemoteMemoryProtocol
|
||||
except ImportError:
|
||||
@@ -18,23 +19,23 @@ class QemuTarget(Target):
|
||||
""""""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
avatar,
|
||||
executable=None,
|
||||
cpu_model=None,
|
||||
firmware=None,
|
||||
gdb_executable=None,
|
||||
gdb_port=3333,
|
||||
gdb_unix_socket_path=None,
|
||||
additional_args=None,
|
||||
gdb_additional_args=None,
|
||||
gdb_verbose=False,
|
||||
qmp_port=3334,
|
||||
entry_address=0x00,
|
||||
log_items=None,
|
||||
log_file=None,
|
||||
system_clock_scale=None,
|
||||
**kwargs
|
||||
self,
|
||||
avatar,
|
||||
executable=None,
|
||||
cpu_model=None,
|
||||
firmware=None,
|
||||
gdb_executable=None,
|
||||
gdb_port=3333,
|
||||
gdb_unix_socket_path=None,
|
||||
additional_args=None,
|
||||
gdb_additional_args=None,
|
||||
gdb_verbose=False,
|
||||
qmp_port=3334,
|
||||
entry_address=0x00,
|
||||
log_items=None,
|
||||
log_file=None,
|
||||
system_clock_scale=None,
|
||||
**kwargs
|
||||
):
|
||||
super(QemuTarget, self).__init__(avatar, **kwargs)
|
||||
|
||||
@@ -101,14 +102,14 @@ class QemuTarget(Target):
|
||||
qmp = ["-qmp", "tcp:127.0.0.1:%d,server,nowait" % self.qmp_port]
|
||||
|
||||
cmd_line = (
|
||||
executable_name
|
||||
+ machine
|
||||
+ kernel
|
||||
+ gdb_option
|
||||
+ stop_on_startup
|
||||
+ self.additional_args
|
||||
+ nographic
|
||||
+ qmp
|
||||
executable_name
|
||||
+ machine
|
||||
+ kernel
|
||||
+ gdb_option
|
||||
+ stop_on_startup
|
||||
+ self.additional_args
|
||||
+ nographic
|
||||
+ qmp
|
||||
)
|
||||
|
||||
if self.log_items is not None:
|
||||
@@ -264,7 +265,7 @@ class QemuTarget(Target):
|
||||
)
|
||||
|
||||
with open(
|
||||
"%s/%s_out.txt" % (self.avatar.output_directory, self.name), "wb"
|
||||
"%s/%s_out.txt" % (self.avatar.output_directory, self.name), "wb"
|
||||
) as out, open(
|
||||
"%s/%s_err.txt" % (self.avatar.output_directory, self.name), "wb"
|
||||
) as err:
|
||||
|
||||
@@ -156,8 +156,8 @@ class TargetProtocolStore(object):
|
||||
|
||||
def shutdown(self):
|
||||
"""Shutsdown all the associated protocols"""
|
||||
for p in self.protocols:
|
||||
#print("Unloading %s" % str(p))
|
||||
l = list(self.protocols)
|
||||
for p in reversed(l):
|
||||
setattr(self, p, None)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
@@ -356,7 +356,7 @@ class Target(object):
|
||||
if target_range is not None and target_range.forwarded is True and \
|
||||
target_range.forwarded_to != self:
|
||||
return target_range.forwarded_to.write_memory(address, size, value,
|
||||
num_words, raw)
|
||||
num_words, raw, origin=self)
|
||||
|
||||
return self.protocols.memory.write_memory(address, size, value,
|
||||
num_words, raw)
|
||||
@@ -381,12 +381,12 @@ class Target(object):
|
||||
if target_range is not None and target_range.forwarded is True and \
|
||||
target_range.forwarded_to != self:
|
||||
return target_range.forwarded_to.read_memory(address, size,
|
||||
num_words, raw)
|
||||
num_words, raw, origin=self)
|
||||
|
||||
return self.protocols.memory.read_memory(address, size, num_words, raw)
|
||||
|
||||
@watch('TargetRegisterWrite')
|
||||
#@action_valid_decorator_factory(TargetStates.STOPPED, 'registers')
|
||||
@action_valid_decorator_factory(TargetStates.STOPPED, 'registers')
|
||||
def write_register(self, register, value):
|
||||
"""
|
||||
Writing a register to the target
|
||||
@@ -397,7 +397,7 @@ class Target(object):
|
||||
return self.protocols.registers.write_register(register, value)
|
||||
|
||||
@watch('TargetRegisterRead')
|
||||
#@action_valid_decorator_factory(TargetStates.STOPPED, 'registers')
|
||||
@action_valid_decorator_factory(TargetStates.STOPPED, 'registers')
|
||||
def read_register(self, register):
|
||||
"""
|
||||
Reading a register from the target
|
||||
|
||||
+16
-5
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
from threading import Thread
|
||||
from functools import wraps
|
||||
|
||||
@@ -30,7 +31,11 @@ class WatchedTypes(object):
|
||||
'TargetWait',
|
||||
'TargetSetFile',
|
||||
'TargetDownload',
|
||||
'TargetInjectInterrupt'
|
||||
'TargetInjectInterrupt',
|
||||
'TargetInterruptEnter',
|
||||
'TargetInterruptExit',
|
||||
'HWEnter',
|
||||
'HWExit'
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
@@ -73,6 +78,11 @@ def watch(watched_type):
|
||||
elif isinstance(self, Target):
|
||||
avatar = self.avatar
|
||||
cb_kwargs['watched_target'] = self
|
||||
elif getattr(self, 'avatar', None) is not None:
|
||||
avatar = self.avatar
|
||||
cb_kwargs['watched_target'] = self
|
||||
else:
|
||||
logging.getLogger('avatar').warning("Watchmen decorator called on unsupported object %s" % self)
|
||||
|
||||
avatar.watchmen.t(watched_type, BEFORE, *args, **cb_kwargs)
|
||||
ret = func(self, *args, **kwargs)
|
||||
@@ -101,7 +111,7 @@ class AsyncReaction(Thread):
|
||||
class WatchedEvent(object):
|
||||
# noinspection PyUnusedLocal
|
||||
def __init__(self, watch_type, when, callback, is_async,
|
||||
overwrite_return = False, *args, **kwargs):
|
||||
overwrite_return=False, *args, **kwargs):
|
||||
self._callback = callback
|
||||
self.type = watch_type
|
||||
self.when = when
|
||||
@@ -127,7 +137,6 @@ class WatchedEvent(object):
|
||||
ret = self._callback(avatar, *args, **kwargs)
|
||||
if self.overwrite_return:
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
class Watchmen(object):
|
||||
@@ -147,7 +156,8 @@ class Watchmen(object):
|
||||
if self.watched_types._add(type):
|
||||
self._watched_events[type] = []
|
||||
|
||||
def add_watchman(self, watch_type, when=BEFORE, callback=None, is_async=False, overwrite_return=False, *args, **kwargs):
|
||||
def add_watchman(self, watch_type, when=BEFORE, callback=None, is_async=False, overwrite_return=False, *args,
|
||||
**kwargs):
|
||||
|
||||
if watch_type not in self.watched_types:
|
||||
raise Exception("Requested event_type does not exist")
|
||||
@@ -164,7 +174,8 @@ class Watchmen(object):
|
||||
if x.overwrite_return]
|
||||
|
||||
if len(overwriting_cbs) > 1:
|
||||
self._avatar.log.warning("More than one watchman can modify the return value for the event. Watch type: %s" % watch_type)
|
||||
self._avatar.log.warning(
|
||||
"More than one watchman can modify the return value for the event. Watch type: %s" % watch_type)
|
||||
self._watched_events[watch_type].append(w)
|
||||
return w
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ if __name__ == '__main__':
|
||||
# Number each test is repeated
|
||||
n = 2
|
||||
|
||||
avatar = Avatar(arch=ARMV7M, output_directory='/tmp/inception-tests')
|
||||
avatar = Avatar(arch=ARM_CORTEX_M3, output_directory='/tmp/inception-tests')
|
||||
nucleo = avatar.add_target(InceptionTarget, name='nucleo')
|
||||
dum = avatar.add_target(DummyTarget, name='dum')
|
||||
#qemu = avatar.add_target(QemuTarget, gdb_port=1236)
|
||||
|
||||
Reference in New Issue
Block a user