diff --git a/requirements.txt b/requirements.txt index 7a447fe..d853631 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -pyelftools~=0.27 \ No newline at end of file +pyelftools~=0.27 diff --git a/riscemu/Config.py b/riscemu/Config.py index cc1d74f..40ee2c5 100644 --- a/riscemu/Config.py +++ b/riscemu/Config.py @@ -19,4 +19,5 @@ class RunConfig: # allowed syscalls scall_input: bool = True scall_fs: bool = False + verbosity: int = 0 diff --git a/riscemu/IO/TextIO.py b/riscemu/IO/TextIO.py index d48d465..1a615e0 100644 --- a/riscemu/IO/TextIO.py +++ b/riscemu/IO/TextIO.py @@ -6,8 +6,6 @@ import time def _window_loop(textIO: 'TextIO'): - #textIO.set_sg_window(None) - #return try: import PySimpleGUI as sg @@ -30,10 +28,9 @@ def _window_loop(textIO: 'TextIO'): col.contents_changed() except ImportError: - print("[TextIO] module works best with PySimpleGui!") + print("[TextIO] window disabled - please install PySimpleGui!") textIO.set_sg_window(None) - class TextIO(IOModule): def __init__(self, addr: int, buflen: int = 128): super(TextIO, self).__init__(addr, buflen + 4) diff --git a/riscemu/MMU.py b/riscemu/MMU.py index 0d9c8ff..d7df4af 100644 --- a/riscemu/MMU.py +++ b/riscemu/MMU.py @@ -6,7 +6,7 @@ SPDX-License-Identifier: MIT from .Config import RunConfig from .Executable import Executable, LoadedExecutable, LoadedMemorySection, LoadedInstruction, MemoryFlags -from .helpers import align_addr +from .helpers import align_addr, int_from_bytes from .Exceptions import OutOfMemoryException, InvalidAllocationException from .colors import * from typing import Dict, List, Tuple, Optional @@ -109,7 +109,8 @@ class MMU: raise InvalidAllocationException('Invalid size request', name, req_size, flag) if req_size > self.max_alloc_size: - raise InvalidAllocationException('Cannot allocate more than {} bytes at a time'.format(self.max_alloc_size), name, req_size, flag) + raise InvalidAllocationException('Cannot allocate more than {} bytes at a time'.format(self.max_alloc_size), + name, req_size, flag) base = align_addr(self.first_free_addr) size = align_addr(req_size) @@ -203,6 +204,9 @@ class MMU: if symb in b.symbols: print(" Found local symbol {}: 0x{:X} in {}".format(symb, b.symbols[symb], b.name)) + def read_int(self, addr: int) -> int: + return int_from_bytes(self.read(addr, 4)) + def __repr__(self): return "MMU(\n\t{}\n)".format( "\n\t".join(repr(x) for x in self.sections) diff --git a/riscemu/debug.py b/riscemu/debug.py index d8a656d..930dcbb 100644 --- a/riscemu/debug.py +++ b/riscemu/debug.py @@ -8,6 +8,7 @@ import typing from .Registers import Registers from .colors import FMT_DEBUG, FMT_NONE from .Executable import LoadedInstruction +from .helpers import * if typing.TYPE_CHECKING: from . import * @@ -36,6 +37,9 @@ def launch_debug_session(cpu: 'CPU', mmu: 'MMU', reg: 'Registers', prompt=""): else: mmu.dump(what, *args, **kwargs) + def dump_stack(*args, **kwargs): + mmu.dump(regs.get('sp'), *args, **kwargs) + def ins(): print("Current instruction at 0x{:08X}:".format(cpu.pc)) return mmu.read_ins(cpu.pc) diff --git a/riscemu/priv/ImageLoader.py b/riscemu/priv/ImageLoader.py index 88c8f6b..7ad924c 100644 --- a/riscemu/priv/ImageLoader.py +++ b/riscemu/priv/ImageLoader.py @@ -9,7 +9,7 @@ from .ElfLoader import ElfInstruction, ElfLoadedMemorySection, InstructionAccess from ..decoder import decode from ..IO.IOModule import IOModule from .privmodes import PrivModes -from ..colors import FMT_ERROR, FMT_NONE +from ..colors import FMT_ERROR, FMT_NONE, FMT_MEM import json from functools import lru_cache @@ -117,3 +117,10 @@ class MemoryImageMMU(PrivMMU): if addr >= val: return "{}{:+x} ({}:{})".format(sym, addr - val, sec.owner, sec.name) return "{}:{}{:+x}".format(sec.owner, sec.name, addr - sec.base) + + def symbol(self, symb: str): + print(FMT_MEM + "Looking up symbol {}".format(symb)) + for owner, symbs in self.debug_info['symbols'].items(): + if symb in symbs: + print(" Hit in {}: {} = {}".format(owner, symb, symbs[symb])) + print(FMT_NONE, end="") diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index 02fb648..7fec324 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -83,11 +83,7 @@ class PrivCPU(CPU): if isinstance(ex, LaunchDebuggerException): self.launch_debug = True self.pc += self.INS_XLEN - else: - print(FMT_ERROR + "[CPU] exception caught at 0x{:08X}: {}:".format(self.pc - 1, ins) + FMT_NONE) - print(ex.message()) - if self.conf.debug_on_exception: - self.launch_debug = True + if self.exit: print() print(FMT_CPU + "Program exited with code {}".format(self.exit_code) + FMT_NONE) @@ -114,7 +110,7 @@ class PrivCPU(CPU): def run(self, verbose: bool = False): 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(verbose) + self._run(self.conf.verbosity > 1) def _init_csr(self): # set up CSR @@ -162,8 +158,6 @@ class PrivCPU(CPU): def _handle_trap(self, trap: CpuTrap): # implement trap handling! self.pending_traps.append(trap) - print(FMT_CPU + "Trap {} encountered at {} (0x{:x})".format(trap, self.mmu.translate_address(self.pc), self.pc) + FMT_NONE) - def step(self, verbose=True): try: @@ -178,6 +172,15 @@ class PrivCPU(CPU): self.pc += self.INS_XLEN except CpuTrap as trap: self._handle_trap(trap) + if trap.interrupt == 0 and not isinstance(trap, EcallTrap): + print(FMT_CPU + "[CPU] Trap {} encountered at {} (0x{:x})".format( + trap, + self.mmu.translate_address(self.pc), + self.pc + ) + FMT_NONE) + if self.conf.debug_on_exception: + raise LaunchDebuggerException() + self.pc += self.INS_XLEN def _timer_step(self): if not self._time_interrupt_enabled: @@ -193,7 +196,7 @@ class PrivCPU(CPU): # TODO: actually select based on the official ranking trap = self.pending_traps.pop() # use the most recent trap if not isinstance(trap, TimerInterrupt) or True: - print(FMT_CPU + "[CPU] [{}] taking trap {}!".format(self.cycle, trap) + FMT_NONE) + 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) @@ -206,7 +209,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') @@ -244,4 +247,3 @@ class PrivCPU(CPU): def record_perf_profile(self): self._perf_counters.append((time.perf_counter_ns(), self.cycle)) - diff --git a/riscemu/priv/PrivMMU.py b/riscemu/priv/PrivMMU.py index 28f2ecf..798caa9 100644 --- a/riscemu/priv/PrivMMU.py +++ b/riscemu/priv/PrivMMU.py @@ -22,6 +22,7 @@ class PrivMMU(MMU): def translate_address(self, addr: int): return "" + class LoadedElfMMU(PrivMMU): def __init__(self, elf: ElfExecutable): super().__init__(conf=RunConfig()) diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index a29d336..ca91f37 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -79,15 +79,16 @@ class PrivRV32I(RV32I): mepc = self.cpu.csr.get('mepc') self.cpu.pc = mepc - self.cpu.INS_XLEN - sec = self.mmu.get_sec_containing(mepc) - if sec is not None: - print(FMT_CPU + "[CPU] [{}] returning to mode {} in {} (0x{:x})".format( - self.cpu.cycle, - PrivModes(mpp).name, - self.mmu.translate_address(mepc), - mepc - ) + FMT_NONE) - self.regs.dump_reg_a() + if self.cpu.conf.verbosity > 0: + sec = self.mmu.get_sec_containing(mepc) + if sec is not None: + print(FMT_CPU + "[CPU] returning to mode {} in {} (0x{:x})".format( + PrivModes(mpp).name, + self.mmu.translate_address(mepc), + mepc + ) + FMT_NONE) + if self.cpu.conf.verbosity > 1: + self.regs.dump_reg_a() def instruction_uret(self, ins: 'LoadedInstruction'): raise IllegalInstructionTrap(ins) @@ -139,7 +140,7 @@ class PrivRV32I(RV32I): ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) addr = ins.get_imm(1) - if reg == 'ra' and self.cpu.mode == PrivModes.USER: + if reg == 'ra' and self.cpu.mode == PrivModes.USER and self.cpu.conf.verbosity > 1: print(FMT_CPU + 'Jumping to {} (0x{:x})'.format( self.mmu.translate_address(self.pc + addr), self.pc + addr diff --git a/riscemu/priv/__main__.py b/riscemu/priv/__main__.py index f9d3640..36c0d13 100644 --- a/riscemu/priv/__main__.py +++ b/riscemu/priv/__main__.py @@ -3,9 +3,6 @@ from .ImageLoader import MemoryImageMMU from .PrivMMU import LoadedElfMMU from .ElfLoader import ElfExecutable -from ..Tokenizer import RiscVInput -from ..ExecutableParser import ExecutableParser - import sys if __name__ == '__main__': @@ -15,8 +12,9 @@ if __name__ == '__main__': 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='?') + parser.add_argument('--debug-exceptions', help='Launch the interactive debugger when an exception is generated', action='store_true') - parser.add_argument('-v', '--verbose', help="Verbose output", action='store_true') + parser.add_argument('-v', '--verbose', help="Verbosity level (can be used multiple times)", action='count', default=0) args = parser.parse_args() mmu = None @@ -30,8 +28,8 @@ if __name__ == '__main__': print("You must specify one of --kernel or --image for running in privilege mode!") sys.exit(1) - cpu = PrivCPU(RunConfig(), mmu) - cpu.run(args.verbose) + cpu = PrivCPU(RunConfig(verbosity=args.verbose, debug_on_exception=args.debug_exceptions), mmu) + cpu.run()