[priv] module now able to load and execute elf binaries

kernel-mode
Anton Lydike 4 years ago
parent a4735db388
commit 15da68995c

@ -0,0 +1 @@
pyelftools~=0.27

@ -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! # This requires pyelftools package!
class ElfExecutable(Executable): INCLUDE_SEC = ('.text', '.stack', '.bss')
def __init__(self, name):
from elftools.elf.elffile import ELFFile
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]))

@ -21,3 +21,15 @@ class CpuTrap(BaseException):
class IllegalInstructionTrap(CpuTrap): class IllegalInstructionTrap(CpuTrap):
def __init__(self): def __init__(self):
super().__init__(0, 2, 0) 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)

@ -11,6 +11,9 @@ from .Exceptions import *
from .CSR import CSR from .CSR import CSR
from .PrivRV32I import PrivRV32I from .PrivRV32I import PrivRV32I
from ..instructions.RV32M import RV32M from ..instructions.RV32M import RV32M
from .PrivMMU import PrivMMU
from .ElfLoader import ElfExecutable
from .privmodes import PrivModes
from collections import defaultdict from collections import defaultdict
@ -20,13 +23,6 @@ if typing.TYPE_CHECKING:
from riscemu import Executable, LoadedExecutable, LoadedInstruction from riscemu import Executable, LoadedExecutable, LoadedInstruction
from riscemu.instructions.InstructionSet import InstructionSet from riscemu.instructions.InstructionSet import InstructionSet
class PrivModes(IntEnum):
USER = 0
SUPER = 1
MACHINE = 3
class PrivCPU(CPU): class PrivCPU(CPU):
""" """
This is a CPU that has different modes, instruction sets and registers. This is a CPU that has different modes, instruction sets and registers.
@ -38,7 +34,7 @@ class PrivCPU(CPU):
csr: CSR 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 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) the equivalent of "1 byte" (this is actually impossible)
@ -48,12 +44,18 @@ class PrivCPU(CPU):
super().__init__(conf, [PrivRV32I, RV32M]) super().__init__(conf, [PrivRV32I, RV32M])
self.mode: PrivModes = PrivModes.MACHINE self.mode: PrivModes = PrivModes.MACHINE
exec = ElfExecutable('kernel')
self.mmu = PrivMMU(exec)
self.pc = exec.run_ptr
self.syscall_int = None
# set up CSR # set up CSR
self.csr = CSR() self.csr = CSR()
# TODO: Actually populate the CSR with real data (vendorID, heartID, machine implementation etc) # TODO: Actually populate the CSR with real data (vendorID, heartID, machine implementation etc)
self.csr.set('mhartid', 0) # core id self.csr.set('mhartid', 0) # core id
self.csr.set('mimpid', 1) # implementation 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): def _run(self, verbose=False):
if self.pc <= 0: if self.pc <= 0:
@ -66,7 +68,7 @@ class PrivCPU(CPU):
ins = self.mmu.read_ins(self.pc) ins = self.mmu.read_ins(self.pc)
if verbose: if verbose:
print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)) print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins))
self.pc += 1 self.pc += self.INS_XLEN
self.run_instruction(ins) self.run_instruction(ins)
except CpuTrap as trap: except CpuTrap as trap:
mie = self.csr.get_mstatus('mie') mie = self.csr.get_mstatus('mie')
@ -97,8 +99,6 @@ class PrivCPU(CPU):
else: else:
# standard mode # standard mode
self.pc = (mtvec >> 2) self.pc = (mtvec >> 2)
except RiscemuBaseException as ex: except RiscemuBaseException as ex:
if not isinstance(ex, LaunchDebuggerException): if not isinstance(ex, LaunchDebuggerException):
print(FMT_ERROR + "[CPU] excpetion caught at 0x{:08X}: {}:".format(self.pc - 1, ins) + FMT_NONE) print(FMT_ERROR + "[CPU] excpetion caught at 0x{:08X}: {}:".format(self.pc - 1, ins) + FMT_NONE)
@ -119,3 +119,17 @@ class PrivCPU(CPU):
else: else:
print() print()
print(FMT_CPU + "Program stopped without exiting - perhaps you stopped the debugger?" + FMT_NONE) 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)

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

@ -4,24 +4,28 @@ RiscEmu (c) 2021 Anton Lydike
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT
""" """
from riscemu.instructions.RV32I import * from ..instructions.RV32I import *
from ..Exceptions import INS_NOT_IMPLEMENTED from ..Exceptions import INS_NOT_IMPLEMENTED
from riscemu.priv.PrivCPU import PrivModes, PrivCPU
from .Exceptions import * from .Exceptions import *
from .privmodes import PrivModes
import typing
if typing.TYPE_CHECKING:
from riscemu.priv.PrivCPU import PrivCPU
class PrivRV32I(RV32I): class PrivRV32I(RV32I):
cpu: PrivCPU cpu: 'PrivCPU'
""" """
This is an extension of RV32I, written for the PrivCPU class This is an extension of RV32I, written for the PrivCPU class
""" """
def instruction_csrrw(self, ins: 'LoadedInstruction'): 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': 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.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'): def instruction_csrrs(self, ins: 'LoadedInstruction'):
INS_NOT_IMPLEMENTED(ins) INS_NOT_IMPLEMENTED(ins)
@ -63,17 +67,64 @@ class PrivRV32I(RV32I):
Overwrite the scall from userspace RV32I Overwrite the scall from userspace RV32I
""" """
if self.cpu.mode == PrivModes.USER: 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: 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: 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'): def parse_crs_ins(self, ins: 'LoadedInstruction'):
ASSERT_LEN(ins.args, 3) 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)

@ -1,4 +1,4 @@
from .PrivCPU import * from .PrivCPU import PrivCPU, RunConfig
from ..Tokenizer import RiscVInput from ..Tokenizer import RiscVInput
from ..ExecutableParser import ExecutableParser from ..ExecutableParser import ExecutableParser
@ -11,17 +11,6 @@ if __name__ == '__main__':
cpu = PrivCPU(RunConfig()) cpu = PrivCPU(RunConfig())
try: cpu.run()
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)

@ -0,0 +1,7 @@
from enum import IntEnum
class PrivModes(IntEnum):
USER = 0
SUPER = 1
MACHINE = 3
Loading…
Cancel
Save