From 1f03449694e5eb0101ddf7aa9d8f16b335268836 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Thu, 26 Aug 2021 10:46:06 +0200 Subject: [PATCH] added memory image support to priv emulator --- riscemu/CPU.py | 3 +-- riscemu/Registers.py | 15 ++++++++++----- riscemu/debug.py | 4 +--- riscemu/decoder/decoder.py | 7 ++----- riscemu/helpers.py | 11 +++-------- riscemu/priv/CSR.py | 4 ++-- riscemu/priv/ElfLoader.py | 2 +- riscemu/priv/Exceptions.py | 7 ++++++- riscemu/priv/PrivCPU.py | 33 +++++++++++++++++++++------------ riscemu/priv/PrivRV32I.py | 7 +------ riscemu/priv/__main__.py | 9 ++++++++- 11 files changed, 56 insertions(+), 46 deletions(-) diff --git a/riscemu/CPU.py b/riscemu/CPU.py index 469092f..962ef85 100644 --- a/riscemu/CPU.py +++ b/riscemu/CPU.py @@ -148,8 +148,7 @@ class CPU: print(FMT_CPU + "[CPU] Returning to debugger!" + FMT_NONE) return if self.conf.debug_on_exception: - launch_debug_session(self, self.mmu, self.regs, - "Exception encountered, launching debug:") + launch_debug_session(self, self.mmu, self.regs, "Exception encountered, launching debug:") if self.exit: print() diff --git a/riscemu/Registers.py b/riscemu/Registers.py index ab74821..3cc10db 100644 --- a/riscemu/Registers.py +++ b/riscemu/Registers.py @@ -96,14 +96,19 @@ class Registers: """ if reg == 'zero': return False - if reg not in Registers.all_registers(): - raise InvalidRegisterException(reg) + #if reg not in Registers.all_registers(): + # raise InvalidRegisterException(reg) # replace fp register with s1, as these are the same register if reg == 'fp': reg = 's1' if mark_set: self.last_set = reg - self.vals[reg] = val & (2**32 - 1) + # check 32 bit signed bounds + if val < -2147483648: + val = -2147483648 + elif val > 2147483647: + val = 2147483647 + self.vals[reg] = val return True def get(self, reg, mark_read=True): @@ -113,8 +118,8 @@ class Registers: :param mark_read: If the register should be markes as "last read" (only used internally) :return: The contents of register reg """ - if reg not in Registers.all_registers(): - raise InvalidRegisterException(reg) + #if reg not in Registers.all_registers(): + # raise InvalidRegisterException(reg) if reg == 'fp': reg = 's0' if mark_read: diff --git a/riscemu/debug.py b/riscemu/debug.py index 9e6704d..d8a656d 100644 --- a/riscemu/debug.py +++ b/riscemu/debug.py @@ -45,9 +45,7 @@ def launch_debug_session(cpu: 'CPU', mmu: 'MMU', reg: 'Registers', prompt=""): print("Invalid arg count!") return bin = mmu.get_bin_containing(cpu.pc) - if bin is None: - print(FMT_DEBUG + '[Debugger] Not in a section, can\'t execute instructions!' + FMT_NONE) - return + ins = LoadedInstruction(name, list(args), bin) print(FMT_DEBUG + "Running instruction " + ins + FMT_NONE) cpu.run_instruction(ins) diff --git a/riscemu/decoder/decoder.py b/riscemu/decoder/decoder.py index 462add4..267fdf6 100644 --- a/riscemu/decoder/decoder.py +++ b/riscemu/decoder/decoder.py @@ -23,10 +23,7 @@ STATIC_INSN: Dict[int, Tuple[str, List[int], int]] = { def int_from_ins(insn: bytearray): - return (insn[3] << (8 * 3)) + \ - (insn[2] << (8 * 2)) + \ - (insn[1] << 8) + \ - insn[0] + return int.from_bytes(insn, 'little') def name_from_insn(ins: int): @@ -68,7 +65,7 @@ def name_from_insn(ins: int): raise RuntimeError(f"Invalid instruction: {ins:x}") -def decode(ins: bytearray) -> Tuple[str, List[int], int]: +def decode(ins: Union[bytearray, bytes]) -> Tuple[str, List[int], int]: insn = int_from_ins(ins) if insn & 3 != 3: diff --git a/riscemu/helpers.py b/riscemu/helpers.py index 97458cc..85b64af 100644 --- a/riscemu/helpers.py +++ b/riscemu/helpers.py @@ -33,19 +33,14 @@ def int_to_bytes(val, bytes=4, unsigned=False) -> bytearray: """ if unsigned and val < 0: raise NumberFormatException("unsigned negative number!") - return bytearray([ - (val >> ((bytes - i - 1) * 8)) & 0xFF for i in range(bytes) - ]) + return bytearray(to_unsigned(val, bytes).to_bytes(bytes, 'little')) def int_from_bytes(bytes, unsigned=False) -> int: """ byte -> int (two's complement) """ - num = 0 - for b in bytes: - num = num << 8 - num += b + num = int.from_bytes(bytes, 'little') if unsigned: return num @@ -55,7 +50,7 @@ def int_from_bytes(bytes, unsigned=False) -> int: def to_unsigned(num: int, bytes=4) -> int: if num < 0: - return 2 ** (bytes * 8) + num + return (2 ** (bytes * 8)) + num return num diff --git a/riscemu/priv/CSR.py b/riscemu/priv/CSR.py index 547f883..4a2cc7b 100644 --- a/riscemu/priv/CSR.py +++ b/riscemu/priv/CSR.py @@ -2,7 +2,7 @@ from typing import Dict, Union, Callable, Optional from collections import defaultdict from .privmodes import PrivModes from .Exceptions import InstructionAccessFault -from ..helpers import to_unsigned +from ..helpers import to_signed from ..colors import FMT_CSR, FMT_NONE from .CSRConsts import CSR_NAME_TO_ADDR, MSTATUS_LEN_2, MSTATUS_OFFSETS @@ -38,7 +38,7 @@ class CSR: addr = self._name_to_addr(addr) if addr is None: return - val = to_unsigned(val) + val = to_signed(val) self.listeners[addr](self.regs[addr], val) if addr == 0x300: self.mstatus_cache_dirty = True diff --git a/riscemu/priv/ElfLoader.py b/riscemu/priv/ElfLoader.py index e6084fa..8f71740 100644 --- a/riscemu/priv/ElfLoader.py +++ b/riscemu/priv/ElfLoader.py @@ -39,7 +39,7 @@ class ElfExecutable: if not elf.header.e_ident.EI_CLASS == 'ELFCLASS32': raise InvalidElfException("Only 32bit executables are supported!") - self.run_ptr = elf.header.e_entry; + self.run_ptr = elf.header.e_entry for sec in elf.iter_sections(): if isinstance(sec, SymbolTableSection): diff --git a/riscemu/priv/Exceptions.py b/riscemu/priv/Exceptions.py index 2dcbbfd..85503c3 100644 --- a/riscemu/priv/Exceptions.py +++ b/riscemu/priv/Exceptions.py @@ -58,7 +58,7 @@ class CpuTrap(BaseException): if (self.interrupt, self.code) in MCAUSE_TRANSLATION: name = MCAUSE_TRANSLATION[(self.interrupt, self.code)] + "({}, {})".format(self.interrupt, self.code) - return "{} {{priv={}, type={}, mtval={}}}".format( + return "{} {{priv={}, type={}, mtval={:x}}}".format( name, self.priv.name, self.type.name, self.mtval ) @@ -84,3 +84,8 @@ class InstructionAccessFault(CpuTrap): class TimerInterrupt(CpuTrap): def __init__(self): super().__init__(7, 0, CpuTrapType.TIMER) + + +class EcallTrap(CpuTrap): + def __init__(self, mode: PrivModes): + super().__init__(mode.value + 8, 0, CpuTrapType.SOFTWARE) diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 12089d6..aec1cd6 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -8,11 +8,14 @@ import time from riscemu.CPU import * from .CSR import CSR from .ElfLoader import ElfExecutable +from .ImageLoader import ContinuousMMU from .Exceptions import * from .PrivMMU import PrivMMU +from ..IO import TextIO from .PrivRV32I import PrivRV32I from .privmodes import PrivModes from ..instructions.RV32M import RV32M +import json if typing.TYPE_CHECKING: from riscemu import Executable, LoadedExecutable, LoadedInstruction @@ -48,9 +51,14 @@ class PrivCPU(CPU): super().__init__(conf, [PrivRV32I, RV32M]) self.mode: PrivModes = PrivModes.MACHINE - kernel = ElfExecutable('kernel') - self.mmu = PrivMMU(kernel) - self.pc = kernel.run_ptr + with open('mem.img', 'rb') as memf: + data = memf.read() + with open('mem.img.dbg', 'r') as dbgf: + debug_info = json.load(dbgf) + + self.mmu = ContinuousMMU(data, debug_info, self) + self.pc = 0x100 + self.mmu.add_io(TextIO.TextIO(0xff0000, 64)) self.syscall_int = None self.launch_debug = False @@ -109,7 +117,7 @@ class PrivCPU(CPU): def run(self): print(FMT_CPU + '[CPU] Started running from 0x{:08X} ({})'.format(self.pc, "kernel") + FMT_NONE) self._time_start = time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._run(True) + self._run() def _init_csr(self): # set up CSR @@ -161,7 +169,7 @@ class PrivCPU(CPU): def step(self, verbose=True): try: self.cycle += 1 - if self.cycle % 10 == 0: + if self.cycle % 20 == 0: self._timer_step() self._check_interrupt() ins = self.mmu.read_ins(self.pc) @@ -178,7 +186,6 @@ class PrivCPU(CPU): if self._time_timecmp <= (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start: self.pending_traps.append(TimerInterrupt()) self._time_interrupt_enabled = False - print(FMT_CPU + "[CPU] raising timer interrupt: tartegt: {}, current: {}".format(self._time_timecmp, (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start) + FMT_NONE) def _check_interrupt(self): if not (len(self.pending_traps) > 0 and self.csr.get_mstatus('mie')): @@ -186,10 +193,12 @@ class PrivCPU(CPU): # select best interrupt # TODO: actually select based on the official ranking trap = self.pending_traps.pop() # use the most recent trap - print(FMT_CPU + "[CPU] taking trap {}!".format(trap) + FMT_NONE) + if not isinstance(trap, TimerInterrupt): + print(FMT_CPU + "[CPU] taking trap {}!".format(trap) + FMT_NONE) if trap.priv != PrivModes.MACHINE: print(FMT_CPU + "[CPU] Trap not targeting machine mode encountered! - undefined behaviour!" + FMT_NONE) + raise Exception("Undefined behaviour!") if self.mode != PrivModes.USER: print(FMT_CPU + "[CPU] Trap triggered outside of user mode?!" + FMT_NONE) @@ -198,7 +207,7 @@ class PrivCPU(CPU): self.csr.set_mstatus('mpp', self.mode.value) self.csr.set_mstatus('mie', 0) self.csr.set('mcause', trap.mcause) - self.csr.set('mepc', self.pc) + self.csr.set('mepc', self.pc-self.INS_XLEN) self.csr.set('mtval', trap.mtval) self.mode = trap.priv mtvec = self.csr.get('mtvec') @@ -223,16 +232,16 @@ class PrivCPU(CPU): continue cps = (cycle - cycled) / (time_ns - timed) * 1000000000 - #print(" {:03d} cycles in {:08d}ns ({:.2f} cycles/s)".format( + # print(" {:03d} cycles in {:08d}ns ({:.2f} cycles/s)".format( # cycle - cycled, # time_ns - timed, # cps - #)) + # )) cycled = cycle timed = time_ns cps_list.append(cps) - print(" on average {:.0f} cycles/s".format(sum(cps_list) / len(cps_list)) + FMT_NONE) + print(" on average {:.0f} instructions/s".format(sum(cps_list) / len(cps_list)) + FMT_NONE) self._perf_counters = list() def record_perf_profile(self): - self._perf_counters.append((time.perf_counter_ns(), self.cycle)) \ No newline at end of file + self._perf_counters.append((time.perf_counter_ns(), self.cycle)) diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index 78631be..c61c344 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -88,12 +88,7 @@ class PrivRV32I(RV32I): """ Overwrite the scall from userspace RV32I """ - if self.cpu.mode == PrivModes.USER: - raise CpuTrap(8, 0, CpuTrapType.SOFTWARE, self.cpu.mode) # ecall from U mode - elif self.cpu.mode == PrivModes.SUPER: - raise CpuTrap(9, 0, CpuTrapType.SOFTWARE, self.cpu.mode) # ecall from S mode - should not happen - elif self.cpu.mode == PrivModes.MACHINE: - raise CpuTrap(11, 0, CpuTrapType.SOFTWARE, self.cpu.mode) # ecall from M mode + raise EcallTrap(self.cpu.mode) def instruction_beq(self, ins: 'LoadedInstruction'): rs1, rs2, dst = self.parse_rs_rs_imm(ins) diff --git a/riscemu/priv/__main__.py b/riscemu/priv/__main__.py index 0cd2184..47820b7 100644 --- a/riscemu/priv/__main__.py +++ b/riscemu/priv/__main__.py @@ -6,11 +6,18 @@ from ..ExecutableParser import ExecutableParser import sys if __name__ == '__main__': + import argparse - files = sys.argv + #parser = argparse.ArgumentParser(description='RISC-V privileged architecture emulator', prog='riscemu') + + #parser.add_argument('--kernel', type=str, help='Kernel elf loaded with user programs', nargs='?') + #parser.add_argument('--image', type=str, help='Memory image containing kernel', nargs='?') + + #args = parser.parse_args() cpu = PrivCPU(RunConfig()) cpu.run() +