kernel-mode #1

Manually merged
anton merged 69 commits from kernel-mode into master 2021-11-16 08:02:40 +01:00
11 changed files with 56 additions and 46 deletions
Showing only changes of commit 1f03449694 - Show all commits

View File

@ -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()

View File

@ -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:

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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')
@ -223,15 +232,15 @@ class PrivCPU(CPU):
continue continue
cps = (cycle - cycled) / (time_ns - timed) * 1000000000 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, # cycle - cycled,
# time_ns - timed, # time_ns - timed,
# cps # cps
#)) # ))
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):

View File

@ -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)

View File

@ -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()