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:
Florian Albrecht
2023-12-03 21:20:15 +01:00
committed by GitHub
co-authored by Florian Albrecht rawsample
parent a64a8b6775
commit e29b338b8b
24 changed files with 2034 additions and 203 deletions
+2 -3
View File
@@ -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
View File
@@ -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)
+9 -9
View File
@@ -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
+48
View File
@@ -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
+10 -7
View File
@@ -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)
+101
View File
@@ -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)
+103
View File
@@ -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)
+208
View File
@@ -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)
+88
View File
@@ -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)
+19 -4
View File
@@ -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)
+198
View File
@@ -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()
+361
View File
@@ -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
View File
@@ -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):
+73 -68
View File
@@ -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")
+101
View File
@@ -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()
+261
View File
@@ -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")
+3 -1
View File
@@ -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'))
+3 -1
View File
@@ -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
+27 -26
View File
@@ -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:
+6 -6
View File
@@ -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
View File
@@ -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)