802 lines
31 KiB
Python
802 lines
31 KiB
Python
# pyright: reportUnknownMemberType=false, reportOptionalMemberAccess=false, reportPrivateUsage=false, reportUnknownVariableType=false, reportUninitializedInstanceVariable=false
|
|
|
|
from ctypes import *
|
|
from pylink import JLink
|
|
import crcmod
|
|
from enum import IntEnum
|
|
import struct
|
|
import random
|
|
import traceback
|
|
import sys
|
|
import time
|
|
|
|
from logging import Logger, getLogger, basicConfig
|
|
from dataclasses import dataclass
|
|
from typing_extensions import override
|
|
from construct import *
|
|
from construct_typed import DataclassMixin, DataclassStruct, csfield
|
|
from typing import IO, Any, Literal, Self
|
|
from tap import Tap
|
|
|
|
class _EnhancedDataclassMixin(DataclassMixin):
|
|
@classmethod
|
|
def format(cls):
|
|
return DataclassStruct(cls)
|
|
|
|
@classmethod
|
|
def build(cls, obj: Self, **kw: Any): # pyright: ignore[reportExplicitAny, reportAny]
|
|
return cls.format().build(obj, **kw)
|
|
|
|
@classmethod
|
|
def parse(cls, data: bytes|bytearray, **kw: Any):# pyright: ignore[reportExplicitAny, reportAny]
|
|
return cls.format().parse(data, **kw)
|
|
|
|
@classmethod
|
|
def parse_file(cls, file: str, **kw: Any):# pyright: ignore[reportExplicitAny, reportAny]
|
|
return cls.format().parse_file(file, **kw)
|
|
|
|
@classmethod
|
|
def parse_stream(cls, stream: IO[bytes], **kw: Any):# pyright: ignore[reportExplicitAny, reportAny]
|
|
return cls.format().parse_stream(stream, **kw)
|
|
|
|
class ArmRegs(IntEnum):
|
|
ARM_REG_R0 = 0
|
|
ARM_REG_R1 = 1
|
|
ARM_REG_R2 = 2
|
|
ARM_REG_R3 = 3
|
|
ARM_REG_R4 = 4
|
|
ARM_REG_R5 = 5
|
|
ARM_REG_R6 = 6
|
|
ARM_REG_R7 = 7
|
|
ARM_REG_CPSR = 8
|
|
ARM_REG_R15 = 9
|
|
ARM_REG_R8_USR = 10
|
|
ARM_REG_R9_USR = 11
|
|
ARM_REG_R10_USR = 12
|
|
ARM_REG_R11_USR = 13
|
|
ARM_REG_R12_USR = 14
|
|
ARM_REG_R13_USR = 15
|
|
ARM_REG_R14_USR = 16
|
|
ARM_REG_SPSR_FIQ = 17
|
|
ARM_REG_R8_FIQ = 18
|
|
ARM_REG_R9_FIQ = 19
|
|
ARM_REG_R10_FIQ = 20
|
|
ARM_REG_R11_FIQ = 21
|
|
ARM_REG_R12_FIQ = 22
|
|
ARM_REG_R13_FIQ = 23
|
|
ARM_REG_R14_FIQ = 24
|
|
ARM_REG_SPSR_SVC = 25
|
|
ARM_REG_R13_SVC = 26
|
|
ARM_REG_R14_SVC = 27
|
|
ARM_REG_SPSR_ABT = 28
|
|
ARM_REG_R13_ABT = 29
|
|
ARM_REG_R14_ABT = 30
|
|
ARM_REG_SPSR_IRQ = 31
|
|
ARM_REG_R13_IRQ = 32
|
|
ARM_REG_R14_IRQ = 33
|
|
ARM_REG_SPSR_UND = 34
|
|
ARM_REG_R13_UND = 35
|
|
ARM_REG_R14_UND = 36
|
|
ARM_REG_FPSID = 37
|
|
ARM_REG_FPSCR = 38
|
|
ARM_REG_FPEXC = 39
|
|
ARM_REG_FPS0 = 40
|
|
ARM_REG_FPS1 = 41
|
|
ARM_REG_FPS2 = 42
|
|
ARM_REG_FPS3 = 43
|
|
ARM_REG_FPS4 = 44
|
|
ARM_REG_FPS5 = 45
|
|
ARM_REG_FPS6 = 46
|
|
ARM_REG_FPS7 = 47
|
|
ARM_REG_FPS8 = 48
|
|
ARM_REG_FPS9 = 49
|
|
ARM_REG_FPS10 = 50
|
|
ARM_REG_FPS11 = 51
|
|
ARM_REG_FPS12 = 52
|
|
ARM_REG_FPS13 = 53
|
|
ARM_REG_FPS14 = 54
|
|
ARM_REG_FPS15 = 55
|
|
ARM_REG_FPS16 = 56
|
|
ARM_REG_FPS17 = 57
|
|
ARM_REG_FPS18 = 58
|
|
ARM_REG_FPS19 = 59
|
|
ARM_REG_FPS20 = 60
|
|
ARM_REG_FPS21 = 61
|
|
ARM_REG_FPS22 = 62
|
|
ARM_REG_FPS23 = 63
|
|
ARM_REG_FPS24 = 64
|
|
ARM_REG_FPS25 = 65
|
|
ARM_REG_FPS26 = 66
|
|
ARM_REG_FPS27 = 67
|
|
ARM_REG_FPS28 = 68
|
|
ARM_REG_FPS29 = 69
|
|
ARM_REG_FPS30 = 70
|
|
ARM_REG_FPS31 = 71
|
|
ARM_REG_R8 = 72
|
|
ARM_REG_R9 = 73
|
|
ARM_REG_R10 = 74
|
|
ARM_REG_R11 = 75
|
|
ARM_REG_R12 = 76
|
|
ARM_REG_R13 = 77
|
|
ARM_REG_R14 = 78
|
|
ARM_REG_SPSR = 79
|
|
|
|
class ResetType(IntEnum):
|
|
ARM_RESET_TYPE_NORMAL = 0
|
|
ARM_RESET_TYPE_BP0 = 1
|
|
ARM_RESET_TYPE_ADI = 2
|
|
ARM_RESET_TYPE_NO_RESET = 3
|
|
ARM_RESET_TYPE_HALT_WP = 4
|
|
ARM_RESET_TYPE_HALT_DBGRQ = 5
|
|
ARM_RESET_TYPE_SOFT = 6
|
|
ARM_RESET_TYPE_HALT_DURING = 7
|
|
ARM_RESET_TYPE_SAM7 = 8
|
|
ARM_RESET_TYPE_LPC = 9
|
|
RESET_TYPE_CORE = 100
|
|
RESET_TYPE_RESET_PIN = 101
|
|
|
|
@dataclass
|
|
class NANDDeviceInfo:
|
|
page_size: int
|
|
flash_size: int
|
|
block_size: int
|
|
bits: int
|
|
|
|
_NAND_DEV_IDS = {
|
|
0x6e: NANDDeviceInfo(0x100, 0x100000, 0x800, 8),
|
|
0x64: NANDDeviceInfo(0x100, 0x200000, 0x800, 8),
|
|
0xe8: NANDDeviceInfo(0x100, 0x100000, 0x800, 8),
|
|
0xec: NANDDeviceInfo(0x100, 0x100000, 0x800, 8),
|
|
0xea: NANDDeviceInfo(0x100, 0x200000, 0x800, 8),
|
|
0x6b: NANDDeviceInfo(0x200, 0x400000, 0x2000, 8),
|
|
0xe3: NANDDeviceInfo(0x200, 0x400000, 0x2000, 8),
|
|
0xe5: NANDDeviceInfo(0x200, 0x400000, 0x2000, 8),
|
|
0xd6: NANDDeviceInfo(0x200, 0x800000, 0x2000, 8),
|
|
0x39: NANDDeviceInfo(0x200, 0x8000000, 0x4000, 8),
|
|
0xe6: NANDDeviceInfo(0x200, 0x800000, 0x2000, 8),
|
|
0x49: NANDDeviceInfo(0x200, 0x8000000, 0x4000, 16),
|
|
0x59: NANDDeviceInfo(0x200, 0x8000000, 0x4000, 16),
|
|
0x33: NANDDeviceInfo(0x200, 0x1000000, 0x4000, 8),
|
|
0x73: NANDDeviceInfo(0x200, 0x1000000, 0x4000, 8),
|
|
0x43: NANDDeviceInfo(0x200, 0x1000000, 0x4000, 16),
|
|
0x53: NANDDeviceInfo(0x200, 0x1000000, 0x4000, 16),
|
|
0x35: NANDDeviceInfo(0x200, 0x2000000, 0x4000, 8),
|
|
0x75: NANDDeviceInfo(0x200, 0x2000000, 0x4000, 8),
|
|
0x45: NANDDeviceInfo(0x200, 0x2000000, 0x4000, 16),
|
|
0x55: NANDDeviceInfo(0x200, 0x2000000, 0x4000, 16),
|
|
0x36: NANDDeviceInfo(0x200, 0x4000000, 0x4000, 8),
|
|
0x76: NANDDeviceInfo(0x200, 0x4000000, 0x4000, 8),
|
|
0x46: NANDDeviceInfo(0x200, 0x4000000, 0x4000, 16),
|
|
0x56: NANDDeviceInfo(0x200, 0x4000000, 0x4000, 16),
|
|
0x78: NANDDeviceInfo(0x200, 0x8000000, 0x4000, 8),
|
|
0x79: NANDDeviceInfo(0x200, 0x8000000, 0x4000, 8),
|
|
0x72: NANDDeviceInfo(0x200, 0x8000000, 0x4000, 16),
|
|
0x74: NANDDeviceInfo(0x200, 0x8000000, 0x4000, 16),
|
|
0x71: NANDDeviceInfo(0x200, 0x10000000, 0x4000, 8),
|
|
0xa2: NANDDeviceInfo(0x00, 0x4000000, 0x00, 8),
|
|
0xb2: NANDDeviceInfo(0x00, 0x4000000, 0x00, 16),
|
|
0xc2: NANDDeviceInfo(0x00, 0x4000000, 0x00, 16),
|
|
0xf2: NANDDeviceInfo(0x00, 0x4000000, 0x00, 8),
|
|
0xa1: NANDDeviceInfo(0x00, 0x8000000, 0x00, 8),
|
|
0xb1: NANDDeviceInfo(0x00, 0x8000000, 0x00, 16),
|
|
0xc1: NANDDeviceInfo(0x00, 0x8000000, 0x00, 16),
|
|
0xf1: NANDDeviceInfo(0x00, 0x8000000, 0x00, 8),
|
|
0xaa: NANDDeviceInfo(0x00, 0x10000000, 0x00, 8),
|
|
0xba: NANDDeviceInfo(0x00, 0x10000000, 0x00, 16),
|
|
0xca: NANDDeviceInfo(0x00, 0x10000000, 0x00, 16),
|
|
0xda: NANDDeviceInfo(0x00, 0x10000000, 0x00, 8),
|
|
0xac: NANDDeviceInfo(0x00, 0x20000000, 0x00, 8),
|
|
0xbc: NANDDeviceInfo(0x00, 0x20000000, 0x00, 16),
|
|
0xcc: NANDDeviceInfo(0x00, 0x20000000, 0x00, 16),
|
|
0xdc: NANDDeviceInfo(0x00, 0x20000000, 0x00, 8),
|
|
0xa3: NANDDeviceInfo(0x00, 0x40000000, 0x00, 8),
|
|
0xb3: NANDDeviceInfo(0x00, 0x40000000, 0x00, 16),
|
|
0xc3: NANDDeviceInfo(0x00, 0x40000000, 0x00, 16),
|
|
0xd3: NANDDeviceInfo(0x00, 0x40000000, 0x00, 8),
|
|
0xa5: NANDDeviceInfo(0x00, 0x80000000, 0x00, 8),
|
|
0xb5: NANDDeviceInfo(0x00, 0x80000000, 0x00, 16),
|
|
0xc5: NANDDeviceInfo(0x00, 0x80000000, 0x00, 16),
|
|
0xd5: NANDDeviceInfo(0x00, 0x80000000, 0x00, 8)
|
|
}
|
|
|
|
@dataclass
|
|
class DCCLoaderInfoPacket(_EnhancedDataclassMixin):
|
|
@dataclass
|
|
class Error(_EnhancedDataclassMixin):
|
|
error_code: int = csfield(Hex(Int32ul))
|
|
|
|
@dataclass
|
|
class BufferInfo(_EnhancedDataclassMixin):
|
|
read_buffer_size: int = csfield(Int32ul)
|
|
write_buffer_size: int = csfield(IfThenElse(this._.dev_type & 0x100, Int32ul, Computed(this.read_buffer_size)))
|
|
|
|
@dataclass
|
|
class MemoryInfo(_EnhancedDataclassMixin):
|
|
@dataclass
|
|
class ExtendParams(_EnhancedDataclassMixin):
|
|
emmc_no_sectors: int | None = csfield(If(this._._dev_type & 0x10, Int32ul))
|
|
name: str | None = csfield(If(this._._dev_type & 0x80, Aligned(4, PascalString(Int8ul, "ascii"))))
|
|
extend_flags: int = csfield(Hex(Rebuild(Int8ul, this._._dev_type & 0xff)))
|
|
page_size_log2: int = csfield(Hex(Int8ul))
|
|
block_size_log2: int = csfield(Hex(Int8ul))
|
|
size_mb_log2: int = csfield(Hex(Int8ul))
|
|
|
|
_dev_type: int = csfield(Computed(this._.dev_type)) # pyright: ignore[reportAny]
|
|
manufacturer_id: int = csfield(Hex(Int16ul))
|
|
device_id: int = csfield(Hex(Int16ul))
|
|
extend_params: ExtendParams | None = csfield(If((this._dev_type & 3) == 3, ExtendParams.format()))
|
|
have_oob: bool = csfield(Computed(lambda x: (x._dev_type & 0xb) in [0x0, 0x3])) # pyright: ignore[reportAny]
|
|
|
|
dev_magic: bytes = csfield(Const(b"OK"))
|
|
dev_type: int = csfield(Hex(Int16ul))
|
|
data: Error | BufferInfo | MemoryInfo = csfield(IfThenElse(this.dev_type == 0xffff, Error.format(), IfThenElse(this.dev_type & 4, BufferInfo.format(), MemoryInfo.format())))
|
|
|
|
@dataclass
|
|
class DCCMemDevice:
|
|
manufacturer_id: int
|
|
device_id: int
|
|
page_size: int # 1 << n
|
|
block_size: int # 1 << n
|
|
flash_size: int # (1 << n) << 20
|
|
have_oob: bool
|
|
|
|
class DumpDCC():
|
|
def __init__(self, jlink: JLink, bp_loader: bool=False, logger: Logger | None=None):
|
|
self.jlink: JLink = jlink # JLink context
|
|
self.__is_bp_loader: bool = bp_loader # Breakpoint loader
|
|
|
|
# Variables
|
|
self.__is_loaded: bool = False
|
|
self.__start_offset: int = 0
|
|
self.flash_devices: list[DCCMemDevice | None] = []
|
|
self.__logger: Logger | None = logger
|
|
self.__read_buf_size: int = 0
|
|
self.__write_buf_size: int = 0
|
|
self.__bp_loader_breakpoint_pc: int = 0
|
|
self.__bp_loader_read_data: list[int] = []
|
|
self.__bp_loader_data_fetched: bool = False
|
|
|
|
def load(self, binary: bytes|bytearray|str, start_offset: int):
|
|
if isinstance(binary, str):
|
|
binary = open(binary, "rb").read()
|
|
|
|
assert (start_offset % 4) == 0, "Invalid destination offset"
|
|
|
|
# 00 - Reset variables
|
|
self.__is_loaded = False
|
|
self.__start_offset = start_offset
|
|
self.flash_devices.clear()
|
|
self.__read_buf_size = 0
|
|
self.__write_buf_size = 0
|
|
self.__bp_loader_breakpoint_pc = 0
|
|
self.jlink.breakpoint_clear_all()
|
|
self.__bp_loader_read_data.clear()
|
|
self.__bp_loader_data_fetched = False
|
|
|
|
# 01 - Ram self test
|
|
if self.__logger is not None:
|
|
self.__logger.debug("Running RAM self test for 0x%08x", start_offset)
|
|
|
|
ram_selftest_data = random.randbytes(0x800)
|
|
self.jlink.memory_write8(start_offset, [x for x in ram_selftest_data])
|
|
|
|
ram_selftest_read = bytes(self.jlink.memory_read8(start_offset, 0x800))
|
|
assert ram_selftest_read == ram_selftest_data, "Upload test failed"
|
|
|
|
# 02 - Upload and restart
|
|
if self.__logger is not None:
|
|
self.__logger.debug("Uploading loader to 0x%08x", start_offset)
|
|
|
|
self.jlink.memory_write8(start_offset, [x for x in binary])
|
|
|
|
if self.__is_bp_loader:
|
|
#bp_loader_breakpoint = int.from_bytes(binary[0x34:0x38], "little")
|
|
bp_loader_breakpoint: int = self.jlink.memory_read32(start_offset + 0x34, 1)[0]
|
|
if self.__logger is not None:
|
|
self.__logger.debug("Setting loader breakpoint at 0x%08x", bp_loader_breakpoint)
|
|
|
|
self.__bp_loader_breakpoint_pc = bp_loader_breakpoint
|
|
self.jlink.hardware_breakpoint_set(bp_loader_breakpoint, arm=True)
|
|
|
|
self.jlink.register_write(ArmRegs.ARM_REG_CPSR, 0xd3) # Disable all interrupts and go to supervisor mode
|
|
self.jlink.register_write(ArmRegs.ARM_REG_R15, start_offset)
|
|
|
|
self.jlink.restart()
|
|
|
|
# 03 - DCC Load start
|
|
if self.__logger is not None:
|
|
self.__logger.debug("Began flash device probing")
|
|
|
|
dcc_init_data = GreedyRange(DCCLoaderInfoPacket.format()).parse(self.__dcc_read_data())
|
|
|
|
for flash_n, f in enumerate(dcc_init_data, start=1):
|
|
if isinstance(f.data, DCCLoaderInfoPacket.Error):
|
|
if self.__logger is not None:
|
|
self.__logger.warning("Probe failed for dev id %d (code 0x%02x)", flash_n, f.data.error_code)
|
|
|
|
self.flash_devices.append(None)
|
|
|
|
elif isinstance(f.data, DCCLoaderInfoPacket.BufferInfo):
|
|
if self.__logger is not None:
|
|
self.__logger.debug("read buffer size: 0x%08x, write buffer size: 0x%08x", f.data.read_buffer_size, f.data.write_buffer_size)
|
|
|
|
self.__read_buf_size = f.data.read_buffer_size
|
|
self.__write_buf_size = f.data.write_buffer_size
|
|
|
|
else:
|
|
if f.data.extend_params is None:
|
|
device_id = f.data.device_id & 0xff
|
|
is_nor = (f.dev_type & 0x3) == 1
|
|
|
|
block_size = (1 << (f.dev_type >> 8)) if is_nor else _NAND_DEV_IDS[device_id].block_size
|
|
page_size = 0x200 if is_nor else _NAND_DEV_IDS[device_id].page_size
|
|
|
|
if not page_size:
|
|
device_id_high = f.data.device_id >> 8
|
|
page_size_bits = device_id_high & 3
|
|
block_size_bits = (device_id_high >> 4) & 3
|
|
|
|
page_size = 1 << (10 + page_size_bits)
|
|
block_size = (1 << (6 + block_size_bits)) << 10
|
|
|
|
self.flash_devices.append(DCCMemDevice(f.data.manufacturer_id, f.data.device_id, page_size, block_size, _NAND_DEV_IDS[device_id].flash_size, f.data.have_oob))
|
|
|
|
else:
|
|
self.flash_devices.append(DCCMemDevice(f.data.manufacturer_id, f.data.device_id, 1 << f.data.extend_params.page_size_log2, 1 << f.data.extend_params.block_size_log2, (1 << f.data.extend_params.size_mb_log2) << 20, f.data.have_oob))
|
|
|
|
if self.__logger is not None:
|
|
self.__logger.debug("flash devices:")
|
|
for id, fd in enumerate(self.flash_devices, start=1):
|
|
self.__logger.debug("id %d: %s", id, fd)
|
|
|
|
self.__is_loaded = True
|
|
|
|
def __dcc_do_read(self, length: int) -> list[int]:
|
|
if self.__is_bp_loader:
|
|
if not self.__bp_loader_data_fetched:
|
|
# 01 - Watit for breakpoint
|
|
while True:
|
|
time.sleep(0.01)
|
|
if self.jlink.halted():
|
|
break
|
|
|
|
# 02 - Check breakpoint PC
|
|
pc = self.jlink.register_read(ArmRegs.ARM_REG_R15)
|
|
assert pc == self.__bp_loader_breakpoint_pc, "Something is wrong with the breakpoint"
|
|
|
|
# 03 - Read breakpoint data
|
|
bp_loader_ptr: int = self.jlink.memory_read32(self.__start_offset + 0x30, 1)[0]
|
|
bp_loader_size: int = self.jlink.memory_read32(self.__start_offset + 0x2c, 1)[0]
|
|
|
|
self.__bp_loader_read_data = list[int](self.jlink.memory_read32(bp_loader_ptr, bp_loader_size >> 2)) # pyright: ignore[reportUnknownArgumentType]
|
|
assert len(self.__bp_loader_read_data) == (bp_loader_size >> 2), f"dcc read error ({len(self.__bp_loader_read_data)} != {bp_loader_size >> 2})"
|
|
|
|
# 04 - Mark as fetched
|
|
self.__bp_loader_data_fetched = True
|
|
|
|
if self.__logger is not None:
|
|
self.__logger.debug("bp loader read buffer: %s, read length: %d", self.__bp_loader_read_data[:16], length)
|
|
|
|
temp = self.__bp_loader_read_data[:length]
|
|
self.__bp_loader_read_data = self.__bp_loader_read_data[length:]
|
|
|
|
return temp
|
|
|
|
else:
|
|
dcc_read_data = (c_uint32 * length)()
|
|
dcc_read_num: int = self.jlink._dll.JLINKARM_ReadDCC(dcc_read_data, length, 2500)
|
|
|
|
assert dcc_read_num == length, f"dcc read error ({dcc_read_num} != {length})"
|
|
|
|
return list[int](dcc_read_data)
|
|
|
|
def __dcc_do_write(self, values: list[int]):
|
|
if self.__is_bp_loader:
|
|
self.__bp_loader_read_data.clear()
|
|
self.__bp_loader_data_fetched = False
|
|
|
|
bp_loader_ptr: int = self.jlink.memory_read32(self.__start_offset + 0x30, 1)[0]
|
|
|
|
write_no_values = self.jlink.memory_write32(bp_loader_ptr, values)
|
|
write_len = self.jlink.memory_write32(self.__start_offset + 0x2c, [len(values) << 2])
|
|
|
|
assert write_no_values == len(values), f"dcc write error ({write_no_values} != {len(values)})"
|
|
assert write_len == 1, f"dcc write error (len) ({write_len} != 1)"
|
|
|
|
else:
|
|
dcc_write_data = (c_uint32 * len(values))(*values)
|
|
dcc_write_num: int = self.jlink._dll.JLINKARM_WriteDCC(dcc_write_data, len(values), 2500)
|
|
|
|
assert dcc_write_num == len(values), f"dcc write error ({dcc_write_num} != {len(values)})"
|
|
|
|
def __dcc_read_data(self) -> bytes:
|
|
# 01 - Get Length
|
|
dcc_data_len: int = self.__dcc_do_read(1)[0]
|
|
|
|
# 02 - Read Data
|
|
len_data_plus_crc: int = dcc_data_len + 1
|
|
dcc_read_data: list[int] = self.__dcc_do_read(len_data_plus_crc)
|
|
|
|
dcc_data: list[int] = dcc_read_data[:dcc_data_len]
|
|
dcc_crc32_expect: int = dcc_read_data[dcc_data_len]
|
|
dcc_data_bytes: bytes = b"".join(x.to_bytes(4, "little") for x in dcc_data)
|
|
|
|
# 03 - Verify data
|
|
dcc_crc32_hash = crcmod.mkCrcFun(0x104c11db7, 0xffffffff, False, 0)
|
|
|
|
dcc_crc_computed = dcc_crc32_hash(dcc_data_bytes)
|
|
assert dcc_crc_computed == dcc_crc32_expect, f"dcc data checksum mismatch (0x{dcc_crc_computed:08x} != 0x{dcc_crc32_expect:08x})"
|
|
|
|
return dcc_data_bytes
|
|
|
|
@staticmethod
|
|
def __decompress(data: bytes) -> bytes:
|
|
o = 0
|
|
buf = bytearray()
|
|
|
|
while o < len(data):
|
|
c = int.from_bytes(data[o:o + 2], "little")
|
|
|
|
if c & 0x8000:
|
|
c &= 0x7fff
|
|
#print("rle", hex(c[0]))
|
|
buf += data[o + 2:o + 3] * c
|
|
o += 3
|
|
|
|
else:
|
|
#print("raw", hex(c))
|
|
buf += data[o + 2:(o + 2) + c]
|
|
o += 2 + c
|
|
|
|
return bytes(buf)
|
|
|
|
def read_flash(self, offset: int, length: int, flash_id: int=1) -> tuple[bytes, bytes]:
|
|
# 01 - Check parameters
|
|
assert self.__is_loaded, "Please load the DCC loader code first"
|
|
|
|
if flash_id == 0: # MCU
|
|
assert (offset % 4) == 0, "read offset must be dword aligned"
|
|
assert (length % 4) == 0, "read size must be dword aligned"
|
|
|
|
else: # Flash
|
|
read_flash_id = flash_id - 1
|
|
assert read_flash_id < len(self.flash_devices) and self.flash_devices[read_flash_id] is not None, "Invalid flash ID"
|
|
assert (offset % self.flash_devices[read_flash_id].page_size) == 0, "read offset must be page aligned"
|
|
assert (length % self.flash_devices[read_flash_id].page_size) == 0, "read size must be page aligned"
|
|
|
|
# 02 - Initialize variables
|
|
outp_data = bytearray()
|
|
outp_oob = bytearray()
|
|
|
|
read_n = 0
|
|
|
|
# 03 - Main read
|
|
while length > 0:
|
|
read_len = min(self.__read_buf_size, length)
|
|
|
|
# 04 - Send read command
|
|
if self.__logger is not None:
|
|
self.__logger.debug("sending read command: id %d, offset: 0x%08x, size: 0x%08x", flash_id, offset + read_n, read_len)
|
|
|
|
self.__dcc_do_write([0x52 | (flash_id << 8), offset + read_n, read_len])
|
|
data = self.__dcc_read_data()
|
|
|
|
# 05 - Handle response
|
|
if self.__logger is not None:
|
|
self.__logger.debug("got read response: %s", data[:16])
|
|
|
|
match data[0]:
|
|
case 0xff:
|
|
raise Exception(f"read flash error, code: 0x{data[1]:02x}")
|
|
|
|
case 0x00:
|
|
outp_data += data[4:4+read_len]
|
|
outp_oob += data[4+read_len:]
|
|
|
|
case 0x01:
|
|
rle_read_size = int.from_bytes(data[4:8], "little") - 4
|
|
rle_decomp = self.__decompress(data[8:8+rle_read_size])
|
|
|
|
outp_data += rle_decomp[:read_len]
|
|
outp_oob += rle_decomp[read_len:]
|
|
|
|
case _:
|
|
raise Exception(f"not implemented: 0x{data[0]:02x}")
|
|
|
|
read_n += read_len
|
|
length -= read_len
|
|
|
|
return bytes(outp_data), bytes(outp_oob)
|
|
|
|
def runHAS(j: JLink, has: str):
|
|
myHAS = open(has, "rb")
|
|
while True:
|
|
fp = myHAS.read(4)
|
|
if len(fp) < 4: break
|
|
|
|
cmd = int.from_bytes(fp, "little")
|
|
cmd ^= 0xffffffff
|
|
|
|
match cmd:
|
|
case 0x00: # 0xff
|
|
offset, value = struct.unpack("<LL", myHAS.read(8))
|
|
print(f"{cmd} (WRITE): {hex(offset)} = {hex(value)}")
|
|
j.memory_write32(offset, [value])
|
|
|
|
case 0x01: # 0xfe
|
|
offset = int.from_bytes(myHAS.read(4), "little")
|
|
print(f"{cmd} (READ): {hex(offset)}")
|
|
|
|
case 0x02: # 0xfd
|
|
offset, value = struct.unpack("<LL", myHAS.read(8))
|
|
print(f"{cmd} (WRITE AND): {hex(offset)} |= {hex(value)}")
|
|
|
|
case 0x03: # 0xfc
|
|
offset, mask, value = struct.unpack("<LLL", myHAS.read(12))
|
|
print(f"{cmd} (WRITE AND OR): {hex(offset)} |= ({hex(value)} & {hex(mask)})")
|
|
|
|
case 0x05: # 0xfa
|
|
unknown = int.from_bytes(myHAS.read(4), "little")
|
|
print(f"{cmd} (UNKNOWN): {hex(unknown)}")
|
|
|
|
case 0x06: # 0xf9
|
|
offset, mask, value = struct.unpack("<LLL", myHAS.read(0xc))
|
|
print(f"{cmd} (WRITE OR AND): {hex(offset)} = (v({hex(offset)}) & {hex(mask)}) | {hex(value)}")
|
|
|
|
case 0x07: # 0xf8
|
|
offset, value = struct.unpack("<LL", myHAS.read(8))
|
|
print(f"{cmd} (WRITE16): {hex(offset)} = {hex(value)}")
|
|
j.memory_write16(offset, [value])
|
|
|
|
case 0x08: # 0xf7
|
|
offset, value = struct.unpack("<LL", myHAS.read(8))
|
|
print(f"{cmd} (WRITE8): {hex(offset)} = {hex(value)}")
|
|
j.memory_write8(offset, [value])
|
|
|
|
case 0x09: # 0xf6
|
|
offset = int.from_bytes(myHAS.read(4), "little")
|
|
print(f"{cmd} (READ16): {hex(offset)}")
|
|
|
|
case 0x0a: # 0xf5
|
|
offset = int.from_bytes(myHAS.read(4), "little")
|
|
print(f"{cmd} (READ8): {hex(offset)}")
|
|
|
|
case 0x0b: # 0xf4
|
|
cr_m, cr_n, cp_no, op, value = struct.unpack("<BBBBL", myHAS.read(8))
|
|
print(F"{cmd} (COPROC) {cp_no}, {cr_n}, {cr_m}, {op}, {hex(value)}")
|
|
if cp_no == 15:
|
|
j.cp15_register_write(cr_n, op, cr_m, 0, value)
|
|
|
|
case 0x0c: # 0xf3
|
|
cr_m, cr_n, cp_no, op, value = struct.unpack("<BBBBL", myHAS.read(8))
|
|
print(F"{cmd} (COPROC OR) {cp_no}, {cr_n}, {cr_m}, {op}, &= {hex(value)}")
|
|
|
|
case 0x0d: # 0xf2
|
|
cr_m, cr_n, cp_no, op, value = struct.unpack("<BBBBL", myHAS.read(8))
|
|
print(F"{cmd} (COPROC AND) {cp_no}, {cr_n}, {cr_m}, {op}, |= {hex(value)}")
|
|
|
|
case 0x0f: # 0xf0
|
|
val = int.from_bytes(myHAS.read(4), "little")
|
|
print(f"{cmd} (SLEEP): {val}")
|
|
|
|
case 0x10: # 0xef
|
|
offset, mask, expected, delay, branch = struct.unpack("<LLLLL", myHAS.read(0x14))
|
|
print(f"{cmd} (POLL_TIMEOUT): (v({hex(offset)}) & {hex(mask)}) == {hex(expected)}, max: {delay}ms, SKIP {branch} INSTRUCTION if TIMEOUT")
|
|
|
|
case 0x11: # 0xee
|
|
offset, mask, expected, delay, branch = struct.unpack("<LLLLL", myHAS.read(0x14))
|
|
print(f"{cmd} (POLL): (v({hex(offset)}) & {hex(mask)}) == {expected}, max: {delay}ms, SKIP {branch} INSTRUCTION if TRUE")
|
|
|
|
case 0x12: # 0xed
|
|
code = int.from_bytes(myHAS.read(4), "little")
|
|
print(f"{cmd} (SKIP) {code} instructions")
|
|
|
|
case 0x18: # 0xe7
|
|
offset, mask = struct.unpack("<LL", myHAS.read(0x8))
|
|
print(f"{cmd} (WRITE OR) {hex(offset)} &= {hex(mask)}")
|
|
|
|
case 0x25: # 0xda
|
|
mask, cond, branch = struct.unpack("<LLL", myHAS.read(0xc))
|
|
print(f"{cmd} (COND): (a & {hex(mask)}) == {hex(cond)}, SKIP {branch} INSTRUCTION if TRUE")
|
|
|
|
case 0x26: # 0xd9
|
|
mask, cond, branch = struct.unpack("<LLL", myHAS.read(0xc))
|
|
print(f"{cmd} (COND): (a & {hex(mask)}) == {hex(cond)}, SKIP {branch} INSTRUCTION if FALSE")
|
|
|
|
case 0x27: # 0xd8
|
|
offset = int.from_bytes(myHAS.read(4), "little")
|
|
print(f"{cmd}: (READ AND PRINT) {hex(offset)}")
|
|
|
|
case 0x28: # 0xd7
|
|
val = int.from_bytes(myHAS.read(4), "little")
|
|
print(f"{cmd}: (PRINT) {hex(val)}")
|
|
|
|
case 0x29: # 0xd6
|
|
offset, value = struct.unpack("<LL", myHAS.read(8))
|
|
print(f"{cmd} (WRITE OR): {hex(offset)} &= ~{hex(value)}")
|
|
|
|
case 0x2a: # 0xd5
|
|
offset, mask, value = struct.unpack("<LLL", myHAS.read(0xc))
|
|
print(f"{cmd} (WRITE OR AND): {hex(offset)} &= ({hex(value)} & {hex(mask)})")
|
|
|
|
case 0xf9: # 0x06
|
|
val = int.from_bytes(myHAS.read(4), "little")
|
|
print(f"{cmd}: (SET) {val}")
|
|
|
|
case 0xfe: # 0x01
|
|
print(f"{cmd} (RETURN)")
|
|
|
|
case _:
|
|
raise Exception(f"command {cmd} {hex(myHAS.tell() - 4)}")
|
|
|
|
# cmd = int.from_bytes(fp, "little", signed=True)
|
|
# if cmd == -1:
|
|
# offset, data = struct.unpack("<LL", myHAS.read(8))
|
|
# #print(f"{cmd} (WRITE): {hex(offset)} {hex(data)}")
|
|
# j.memory_write32(offset, data)
|
|
|
|
# elif cmd == -9:
|
|
# offset, data = struct.unpack("<LL", myHAS.read(8))
|
|
# #print(f"{cmd} (WRITE8): {hex(offset)} {hex(data)}")
|
|
# j.memory_write8(offset, data)
|
|
|
|
# elif cmd == -8:
|
|
# offset, data = struct.unpack("<LL", myHAS.read(8))
|
|
# #print(f"{cmd} (WRITE16): {hex(offset)} {hex(data)}")
|
|
# j.memory_write16(offset, data)
|
|
|
|
# elif cmd == -12: # COPROC
|
|
# cr_m, cr_n, cp_no, op, data = struct.unpack("<BBBBL", myHAS.read(8))
|
|
# #print(F"{cmd} (COPROC) {cp_no}, {cr_n}, {cr_m}, {op}, {hex(data)}")
|
|
# if cp_no == 15: j.cp15_register_write(cr_n, op, cr_m, 0, data)
|
|
|
|
# else:
|
|
# raise Exception(f"command {cmd} {hex(myHAS.tell() - 4)}")
|
|
|
|
class Args(Tap):
|
|
loader: str # DCC loader to load
|
|
output: str # Output file
|
|
load_offset: int # Load offset
|
|
read_start_offset: int # Read start offset
|
|
read_size: int # Read size (enter 0 for full flash)
|
|
family: str = "ARM9" # Device family
|
|
script: str = "" # HAS script
|
|
speed: int = -2 # TCK speed (-2 = RTCK, -1 = Auto)
|
|
flash_id: int = 1 # Read flash ID (0 = MCU)
|
|
reset_strategy: Literal["halt_reset", "halt_bp", "halt_wp", "halt_dbgrq", "software"] = "halt_reset" # ARM Reset strategy
|
|
breakpoint_loader: bool = False # Loader is a breakpoint based loader
|
|
read_block_size: int = 0x100000 # DCC read block size
|
|
|
|
@staticmethod
|
|
def intorhex(x: str):
|
|
try:
|
|
return int(x)
|
|
|
|
except ValueError:
|
|
return int(x, 16)
|
|
|
|
@override
|
|
def configure(self) -> None:
|
|
self.add_argument("loader")
|
|
self.add_argument("output")
|
|
self.add_argument("load_offset", type=self.intorhex)
|
|
self.add_argument("read_start_offset", type=self.intorhex)
|
|
self.add_argument("read_size", type=self.intorhex)
|
|
self.add_argument("--read_block_size", type=self.intorhex)
|
|
|
|
if __name__ == "__main__":
|
|
basicConfig(level="INFO", format="[%(levelname)s] (%(name)s) %(message)s")
|
|
logger = getLogger("DCC Dumper")
|
|
|
|
args = Args().parse_args()
|
|
speed = "adaptive" if args.speed == -2 else ("auto" if args.speed == -1 else args.speed)
|
|
logger.info("Loader: %s", args.loader)
|
|
logger.info("Output: %s", args.output)
|
|
logger.info("Load offset: 0x%08x", args.load_offset)
|
|
logger.info("Read start offset: 0x%08x", args.read_start_offset)
|
|
logger.info("Read size: 0x%08x", args.read_size)
|
|
logger.info("Speed (kHZ): %s", speed)
|
|
logger.info("Family: %s", args.family)
|
|
logger.info("Script: %s", args.script if args.script else "(none)")
|
|
logger.info("Target Flash ID: %d", args.flash_id)
|
|
logger.info("Breakpoint loader: %s", args.breakpoint_loader)
|
|
logger.info("Read block size: 0x%08x", args.read_block_size)
|
|
|
|
myJLink: JLink = JLink()
|
|
myJLink.disable_dialog_boxes()
|
|
myJLink.exec_command("SuppressEmuUSBDialog")
|
|
|
|
logger.info("Connecting to JLink")
|
|
myJLink.open()
|
|
|
|
logger.info("Connecting to target")
|
|
myJLink.connect(args.family, speed=speed) # pyright: ignore[reportArgumentType]
|
|
|
|
logger.info("Halting target")
|
|
myJLink.halt()
|
|
|
|
assert myJLink.halted(), "Target cannot be halted" # Cannot halt the target
|
|
|
|
logger.info("Resetting target")
|
|
match args.reset_strategy:
|
|
case "halt_reset":
|
|
myJLink.set_reset_strategy(ResetType.ARM_RESET_TYPE_NORMAL)
|
|
|
|
case "halt_bp":
|
|
myJLink.set_reset_strategy(ResetType.ARM_RESET_TYPE_BP0)
|
|
|
|
case "halt_wp":
|
|
myJLink.set_reset_strategy(ResetType.ARM_RESET_TYPE_HALT_WP)
|
|
|
|
case "halt_dbgrq":
|
|
myJLink.set_reset_strategy(ResetType.ARM_RESET_TYPE_HALT_DBGRQ)
|
|
|
|
case "software":
|
|
myJLink.set_reset_strategy(ResetType.ARM_RESET_TYPE_SOFT)
|
|
|
|
myJLink.reset()
|
|
assert myJLink.halted(), "Target not halted after reset" # Cannot halt the target after reset
|
|
|
|
if args.script:
|
|
logger.info("Executing HAS script")
|
|
runHAS(myJLink, args.script)
|
|
|
|
logger.info("Uploading loader to target")
|
|
loader = DumpDCC(myJLink, args.breakpoint_loader, logger)
|
|
loader.load(args.loader, args.load_offset)
|
|
|
|
logger.info("Available flash devices:")
|
|
for id, flash in enumerate(filter(lambda x: x is not None, loader.flash_devices), start=1):
|
|
logger.info("CHIP%d: ID 0x%02X/0x%02X (%dMB)", id, flash.manufacturer_id, flash.device_id, flash.flash_size >> 20)
|
|
|
|
dump_oob = bytearray()
|
|
|
|
with open(args.output, "wb") as dumpOut:
|
|
try:
|
|
cur_offset = args.read_start_offset
|
|
END_OFFSET = args.read_start_offset + args.read_size
|
|
|
|
while cur_offset < END_OFFSET:
|
|
read_size = min(args.read_block_size, END_OFFSET - cur_offset)
|
|
logger.debug("Reading %d bytes command to loader at 0x%08x", read_size, cur_offset)
|
|
|
|
blk_dump_data, blk_dump_oob = loader.read_flash(cur_offset, read_size, args.flash_id)
|
|
|
|
dumpOut.write(blk_dump_data)
|
|
dump_oob += blk_dump_oob
|
|
|
|
cur_offset += read_size
|
|
|
|
dumpOut.write(dump_oob)
|
|
|
|
except Exception as e:
|
|
print(f"ERROR: {e}", file=sys.stderr)
|
|
traceback.print_exc()
|
|
|
|
try:
|
|
myJLink.halt()
|
|
assert myJLink.halted(), "cannot halt"
|
|
|
|
print("registers:")
|
|
|
|
regs = myJLink.register_list()
|
|
vals = myJLink.register_read_multiple(regs)
|
|
|
|
for reg, val in zip(regs, vals):
|
|
name = myJLink.register_name(reg)
|
|
print(f"{name} = 0x{val:08x}")
|
|
|
|
print("stack (r13; sp:sp+0x40):")
|
|
sp = myJLink.register_read(ArmRegs.ARM_REG_R13_SVC)
|
|
for e, i in enumerate(myJLink.memory_read32(sp, 16)):
|
|
print(f"0x{sp + (4 * e):08x} = 0x{i:08x}")
|
|
|
|
print("stack (r13; sp-0x40:sp):")
|
|
for e, i in enumerate(myJLink.memory_read32(sp - 0x40, 16)):
|
|
print(f"0x{(sp - 0x40) + (4 * e):08x} = 0x{i:08x}")
|
|
|
|
except Exception as e:
|
|
print(f"cannot get arm state dump:", file=sys.stderr)
|
|
traceback.print_exc()
|
|
|
|
myJLink.close()
|