diff --git a/requirements.txt b/requirements.txt index e69de29..7a447fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1 @@ +pyelftools~=0.27 \ No newline at end of file diff --git a/riscemu/priv/ElfLoader.py b/riscemu/priv/ElfLoader.py index d61446b..549114e 100644 --- a/riscemu/priv/ElfLoader.py +++ b/riscemu/priv/ElfLoader.py @@ -1,9 +1,118 @@ -from ..Executable import Executable, MemorySection, MemoryFlags +from ..Executable import Executable, MemorySection, MemoryFlags, LoadedExecutable, LoadedMemorySection +from ..Exceptions import RiscemuBaseException +from ..helpers import FMT_PARSE, FMT_NONE +from ..colors import FMT_GREEN, FMT_BOLD + +FMT_ELF = FMT_GREEN + FMT_BOLD + +from .Exceptions import * + +from dataclasses import dataclass + +from typing import List, Dict, Union + +from elftools.elf.elffile import ELFFile +from elftools.elf.sections import Section, SymbolTableSection + +from ..decoder import decode # This requires pyelftools package! -class ElfExecutable(Executable): - def __init__(self, name): - from elftools.elf.elffile import ELFFile +INCLUDE_SEC = ('.text', '.stack', '.bss') + + +class ElfExecutable: + sections: List['ElfLoadedMemorySection'] + sections_by_name: Dict[str, 'ElfLoadedMemorySection'] + symbols: Dict[str, int] + run_ptr: int + + def __init__(self, name: str): + self.sections = list() + self.sections_by_name = dict() + self.symbols = dict() + + with open(name, 'rb') as f: + print(FMT_ELF + "[ElfLoader] Loading elf executable from: {}".format(name) + FMT_NONE) + self._read_elf(ELFFile(f)) + + def _read_elf(self, elf: ELFFile): + if not elf.header.e_machine == 'EM_RISCV': + raise InvalidElfException("Not a RISC-V elf file!") + if not elf.header.e_ident.EI_CLASS == 'ELFCLASS32': + raise InvalidElfException("Only 32bit executables are supported!") + + self.run_ptr = elf.header.e_entry; + + for sec in elf.iter_sections(): + if isinstance(sec, SymbolTableSection): + self._parse_symtab(sec) + continue + + if sec.name not in INCLUDE_SEC: + continue + + sec_ = self._lms_from_elf_sec(sec, 'kernel') + self.sections.append(sec_) + self.sections_by_name[sec.name] = sec_ + + def _lms_from_elf_sec(self, sec: Section, owner: str): + is_code = sec.name in ('.text',) + data = sec.data() + flags = MemoryFlags(is_code, is_code) + print(FMT_ELF + "[ElfLoader] Section {} at: {:X}".format(sec.name, sec.header.sh_addr) + FMT_NONE) + return ElfLoadedMemorySection( + sec.name, + sec.header.sh_addr, + sec.data_size, + data, + flags, + owner + ) + + def _parse_symtab(self, symtab: SymbolTableSection): + self.symbols = { + sym.name: sym.entry.st_value for sym in symtab.iter_symbols() if sym.name + } + + def load_user_elf(self, name: str): + pass + + +class InvalidElfException(RiscemuBaseException): + def __init__(self, msg: str): + super().__init__() + self.msg = msg + + def message(self): + return FMT_PARSE + "{}(\"{}\")".format(self.__class__.__name__, self.msg) + FMT_NONE + + +@dataclass(frozen=True) +class ElfInstruction: + name: str + args: List[Union[int, str]] + + def get_imm(self, num: int): + return self.args[-1] + + def get_imm_reg(self, num: int): + return self.args[-1], self.args[-2] + + def get_reg(self, num: int): + return self.args[num] + + def __repr__(self): + return "{:<8} {}".format( + self.name, + ", ".join(map(str, self.args)) + ) - with open(f) +class ElfLoadedMemorySection(LoadedMemorySection): + def read_instruction(self, offset): + if not self.flags.executable: + print(FMT_PARSE + "Reading instruction from non-executable memory!" + FMT_NONE) + raise InstructionAccessFault(offset + self.base) + if offset % 4 != 0: + raise InstructionAddressMisalignedTrap(offset + self.base) + return ElfInstruction(*decode(self.content[offset:offset + 4])) diff --git a/riscemu/priv/Exceptions.py b/riscemu/priv/Exceptions.py index 6a2eafa..37388ad 100644 --- a/riscemu/priv/Exceptions.py +++ b/riscemu/priv/Exceptions.py @@ -21,3 +21,15 @@ class CpuTrap(BaseException): class IllegalInstructionTrap(CpuTrap): def __init__(self): super().__init__(0, 2, 0) + + +class InstructionAddressMisalignedTrap(CpuTrap): + def __init__(self, addr: int): + super().__init__(0, 0, addr) + + +class InstructionAccessFault(CpuTrap): + def __init__(self, addr: int): + super().__init__(0, 1, addr) + + diff --git a/riscemu/priv/PrivCPU.py b/riscemu/priv/PrivCPU.py index b0e2a63..1a5fe32 100644 --- a/riscemu/priv/PrivCPU.py +++ b/riscemu/priv/PrivCPU.py @@ -11,6 +11,9 @@ from .Exceptions import * from .CSR import CSR from .PrivRV32I import PrivRV32I from ..instructions.RV32M import RV32M +from .PrivMMU import PrivMMU +from .ElfLoader import ElfExecutable +from .privmodes import PrivModes from collections import defaultdict @@ -20,13 +23,6 @@ if typing.TYPE_CHECKING: from riscemu import Executable, LoadedExecutable, LoadedInstruction from riscemu.instructions.InstructionSet import InstructionSet - -class PrivModes(IntEnum): - USER = 0 - SUPER = 1 - MACHINE = 3 - - class PrivCPU(CPU): """ This is a CPU that has different modes, instruction sets and registers. @@ -38,7 +34,7 @@ class PrivCPU(CPU): csr: CSR - INS_XLEN = 1 + INS_XLEN = 4 """ Size of an instruction in memory. Should be 4, but since our loading code is shit, instruction take up the equivalent of "1 byte" (this is actually impossible) @@ -48,12 +44,18 @@ class PrivCPU(CPU): super().__init__(conf, [PrivRV32I, RV32M]) self.mode: PrivModes = PrivModes.MACHINE + exec = ElfExecutable('kernel') + self.mmu = PrivMMU(exec) + self.pc = exec.run_ptr + self.syscall_int = None + # set up CSR self.csr = CSR() # TODO: Actually populate the CSR with real data (vendorID, heartID, machine implementation etc) self.csr.set('mhartid', 0) # core id self.csr.set('mimpid', 1) # implementation id - self.csr.set('misa', ()) # available ISA + # TODO: set correct misa + self.csr.set('misa', 1) # available ISA def _run(self, verbose=False): if self.pc <= 0: @@ -66,7 +68,7 @@ class PrivCPU(CPU): ins = self.mmu.read_ins(self.pc) if verbose: print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)) - self.pc += 1 + self.pc += self.INS_XLEN self.run_instruction(ins) except CpuTrap as trap: mie = self.csr.get_mstatus('mie') @@ -97,8 +99,6 @@ class PrivCPU(CPU): else: # standard mode self.pc = (mtvec >> 2) - - except RiscemuBaseException as ex: if not isinstance(ex, LaunchDebuggerException): print(FMT_ERROR + "[CPU] excpetion caught at 0x{:08X}: {}:".format(self.pc - 1, ins) + FMT_NONE) @@ -119,3 +119,17 @@ class PrivCPU(CPU): else: print() print(FMT_CPU + "Program stopped without exiting - perhaps you stopped the debugger?" + FMT_NONE) + + def load(self, e: riscemu.Executable): + raise NotImplementedError("Not supported!") + + def run_loaded(self, le: 'riscemu.LoadedExecutable'): + raise NotImplementedError("Not supported!") + + def get_tokenizer(self, tokenizer_input): + raise NotImplementedError("Not supported!") + + def run(self): + print(FMT_CPU + '[CPU] Started running from 0x{:08X} ({})'.format(self.pc, "kernel") + FMT_NONE) + self._run(True) + diff --git a/riscemu/priv/PrivMMU.py b/riscemu/priv/PrivMMU.py index e69de29..2d67af8 100644 --- a/riscemu/priv/PrivMMU.py +++ b/riscemu/priv/PrivMMU.py @@ -0,0 +1,25 @@ +from ..MMU import * + +import typing + +from .ElfLoader import ElfExecutable + +class PrivMMU(MMU): + def __init__(self, elf: ElfExecutable): + super(PrivMMU, self).__init__(conf=RunConfig()) + + self.binaries.append(elf) + for sec in elf.sections: + self.sections.append(sec) + + def load_bin(self, exe: Executable) -> LoadedExecutable: + raise NotImplementedError("This is a privMMU, it's initialized with a single ElfExecutable!") + + def allocate_section(self, name: str, req_size: int, flag: MemoryFlags): + raise NotImplementedError("Not supported!") + + + + + + diff --git a/riscemu/priv/PrivRV32I.py b/riscemu/priv/PrivRV32I.py index dc56df9..af31fd3 100644 --- a/riscemu/priv/PrivRV32I.py +++ b/riscemu/priv/PrivRV32I.py @@ -4,24 +4,28 @@ RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ -from riscemu.instructions.RV32I import * +from ..instructions.RV32I import * from ..Exceptions import INS_NOT_IMPLEMENTED -from riscemu.priv.PrivCPU import PrivModes, PrivCPU from .Exceptions import * +from .privmodes import PrivModes +import typing + +if typing.TYPE_CHECKING: + from riscemu.priv.PrivCPU import PrivCPU class PrivRV32I(RV32I): - cpu: PrivCPU + cpu: 'PrivCPU' """ This is an extension of RV32I, written for the PrivCPU class """ def instruction_csrrw(self, ins: 'LoadedInstruction'): - rd, off, rs = self.parse_crs_ins(ins) + rd, rs, ind = self.parse_crs_ins(ins) if rd != 'zero': - old_val = int_from_bytes(self.cpu.csr.read(off, 4)) + old_val = int_from_bytes(self.cpu.csr[ind]) self.regs.set(rd, old_val) - self.cpu.csr.write(off, 4, int_to_bytes(rs)) + self.cpu.csr.set(ind, rs) def instruction_csrrs(self, ins: 'LoadedInstruction'): INS_NOT_IMPLEMENTED(ins) @@ -63,17 +67,64 @@ class PrivRV32I(RV32I): Overwrite the scall from userspace RV32I """ if self.cpu.mode == PrivModes.USER: - raise CpuTrap(0, 8) # ecall from U mode + raise CpuTrap(0, 8) # ecall from U mode elif self.cpu.mode == PrivModes.SUPER: - raise CpuTrap(0, 9) # ecall from S mode - should not happen + raise CpuTrap(0, 9) # ecall from S mode - should not happen elif self.cpu.mode == PrivModes.MACHINE: - raise CpuTrap(0, 11) # ecall from M mode - - - - - + raise CpuTrap(0, 11) # ecall from M mode + + def instruction_beq(self, ins: 'LoadedInstruction'): + rs1, rs2, dst = self.parse_rs_rs_imm(ins) + if rs1 == rs2: + self.pc += dst + + def instruction_bne(self, ins: 'LoadedInstruction'): + rs1, rs2, dst = self.parse_rs_rs_imm(ins) + if rs1 != rs2: + self.pc += dst + + def instruction_blt(self, ins: 'LoadedInstruction'): + rs1, rs2, dst = self.parse_rs_rs_imm(ins) + if rs1 < rs2: + self.pc += dst + + def instruction_bge(self, ins: 'LoadedInstruction'): + rs1, rs2, dst = self.parse_rs_rs_imm(ins) + if rs1 >= rs2: + self.pc += dst + + def instruction_bltu(self, ins: 'LoadedInstruction'): + rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) + if rs1 < rs2: + self.pc += dst + + def instruction_bgeu(self, ins: 'LoadedInstruction'): + rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) + if rs1 >= rs2: + self.pc += dst + + # technically deprecated + def instruction_j(self, ins: 'LoadedInstruction'): + raise NotImplementedError("Should never be reached!") + + def instruction_jal(self, ins: 'LoadedInstruction'): + ASSERT_LEN(ins.args, 2) + reg = ins.get_reg(0) + addr = ins.get_imm(1) + self.regs.set(reg, self.pc) + self.pc += addr + + def instruction_jalr(self, ins: 'LoadedInstruction'): + ASSERT_LEN(ins.args, 3) + rd, rs, imm = self.parse_rd_rs_imm(ins) + self.regs.set(rd, self.pc) + self.pc = rs + imm def parse_crs_ins(self, ins: 'LoadedInstruction'): ASSERT_LEN(ins.args, 3) - return ins.get_reg(0), ins.get_imm(1), self.get_reg_content(ins, 2) + return ins.get_reg(0), self.get_reg_content(ins, 1), ins.get_imm(2) + + def parse_mem_ins(self, ins: 'LoadedInstruction') -> Tuple[str, int]: + ASSERT_LEN(ins.args, 3) + print("dop") + return ins.get_reg(1), self.get_reg_content(ins, 0) + ins.get_imm(2) diff --git a/riscemu/priv/__main__.py b/riscemu/priv/__main__.py index dd38576..0cd2184 100644 --- a/riscemu/priv/__main__.py +++ b/riscemu/priv/__main__.py @@ -1,4 +1,4 @@ -from .PrivCPU import * +from .PrivCPU import PrivCPU, RunConfig from ..Tokenizer import RiscVInput from ..ExecutableParser import ExecutableParser @@ -11,17 +11,6 @@ if __name__ == '__main__': cpu = PrivCPU(RunConfig()) - try: - loaded_exe = None - for file in files: - tk = cpu.get_tokenizer(RiscVInput.from_file(file)) - tk.tokenize() - loaded_exe = cpu.load(ExecutableParser(tk).parse()) - # run the last loaded executable - cpu.run_loaded(loaded_exe) - except RiscemuBaseException as e: - print("Error while parsing: {}".format(e.message())) - import traceback - - traceback.print_exception(type(e), e, e.__traceback__) - sys.exit(1) + cpu.run() + + diff --git a/riscemu/priv/privmodes.py b/riscemu/priv/privmodes.py index e69de29..1e58bc2 100644 --- a/riscemu/priv/privmodes.py +++ b/riscemu/priv/privmodes.py @@ -0,0 +1,7 @@ +from enum import IntEnum + + +class PrivModes(IntEnum): + USER = 0 + SUPER = 1 + MACHINE = 3