[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!
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]))

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

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

@ -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
"""
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)
@ -69,11 +73,58 @@ class PrivRV32I(RV32I):
elif self.cpu.mode == PrivModes.MACHINE:
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)

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

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