Commit 1e295184 authored by zapb's avatar zapb

Initial commit

parents
import sys
import argparse
import struct
import enum
from openocd import OpenOcd
class Register(enum.IntEnum):
R0 = 0
R1 = 1
R2 = 2
R3 = 3
R4 = 4
R5 = 5
R6 = 6
R7 = 7
R8 = 8
R9 = 9
R10 = 10
R11 = 11
R12 = 12
SP = 13
LR = 14
PC = 15
PSR = 16
# Initial stack pointer (SP) value.
INITIAL_SP = 0x20000200
# Vector Table Offset Register (VTOR).
VTOR_ADDR = 0xe000ed08
# Interrupt Control and State Register (ICSR).
ICSR_ADDR = 0xe000ed04
# System Handler Control and State Register (SHCSR).
SHCSR_ADDR = 0xe000ed24
# NVIC Interrupt Set-Enable Registers (ISER).
NVIC_ISER0_ADDR = 0xe000e100
# NVIC Interrupt Set-Pending Registers (ISPR).
NVIC_ISPR0_ADDR = 0xe000e200
# Debug Exception and Monitor Control Register (DEMCR).
DEMCR_ADDR = 0xe000edfc
# Memory region with eXecute Never (XN) property.
MEM_XN_ADDR = 0xe0000000
SVC_INST_ADDR = 0x20000000
NOP_INST_ADDR = 0x20000002
LDR_INST_ADDR = 0x20000004
UNDEF_INST_ADDR = 0x20000006
# Inaccessible exception numbers.
INACCESSIBLE_EXC_NUMBERS = [0, 1, 7, 8, 9, 10, 13]
def generate_exception(openocd, vt_address, exception_number):
openocd.send('reset halt')
# Relocate vector table.
openocd.write_memory(VTOR_ADDR, [vt_address])
registers = dict()
if exception_number == 2:
# Generate a non-maskable interrupt.
openocd.write_memory(ICSR_ADDR, [1 << 31])
registers[Register.PC] = NOP_INST_ADDR
elif exception_number == 3:
# Generate a HardFault exception due to priority escalation.
registers[Register.PC] = UNDEF_INST_ADDR
elif exception_number == 4:
# Generate a MemFault exception by executing memory with
# eXecute-Never (XN) property.
registers[Register.PC] = MEM_XN_ADDR
# Enable MemFault exceptions.
openocd.write_memory(SHCSR_ADDR, [0x10000])
elif exception_number == 5:
# Generate a BusFault exception by executing a load instruction that
# accesses invalid memory.
registers[Register.PC] = LDR_INST_ADDR
# Enable BusFault exceptions.
openocd.write_memory(SHCSR_ADDR, [0x20000])
registers[Register.R6] = 0xffffff00
elif exception_number == 6:
# Generate an UsageFault by executing an undefined instruction.
registers[Register.PC] = UNDEF_INST_ADDR
# Enable UsageFault exceptions.
openocd.write_memory(SHCSR_ADDR, [0x40000])
elif exception_number == 11:
# Generate a Supervisor Call (SVCall) exception.
registers[Register.PC] = SVC_INST_ADDR
elif exception_number == 12:
# Generate a DebugMonitor exception.
registers[Register.PC] = NOP_INST_ADDR
openocd.write_memory(DEMCR_ADDR, [1 << 17])
elif exception_number == 14:
# Generate a PendSV interrupt.
openocd.write_memory(ICSR_ADDR, [1 << 28])
registers[Register.PC] = NOP_INST_ADDR
elif exception_number == 15:
# Generate a SysTick interrupt.
openocd.write_memory(ICSR_ADDR, [1 << 26])
registers[Register.PC] = NOP_INST_ADDR
elif exception_number >= 16:
# Generate an external interrupt.
ext_interrupt_number = exception_number - 16
register_offset = (ext_interrupt_number // 32) * 4
value = (1 << (ext_interrupt_number % 32))
# Enable and make interrupt pending.
openocd.write_memory(NVIC_ISER0_ADDR + register_offset, [value])
openocd.write_memory(NVIC_ISPR0_ADDR + register_offset, [value])
registers[Register.PC] = NOP_INST_ADDR
else:
sys.exit('Exception number %u not handled' % exception_number)
# Ensure that the processor operates in Thumb mode.
registers[Register.PSR] = 0x01000000
registers[Register.SP] = INITIAL_SP
for reg in registers:
openocd.write_register(reg, registers[reg])
# Perform a single step to generate the exception.
openocd.send('step')
def recover_pc(openocd):
(pc, xpsr) = openocd.read_register_list([Register.PC, Register.PSR])
# Recover LSB of the PC from the EPSR.T bit.
t_bit = (xpsr >> 24) & 0x1
return pc | t_bit
def align(address, base):
return address - (address % base)
def calculate_vtor_exc(address):
# Align vector table always to 32 words.
vtor_address = align(address, 32 * 4)
exception_number = (address - vtor_address) // 4
# Use the wrap-around behaviour to access an unreachable vector table entry.
if (vtor_address % (64 * 4)) != 0 \
and exception_number in INACCESSIBLE_EXC_NUMBERS:
exception_number += 32
return (vtor_address, exception_number)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('address', help='Extraction start address')
parser.add_argument('length', help='Number of words to extract')
parser.add_argument('--value', default='0xffffffff',
help='Value to be used for non-extractable memory words. Use "skip" to ignore them')
parser.add_argument('--binary', action='store_true',
help='Output binary')
parser.add_argument('--host', default='localhost',
help='OpenOCD Tcl interface host')
parser.add_argument('--port', type=int, default=6666,
help='OpenOCD Tcl interface port')
args = parser.parse_args()
start_address = int(args.address, 0)
length = int(args.length, 0)
skip_value = args.value
binary_output = args.binary
if skip_value != 'skip':
skip_value = int(skip_value, 0)
oocd = OpenOcd(args.host, args.port)
try:
oocd.connect()
except Exception as e:
sys.exit('Failed to connect to OpenOCD')
# Disable exception masking by OpenOCD. The target must be halted before
# the masking behaviour can be changed.
oocd.halt()
oocd.send('cortex_m maskisr off')
# Write 'svc #0', 'nop', 'ldr r0, [r1, #0]' and an undefined instruction
# to the SRAM. We use them later to generate exceptions.
oocd.write_memory(SVC_INST_ADDR, [0xdf00], word_length=16)
oocd.write_memory(NOP_INST_ADDR, [0xbf00], word_length=16)
oocd.write_memory(LDR_INST_ADDR, [0x7b75], word_length=16)
oocd.write_memory(UNDEF_INST_ADDR, [0xffff], word_length=16)
for address in range(start_address, start_address + (length * 4), 4):
(vtor_address, exception_number) = calculate_vtor_exc(address)
if address == 0x00000000:
oocd.send('reset halt')
recovered_value = oocd.read_register(Register.SP)
elif address == 0x00000004:
oocd.send('reset halt')
recovered_value = recover_pc(oocd)
elif exception_number in INACCESSIBLE_EXC_NUMBERS:
recovered_value = None
else:
generate_exception(oocd, vtor_address, exception_number)
recovered_value = recover_pc(oocd)
if recovered_value is None and skip_value == 'skip':
continue
if recovered_value is None:
recovered_value = skip_value
if binary_output:
output_value = struct.pack('<I', recovered_value)
sys.stdout.buffer.write(output_value)
else:
output_value = '%08x: %08x\n' % (address, recovered_value)
sys.stdout.write(output_value)
sys.stdout.flush()
import socket
class OpenOcd:
COMMAND_TOKEN = '\x1a'
def __init__(self, host='localhost', port=6666):
self._tcl_rpc_host = host
self._tcl_rpc_port = port
self._buffer_size = 4096
self._tcl_variable = 'python_tcl'
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def __enter__(self):
self.connect()
return self
def __exit__(self, type, value, traceback):
try:
self.exit()
finally:
self.close()
def connect(self):
self._socket.connect((self._tcl_rpc_host, self._tcl_rpc_port))
def close(self):
self._socket.close()
def send(self, cmd):
data = (cmd + OpenOcd.COMMAND_TOKEN).encode('utf-8')
self._socket.send(data)
return self._recv()
def _recv(self):
data = bytes()
while True:
tmp = self._socket.recv(self._buffer_size)
data += tmp
if bytes(OpenOcd.COMMAND_TOKEN, encoding='utf-8') in tmp:
break
data = data.decode('utf-8').strip()
# Strip trailing command token.
data = data[:-1]
return data
def exit(self):
self.send('exit')
def step(self):
self.send('step')
def resume(self, address=None):
if address is None:
self.send('resume')
else:
self.send('resume 0x%x' % address)
def halt(self):
self.send('halt')
def wait_halt(self, timeout=5000):
self.send('wait_halt %u' % timeout)
def write_memory(self, address, data, word_length=32):
array = ' '.join(['%d 0x%x' % (i, v) for (i, v) in enumerate(data)])
# Clear the array before using it.
self.send('array unset %s' % self._tcl_variable)
self.send('array set %s { %s }' % (self._tcl_variable, array))
self.send('array2mem %s 0x%x %s %d' % (self._tcl_variable,
word_length, address, len(data)))
def read_memory(self, address, count, word_length=32):
# Clear the array before using it.
self.send('array unset %s' % self._tcl_variable)
self.send('mem2array %s %d 0x%x %d' % (self._tcl_variable,
word_length, address, count))
raw = self.send('return $%s' % self._tcl_variable).split(' ')
order = [int(raw[2 * i]) for i in range(len(raw) // 2)]
values = [int(raw[2 * i + 1]) for i in range(len(raw) // 2)]
# Sort the array because it may not be sorted by the memory address.
result = [0] * len(values)
for (i, pos) in enumerate(order):
result[pos] = values[i]
return result
def read_register(self, register):
if issubclass(type(register), int):
raw = self.send('reg %u' % register).split(': ')
else:
raw = self.send('reg %s' % register).split(': ')
if len(raw) < 2:
return None
return int(raw[1], 16)
def read_registers(self, registers):
result = dict()
for register in registers:
value = self.read_register(register)
if value is None:
return None
result[register] = value
return result
def read_register_list(self, registers):
register_list = [None] * len(registers)
result = self.read_registers(registers)
# Preserve register order.
for reg in result:
register_list[registers.index(reg)] = result[reg]
return register_list
def write_register(self, register, value):
if issubclass(type(register), int):
self.send('reg %u 0x%x' % (register, value))
else:
self.send('reg %s 0x%x' % (register, value))
def write_registers(self, registers):
for reg in registers:
self.write_register(reg, registers[reg])
def set_breakpoint(self, address, length=2, hardware=True):
if hardware:
self.send('bp 0x%x %u hw' % (address, length))
else:
self.send('bp 0x%x %u' % (address, length))
def remove_breakpoint(self):
self.send('rbp 0x%x' % address)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment