from dataclasses import dataclass, field from typing import ClassVar, Callable from ctypes import c_uint32, c_int32, c_uint8 import struct from optable import OPCODES, OpcodeDescription, OpL, OpA, OpF, OpD from enum import IntFlag, auto class VMFlags(IntFlag): AFTER_BRANCH = auto() class Condition: def __init__(self, cond: int): self.i: bool = bool(cond & (1 << 3)) self.v: bool = bool(cond & (1 << 2)) self.n: bool = bool(cond & (1 << 1)) self.z: bool = bool(cond & (1 << 0)) class VMCC(IntFlag): OVERFLOW = 1 << 2 NEGATIVE = 1 << 1 ZERO = 1 << 0 @dataclass class VM: instr_callbacks: ClassVar[dict[OpcodeDescription, Callable]] def __init__(self, mem): self.mem: bytearray = mem self.cc: VMCC = VMCC(0) self.pc: c_uint32 = c_uint32(0) self.registers: list[c_int32] = [c_int32(0) for _ in range(256)] self.breakpoints: set[int] = field(default_factory=set) self._vm_flags: VMFlags = VMFlags(0) self.__init_callbacks__() def __init_callbacks__(self): VM.instr_callbacks = { # ariphmetic OpD(OpF(0), OpL.MATH, OpA.ADD): self._math_callback_gen(lambda lhs, rhs: lhs + rhs), OpD(OpF.QUICK, OpL.MATH, OpA.ADD): self._math_quick_callback_gen(lambda lhs, rhs: lhs + rhs), OpD(OpF(0), OpL.MATH, OpA.SUB): self._math_callback_gen(lambda lhs, rhs: lhs - rhs), OpD(OpF.QUICK, OpL.MATH, OpA.SUB): self._math_quick_callback_gen(lambda lhs, rhs: lhs - rhs), OpD(OpF(0), OpL.MATH, OpA.MUL): self._math_callback_gen(lambda lhs, rhs: lhs * rhs), OpD(OpF.QUICK, OpL.MATH, OpA.MUL): self._math_quick_callback_gen(lambda lhs, rhs: lhs * rhs), OpD(OpF(0), OpL.MATH, OpA.DIV): self._math_callback_gen(lambda lhs, rhs: lhs // rhs), OpD(OpF.QUICK, OpL.MATH, OpA.DIV): self._math_quick_callback_gen(lambda lhs, rhs: lhs // rhs), # logical ops OpD(OpF.UNEXPANDED, OpL.MATH, OpA.AND): self._math_callback_gen(lambda lhs, rhs: lhs & rhs), OpD(OpF.UNEXPANDED, OpL.MATH, OpA.OR): self._math_callback_gen(lambda lhs, rhs: lhs | rhs), OpD(OpF.UNEXPANDED, OpL.MATH, OpA.XOR): self._math_callback_gen(lambda lhs, rhs: lhs ^ rhs), OpD(OpF.UNEXPANDED, OpL.MATH, OpA.MASK): self._math_callback_gen( lambda lhs, rhs: lhs & ((~rhs + (1 << 32)) % (1 << 32)) ), # block 2 OpD(OpF(0), OpL.MEM, OpA.LOAD): self._load_callback, OpD(OpF.QUICK, OpL.MEM, OpA.LOAD): self._load_callback, OpD(OpF(0), OpL.MEM, OpA.STORE): self._store_callback, OpD(OpF.QUICK, OpL.MEM, OpA.STORE): self._store_callback, # block 3 OpD(OpF(0), OpL.BRANCH, OpA.BRANCH): self._branch_callback, OpD(OpF.QUICK, OpL.BRANCH, OpA.BRANCH): self._branch_callback, OpD(OpF(0), OpL.BRANCH, OpA.IND_BRANCH): self._branch_indexed_callback } def step(self) -> None: """ Make one step (only step into) """ # По какой-то причине адрессация работает # так, будто мы на 1 слово впереди опкода if not VMFlags.AFTER_BRANCH: self.pc = c_uint32(self.cc.value + 1) # сбрасываем флаг AFTER_BRANCH self._vm_flags &= ~(VMFlags.AFTER_BRANCH) opcode = self.mem[self.pc.value] opdesc = self._fetch_opcode_desc(opcode) args = self._parse_arguments(opdesc) self._run_callback(opdesc, args) def continue_(self) -> None: """ Continue from current breakpoint """ while ((self.pc.value < len(self.mem) // 4) and not self.pc.value in self.breakpoints): self.step() print("breakpoint") input() def run(self) -> None: """ Run from very beginning """ self.pc = c_uint32(0) while (self.pc.value < len(self.mem) // 4): self.continue_() def _fetch_opcode_desc(self, opcode: int): return OPCODES[opcode] def _parse_arguments(self, opdesc: OpcodeDescription) -> tuple[int, ...]: addr = self.pc.value main_part = struct.unpack(">BBBb", self.mem[addr:addr+4]) if not OpF.UNEXPANDED in opdesc.flags or OpF.QUICK in opdesc.flags: upper_part = struct.unpack(">i", self.mem[addr+4:addr+8]) return (*main_part, *upper_part) return main_part def _run_callback( self, opdesc: OpcodeDescription, args: tuple[int, ...] ) -> None: if opdesc.layout == OpL.MATH: assert len(args) == 4 _, r3, r1, r2_or_i8 = args self.instr_callbacks[opdesc]( r3, r1, r2_or_i8 ) if opdesc.layout == OpL.MEM: if OpF.QUICK in opdesc.flags: assert len(args) == 4 _, r3, r1, i8 = args self.instr_callbacks[opdesc]( r3, r1, i8 ) else: assert len(args) == 5 _, r3, r1, _, disp = args self.instr_callbacks[opdesc]( r3, r1, disp ) if opdesc.layout == OpL.BRANCH: if OpF.QUICK in opdesc.flags: assert len(args) == 4 _, cond, _, i8 = args self.instr_callbacks[opdesc]( cond, i8 ) elif opdesc.action == OpA.IND_BRANCH: assert len(args) == 5 _, cond, r1, _, disp = args self.instr_callbacks[opdesc]( cond, r1, disp ) else: assert len(args) == 5 _, cond, _, _, disp = args self.instr_callbacks[opdesc]( cond, disp ) def _math_callback_gen( self, operation: Callable[[int, int], int] ) -> Callable[["VM", int, int ,int], None]: """ Поскольку математические операции конструируются по одному шаблону, я завел функцию высшего порядка, которая будет их генерировать """ def callback(self, r3: int, r1: int, r2: int): lhs = self.registers[r1].value rhs = self.registers[r2].value self.registers[r3] = c_int32(operation(lhs, rhs)) return callback def _math_quick_callback_gen( self, operation: Callable[[int, int], int] ) -> Callable[["VM", int, int ,int], None]: """ Поскольку математические операции конструируются по одному шаблону, я завел функцию высшего порядка, которая будет их генерировать. В отлиие от _math_callback_gen, эта уже предназначена для операций с пометкой QUICK """ def callback(self, r3: int, r1: int, i8: int) -> None: self.cc = VMCC(0) lhs = self.registers[r1].value result = operation(lhs, i8) if result < 0: self.cc |= VMCC.NEGATIVE elif result == 0: self.cc |= VMCC.ZERO # самая дорогая проверка на переполнение) try: struct.pack('i', result) except struct.error: self.cc |= VMCC.OVERFLOW self.registers[r3] = c_int32(result) return callback def _load_callback(self, r3: int, r1: int, disp: int) -> None: addr = (self.registers[r1].value + disp) * 4 self.registers[r3] = c_int32( struct.unpack(">i", self.mem[addr:addr+4])[0] ) def _store_callback(self, r3: int, r1: int, disp: int) -> None: addr = (self.registers[r1].value + disp) * 4 self.mem[addr:addr+4] = struct.pack(">i", self.registers[r3].value) def _branch_callback(self, cond: int, disp: int) -> None: c = Condition(cond) vm_c = Condition(self.cc.value) if (c.v & vm_c.v) & (c.n & vm_c.n) & (c.z & vm_c.z) == c.i: self._vm_flags |= VMFlags.AFTER_BRANCH self.pc = c_uint32(self.pc.value + disp) def _branch_indexed_callback(self, cond: int, r1: int, disp: int) -> None: c = Condition(cond) vm_c = Condition(self.cc.value) if (c.v & vm_c.v) & (c.n & vm_c.n) & (c.z & vm_c.z) == c.i: self._vm_flags |= VMFlags.AFTER_BRANCH addr = self.registers[r1].value + disp self.pc = c_uint32(addr)