added memory image support to priv emulator

kernel-mode
Anton Lydike 3 years ago
parent 4c352d8567
commit 1f03449694

@ -148,8 +148,7 @@ class CPU:
print(FMT_CPU + "[CPU] Returning to debugger!" + FMT_NONE) print(FMT_CPU + "[CPU] Returning to debugger!" + FMT_NONE)
return return
if self.conf.debug_on_exception: if self.conf.debug_on_exception:
launch_debug_session(self, self.mmu, self.regs, launch_debug_session(self, self.mmu, self.regs, "Exception encountered, launching debug:")
"Exception encountered, launching debug:")
if self.exit: if self.exit:
print() print()

@ -96,14 +96,19 @@ class Registers:
""" """
if reg == 'zero': if reg == 'zero':
return False return False
if reg not in Registers.all_registers(): #if reg not in Registers.all_registers():
raise InvalidRegisterException(reg) # raise InvalidRegisterException(reg)
# replace fp register with s1, as these are the same register # replace fp register with s1, as these are the same register
if reg == 'fp': if reg == 'fp':
reg = 's1' reg = 's1'
if mark_set: if mark_set:
self.last_set = reg 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 return True
def get(self, reg, mark_read=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) :param mark_read: If the register should be markes as "last read" (only used internally)
:return: The contents of register reg :return: The contents of register reg
""" """
if reg not in Registers.all_registers(): #if reg not in Registers.all_registers():
raise InvalidRegisterException(reg) # raise InvalidRegisterException(reg)
if reg == 'fp': if reg == 'fp':
reg = 's0' reg = 's0'
if mark_read: if mark_read:

@ -45,9 +45,7 @@ def launch_debug_session(cpu: 'CPU', mmu: 'MMU', reg: 'Registers', prompt=""):
print("Invalid arg count!") print("Invalid arg count!")
return return
bin = mmu.get_bin_containing(cpu.pc) 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) ins = LoadedInstruction(name, list(args), bin)
print(FMT_DEBUG + "Running instruction " + ins + FMT_NONE) print(FMT_DEBUG + "Running instruction " + ins + FMT_NONE)
cpu.run_instruction(ins) cpu.run_instruction(ins)

@ -23,10 +23,7 @@ STATIC_INSN: Dict[int, Tuple[str, List[int], int]] = {
def int_from_ins(insn: bytearray): def int_from_ins(insn: bytearray):
return (insn[3] << (8 * 3)) + \ return int.from_bytes(insn, 'little')
(insn[2] << (8 * 2)) + \
(insn[1] << 8) + \
insn[0]
def name_from_insn(ins: int): def name_from_insn(ins: int):
@ -68,7 +65,7 @@ def name_from_insn(ins: int):
raise RuntimeError(f"Invalid instruction: {ins:x}") 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) insn = int_from_ins(ins)
if insn & 3 != 3: if insn & 3 != 3:

@ -33,19 +33,14 @@ def int_to_bytes(val, bytes=4, unsigned=False) -> bytearray:
""" """
if unsigned and val < 0: if unsigned and val < 0:
raise NumberFormatException("unsigned negative number!") raise NumberFormatException("unsigned negative number!")
return bytearray([ return bytearray(to_unsigned(val, bytes).to_bytes(bytes, 'little'))
(val >> ((bytes - i - 1) * 8)) & 0xFF for i in range(bytes)
])
def int_from_bytes(bytes, unsigned=False) -> int: def int_from_bytes(bytes, unsigned=False) -> int:
""" """
byte -> int (two's complement) byte -> int (two's complement)
""" """
num = 0 num = int.from_bytes(bytes, 'little')
for b in bytes:
num = num << 8
num += b
if unsigned: if unsigned:
return num return num
@ -55,7 +50,7 @@ def int_from_bytes(bytes, unsigned=False) -> int:
def to_unsigned(num: int, bytes=4) -> int: def to_unsigned(num: int, bytes=4) -> int:
if num < 0: if num < 0:
return 2 ** (bytes * 8) + num return (2 ** (bytes * 8)) + num
return num return num

@ -2,7 +2,7 @@ from typing import Dict, Union, Callable, Optional
from collections import defaultdict from collections import defaultdict
from .privmodes import PrivModes from .privmodes import PrivModes
from .Exceptions import InstructionAccessFault from .Exceptions import InstructionAccessFault
from ..helpers import to_unsigned from ..helpers import to_signed
from ..colors import FMT_CSR, FMT_NONE from ..colors import FMT_CSR, FMT_NONE
from .CSRConsts import CSR_NAME_TO_ADDR, MSTATUS_LEN_2, MSTATUS_OFFSETS from .CSRConsts import CSR_NAME_TO_ADDR, MSTATUS_LEN_2, MSTATUS_OFFSETS
@ -38,7 +38,7 @@ class CSR:
addr = self._name_to_addr(addr) addr = self._name_to_addr(addr)
if addr is None: if addr is None:
return return
val = to_unsigned(val) val = to_signed(val)
self.listeners[addr](self.regs[addr], val) self.listeners[addr](self.regs[addr], val)
if addr == 0x300: if addr == 0x300:
self.mstatus_cache_dirty = True self.mstatus_cache_dirty = True

@ -39,7 +39,7 @@ class ElfExecutable:
if not elf.header.e_ident.EI_CLASS == 'ELFCLASS32': if not elf.header.e_ident.EI_CLASS == 'ELFCLASS32':
raise InvalidElfException("Only 32bit executables are supported!") 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(): for sec in elf.iter_sections():
if isinstance(sec, SymbolTableSection): if isinstance(sec, SymbolTableSection):

@ -58,7 +58,7 @@ class CpuTrap(BaseException):
if (self.interrupt, self.code) in MCAUSE_TRANSLATION: if (self.interrupt, self.code) in MCAUSE_TRANSLATION:
name = MCAUSE_TRANSLATION[(self.interrupt, self.code)] + "({}, {})".format(self.interrupt, self.code) 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 name, self.priv.name, self.type.name, self.mtval
) )
@ -84,3 +84,8 @@ class InstructionAccessFault(CpuTrap):
class TimerInterrupt(CpuTrap): class TimerInterrupt(CpuTrap):
def __init__(self): def __init__(self):
super().__init__(7, 0, CpuTrapType.TIMER) super().__init__(7, 0, CpuTrapType.TIMER)
class EcallTrap(CpuTrap):
def __init__(self, mode: PrivModes):
super().__init__(mode.value + 8, 0, CpuTrapType.SOFTWARE)

@ -8,11 +8,14 @@ import time
from riscemu.CPU import * from riscemu.CPU import *
from .CSR import CSR from .CSR import CSR
from .ElfLoader import ElfExecutable from .ElfLoader import ElfExecutable
from .ImageLoader import ContinuousMMU
from .Exceptions import * from .Exceptions import *
from .PrivMMU import PrivMMU from .PrivMMU import PrivMMU
from ..IO import TextIO
from .PrivRV32I import PrivRV32I from .PrivRV32I import PrivRV32I
from .privmodes import PrivModes from .privmodes import PrivModes
from ..instructions.RV32M import RV32M from ..instructions.RV32M import RV32M
import json
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from riscemu import Executable, LoadedExecutable, LoadedInstruction from riscemu import Executable, LoadedExecutable, LoadedInstruction
@ -48,9 +51,14 @@ class PrivCPU(CPU):
super().__init__(conf, [PrivRV32I, RV32M]) super().__init__(conf, [PrivRV32I, RV32M])
self.mode: PrivModes = PrivModes.MACHINE self.mode: PrivModes = PrivModes.MACHINE
kernel = ElfExecutable('kernel') with open('mem.img', 'rb') as memf:
self.mmu = PrivMMU(kernel) data = memf.read()
self.pc = kernel.run_ptr 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.syscall_int = None
self.launch_debug = False self.launch_debug = False
@ -109,7 +117,7 @@ class PrivCPU(CPU):
def run(self): def run(self):
print(FMT_CPU + '[CPU] Started running from 0x{:08X} ({})'.format(self.pc, "kernel") + FMT_NONE) 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._time_start = time.perf_counter_ns() // self.TIME_RESOLUTION_NS
self._run(True) self._run()
def _init_csr(self): def _init_csr(self):
# set up CSR # set up CSR
@ -161,7 +169,7 @@ class PrivCPU(CPU):
def step(self, verbose=True): def step(self, verbose=True):
try: try:
self.cycle += 1 self.cycle += 1
if self.cycle % 10 == 0: if self.cycle % 20 == 0:
self._timer_step() self._timer_step()
self._check_interrupt() self._check_interrupt()
ins = self.mmu.read_ins(self.pc) 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: if self._time_timecmp <= (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start:
self.pending_traps.append(TimerInterrupt()) self.pending_traps.append(TimerInterrupt())
self._time_interrupt_enabled = False 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): def _check_interrupt(self):
if not (len(self.pending_traps) > 0 and self.csr.get_mstatus('mie')): if not (len(self.pending_traps) > 0 and self.csr.get_mstatus('mie')):
@ -186,10 +193,12 @@ class PrivCPU(CPU):
# select best interrupt # select best interrupt
# TODO: actually select based on the official ranking # TODO: actually select based on the official ranking
trap = self.pending_traps.pop() # use the most recent trap trap = self.pending_traps.pop() # use the most recent trap
if not isinstance(trap, TimerInterrupt):
print(FMT_CPU + "[CPU] taking trap {}!".format(trap) + FMT_NONE) print(FMT_CPU + "[CPU] taking trap {}!".format(trap) + FMT_NONE)
if trap.priv != PrivModes.MACHINE: if trap.priv != PrivModes.MACHINE:
print(FMT_CPU + "[CPU] Trap not targeting machine mode encountered! - undefined behaviour!" + FMT_NONE) print(FMT_CPU + "[CPU] Trap not targeting machine mode encountered! - undefined behaviour!" + FMT_NONE)
raise Exception("Undefined behaviour!")
if self.mode != PrivModes.USER: if self.mode != PrivModes.USER:
print(FMT_CPU + "[CPU] Trap triggered outside of user mode?!" + FMT_NONE) 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('mpp', self.mode.value)
self.csr.set_mstatus('mie', 0) self.csr.set_mstatus('mie', 0)
self.csr.set('mcause', trap.mcause) 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.csr.set('mtval', trap.mtval)
self.mode = trap.priv self.mode = trap.priv
mtvec = self.csr.get('mtvec') mtvec = self.csr.get('mtvec')
@ -231,7 +240,7 @@ class PrivCPU(CPU):
cycled = cycle cycled = cycle
timed = time_ns timed = time_ns
cps_list.append(cps) 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() self._perf_counters = list()
def record_perf_profile(self): def record_perf_profile(self):

@ -88,12 +88,7 @@ class PrivRV32I(RV32I):
""" """
Overwrite the scall from userspace RV32I Overwrite the scall from userspace RV32I
""" """
if self.cpu.mode == PrivModes.USER: raise EcallTrap(self.cpu.mode)
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
def instruction_beq(self, ins: 'LoadedInstruction'): def instruction_beq(self, ins: 'LoadedInstruction'):
rs1, rs2, dst = self.parse_rs_rs_imm(ins) rs1, rs2, dst = self.parse_rs_rs_imm(ins)

@ -6,11 +6,18 @@ from ..ExecutableParser import ExecutableParser
import sys import sys
if __name__ == '__main__': 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 = PrivCPU(RunConfig())
cpu.run() cpu.run()

Loading…
Cancel
Save