fixed priv start code, added tests

assembly-parser-rework
Anton Lydike 3 years ago
parent 4f1c73df9e
commit cd5795bb74

@ -17,7 +17,7 @@ class MemoryImageLoader(ProgramLoader):
@classmethod
def can_parse(cls, source_path: str) -> float:
if source_path.split('.')[-1] == '.img':
if source_path.split('.')[-1] == 'img':
return 1
return 0

@ -11,6 +11,7 @@ from .CSR import CSR
from .ElfLoader import ElfBinaryFileLoader
from .Exceptions import *
from .ImageLoader import MemoryImageLoader
from .PrivMMU import PrivMMU
from .PrivRV32I import PrivRV32I
from .privmodes import PrivModes
from ..instructions import RV32A, RV32M
@ -45,7 +46,7 @@ class PrivCPU(CPU):
"""
def __init__(self, conf):
super().__init__(MMU(), [PrivRV32I, RV32M, RV32A], conf)
super().__init__(PrivMMU(), [PrivRV32I, RV32M, RV32A], conf)
# start in machine mode
self.mode: PrivModes = PrivModes.MACHINE
@ -90,11 +91,17 @@ class PrivCPU(CPU):
print()
print(FMT_CPU + "[CPU] System stopped without halting - perhaps you stopped the debugger?" + FMT_NONE)
def launch(self, program: Program, verbose: bool = False):
def launch(self, program: Optional[Program] = None, verbose: bool = False):
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.run(self.conf.verbosity > 1 or verbose)
def load_program(self, program: Program):
if program.name == 'kernel':
self.pc = program.entrypoint
super().load_program(program)
def _init_csr(self):
# set up CSR
self.csr = CSR()
@ -230,4 +237,4 @@ class PrivCPU(CPU):
def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]:
return [
AssemblyFileLoader, MemoryImageLoader, ElfBinaryFileLoader
]
]

@ -1,3 +1,5 @@
from riscemu import RunConfig
from riscemu.types import Program
from .PrivCPU import PrivCPU
from .ElfLoader import ElfBinaryFileLoader
from .ImageLoader import MemoryImageLoader
@ -9,26 +11,27 @@ if __name__ == '__main__':
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='?')
parser.add_argument('--debug-exceptions', help='Launch the interactive debugger when an exception is generated', action='store_true')
parser.add_argument('source', type=str,
help='Compiled RISC-V ELF file or memory image containing compiled RISC-V ELF files', nargs='+')
parser.add_argument('--debug-exceptions', help='Launch the interactive debugger when an exception is generated',
action='store_true')
parser.add_argument('-v', '--verbose', help="Verbosity level (can be used multiple times)", action='count', default=0)
parser.add_argument('-v', '--verbose', help="Verbosity level (can be used multiple times)", action='count',
default=0)
args = parser.parse_args()
mmu = None
if args.kernel is not None:
mmu = LoadedElfMMU(ElfExecutable(args.kernel))
elif args.image is not None:
mmu = MemoryImageMMU(args.image)
if mmu is None:
print("You must specify one of --kernel or --image for running in privilege mode!")
sys.exit(1)
cpu = PrivCPU(RunConfig(verbosity=args.verbose, debug_on_exception=args.debug_exceptions), mmu)
cpu.run()
cpu = PrivCPU(RunConfig(verbosity=args.verbose, debug_on_exception=args.debug_exceptions))
for source_path in args.source:
loader = max((loader for loader in cpu.get_loaders()), key=lambda l: l.can_parse(source_path))
argv, opts = loader.get_options(sys.argv)
program = loader.instantiate(source_path, opts).parse()
if isinstance(program, Program):
cpu.load_program(program)
else:
program_iter = program
for program in program_iter:
cpu.load_program(program)
cpu.launch()

@ -0,0 +1,73 @@
import contextlib
import os
from abc import abstractmethod
from tempfile import NamedTemporaryFile
from typing import Optional, Union, Tuple
from unittest import TestCase
from riscemu import CPU, UserModeCPU, InstructionSetDict, RunConfig
from riscemu.types import Program
class EndToEndTest(TestCase):
def __init__(self, cpu: Optional[CPU] = None):
super().__init__()
if cpu is None:
cpu = UserModeCPU(InstructionSetDict.values(), RunConfig())
self.cpu = cpu
@abstractmethod
def get_source(self) -> Tuple[str, Union[bytes, str, bytearray]]:
"""
This method returns the source code of the program
:return:
"""
pass
def test_run_program(self):
"""
Runs the program and verifies output
:return:
"""
with self.with_source_file() as names:
fname, orig_name = names
loader = self.cpu.get_best_loader_for(fname)
self.program = loader.instantiate(fname, loader.get_options([])).parse()
self._change_program_file_name(self.program, orig_name)
self.cpu.load_program(self.program)
self.after_program_load(self.program)
if isinstance(self.cpu, UserModeCPU):
self.cpu.setup_stack()
try:
self.cpu.launch(self.program)
except Exception as ex:
if self.is_exception_expected(ex):
pass
raise ex
@contextlib.contextmanager
def with_source_file(self):
name, content = self.get_source()
if isinstance(content, str):
f = NamedTemporaryFile('w', suffix=name, delete=False)
else:
f = NamedTemporaryFile('wb', suffix=name, delete=False)
f.write(content)
f.flush()
f.close()
try:
yield f.name, name
finally:
os.unlink(f.name)
def after_program_load(self, program):
pass
def is_exception_expected(self, ex: Exception) -> bool:
return False
def _change_program_file_name(self, program: Program, new_name: str):
program.name = new_name
for sec in program.sections:
sec.owner = new_name

@ -0,0 +1,77 @@
from riscemu.colors import FMT_ERROR, FMT_NONE, FMT_BOLD, FMT_GREEN
from riscemu.exceptions import ASSERT_LEN
from riscemu.helpers import int_from_bytes
from riscemu.instructions import InstructionSet
from riscemu.types import Instruction, CPU
from riscemu.decoder import RISCV_REGS
FMT_SUCCESS = FMT_GREEN + FMT_BOLD
def assert_equals(ins: Instruction, cpu: CPU):
a, b = (get_arg_from_ins(ins, i, cpu) for i in (0, 2))
return a == b
def assert_equals_mem(ins: Instruction, cpu: CPU):
a, b = (get_arg_from_ins(ins, i, cpu) for i in (0, 2))
a = cpu.mmu.read_int(a)
return a == b
def assert_in(ins: Instruction, cpu: CPU):
a = get_arg_from_ins(ins, 0, cpu)
others = [get_arg_from_ins(ins, i, cpu) for i in range(2, len(ins.args))]
return a in others
def _not(func):
def test(ins: Instruction, cpu: CPU):
return not func(ins, cpu)
return test
def get_arg_from_ins(ins: Instruction, num: int, cpu: CPU):
a = ins.args[num]
if a in RISCV_REGS:
return cpu.regs.get(a)
return ins.get_imm(num)
assert_ops = {
'==': assert_equals,
'!=': _not(assert_equals),
'in': assert_in,
'not_in': _not(assert_in),
}
class TestIS(InstructionSet):
def __init__(self, cpu: 'CPU'):
print('[Test] loading testing ISA, this is only meant for running testcases and is not part of the RISC-V ISA!')
self.failed = False
super().__init__(cpu)
def instruction_assert(self, ins: Instruction):
if len(ins.args) < 3:
print(FMT_ERROR + '[Test] Unknown assert statement: {}'.format(ins) + FMT_NONE)
return
op = ins.args[1]
if op not in assert_ops:
print(FMT_ERROR + '[Test] Unknown operation statement: {} in {}'.format(op, ins) + FMT_NONE)
return
if assert_ops[op](ins, self.cpu):
print(FMT_SUCCESS + '[TestCase] 🟢 passed assertion {}'.format(ins))
else:
print(FMT_ERROR + '[TestCase] 🔴 failed assertion {}'.format(ins))
self.cpu.halted = True
self.failed = True
def instruction_fail(self, ins: Instruction):
print(FMT_ERROR + '[TestCase] 🔴 reached fail instruction! {}'.format(ins))
self.cpu.halted = True
self.failed = True
def assert_mem(self, ins: Instruction):

@ -0,0 +1,53 @@
from riscemu import AssemblyFileLoader
from riscemu.colors import *
FMT_SUCCESS = FMT_GREEN + FMT_BOLD
def run_test(path: str):
from riscemu import CPU, UserModeCPU, RunConfig
from riscemu.instructions import InstructionSetDict
from test.test_isa import TestIS
import os
fname = os.path.basename(path)
ISAs = list(InstructionSetDict.values())
ISAs.append(TestIS)
cpu = UserModeCPU(ISAs, RunConfig())
try:
program = AssemblyFileLoader(path, {}).parse()
cpu.load_program(program)
cpu.launch(program)
except Exception as ex:
print(FMT_ERROR + '[Test] 🔴 failed with exception "{}" ({})'.format(ex, fname) + FMT_NONE)
raise ex
if cpu.halted:
for isa in cpu.instruction_sets:
if isinstance(isa, TestIS):
if not isa.failed:
print(FMT_SUCCESS + '[Test] 🟢 successful {}'.format(fname) + FMT_NONE)
return not isa.failed
return False
if __name__ == '__main__':
import os
import glob
successes = 0
failures = 0
ttl = 0
for path in glob.glob(f'{os.path.dirname(__file__)}/*.asm'):
print(FMT_BLUE + '[Test] running testcase ' + os.path.basename(path) + FMT_NONE)
ttl += 1
if run_test(path):
successes += 1
else:
failures += 1

@ -0,0 +1,7 @@
.data
data:
.word 0xFFFFFFFF, 0x0000FFFF, 0xFF00FF00, 0x7FFFFFFF
.text
ebreak

@ -0,0 +1,20 @@
.text
main:
addi a0, zero, main
addi a1, zero, main
addi t0, zero, 1000
assert a0, ==, 0x100
1:
addi a1, a1, 1
blt a1, t0, 1b
sub a1, a1, a0
j 1f
addi a1, zero, 0
fail
1:
assert a1, ==, 744
add a0, zero, a1 ; set exit code to a1
addi a7, zero, SCALL_EXIT ; exit syscall code
scall
fail
Loading…
Cancel
Save