format black

master
Anton Lydike 2 years ago
parent e1fbe4f11d
commit 5515c7795c

@ -30,7 +30,9 @@ class UserModeCPU(CPU):
It is initialized with a configuration and a list of instruction sets.
"""
def __init__(self, instruction_sets: List[Type['riscemu.InstructionSet']], conf: RunConfig):
def __init__(
self, instruction_sets: List[Type["riscemu.InstructionSet"]], conf: RunConfig
):
"""
Creates a CPU instance.
@ -54,7 +56,11 @@ class UserModeCPU(CPU):
Execute a single instruction, then return.
"""
if self.halted:
print(FMT_CPU + "[CPU] Program exited with code {}".format(self.exit_code) + FMT_NONE)
print(
FMT_CPU
+ "[CPU] Program exited with code {}".format(self.exit_code)
+ FMT_NONE
)
return
launch_debugger = False
@ -63,7 +69,9 @@ class UserModeCPU(CPU):
self.cycle += 1
ins = self.mmu.read_ins(self.pc)
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 += self.INS_XLEN
self.run_instruction(ins)
except RiscemuBaseException as ex:
@ -72,12 +80,12 @@ class UserModeCPU(CPU):
if self.debugger_active:
raise ex
print(FMT_CPU + '[CPU] Debugger launch requested!' + FMT_NONE)
print(FMT_CPU + "[CPU] Debugger launch requested!" + FMT_NONE)
launch_debugger = True
else:
print(ex.message())
ex.print_stacktrace()
print(FMT_CPU + '[CPU] Halting due to exception!' + FMT_NONE)
print(FMT_CPU + "[CPU] Halting due to exception!" + FMT_NONE)
self.halted = True
if launch_debugger:
@ -88,7 +96,11 @@ class UserModeCPU(CPU):
self.step(verbose)
if self.conf.verbosity > 0:
print(FMT_CPU + "[CPU] Program exited with code {}".format(self.exit_code) + FMT_NONE)
print(
FMT_CPU
+ "[CPU] Program exited with code {}".format(self.exit_code)
+ FMT_NONE
)
def setup_stack(self, stack_size=1024 * 4) -> bool:
"""
@ -98,22 +110,26 @@ class UserModeCPU(CPU):
"""
stack_sec = BinaryDataMemorySection(
bytearray(stack_size),
'.stack',
".stack",
None, # FIXME: why does a binary data memory section require a context?
'',
0
"",
0,
)
if not self.mmu.load_section(stack_sec, fixed_position=False):
print(FMT_ERROR + "[CPU] Could not insert stack section!" + FMT_NONE)
return False
self.regs.set('sp', Int32(stack_sec.base + stack_sec.size))
self.regs.set("sp", Int32(stack_sec.base + stack_sec.size))
if self.conf.verbosity > 1:
print(FMT_CPU + "[CPU] Created stack of size {} at 0x{:x}".format(
stack_size, stack_sec.base
) + FMT_NONE)
print(
FMT_CPU
+ "[CPU] Created stack of size {} at 0x{:x}".format(
stack_size, stack_sec.base
)
+ FMT_NONE
)
return True

@ -5,12 +5,21 @@ from riscemu.types import MemorySection, MemoryFlags, T_RelativeAddress
class IOModule(MemorySection, ABC):
def __init__(self, name: str, flags: MemoryFlags, size: int, owner: str = 'system', base: int = 0):
def __init__(
self,
name: str,
flags: MemoryFlags,
size: int,
owner: str = "system",
base: int = 0,
):
super(IOModule, self).__init__(name, flags, size, base, owner, None)
def contains(self, addr, size: int = 0):
return self.base <= addr < self.base + self.size and \
self.base <= addr + size <= self.base + self.size
return (
self.base <= addr < self.base + self.size
and self.base <= addr + size <= self.base + self.size
)
def dump(self, *args, **kwargs):
print(self)

@ -8,7 +8,9 @@ class TextIO(IOModule):
raise InstructionAccessFault(self.base + offset)
def __init__(self, base: int, buflen: int = 128):
super(TextIO, self).__init__('TextIO', MemoryFlags(False, False), buflen + 4, base=base)
super(TextIO, self).__init__(
"TextIO", MemoryFlags(False, False), buflen + 4, base=base
)
self.buff = bytearray(buflen)
self.current_line = ""
@ -23,15 +25,15 @@ class TextIO(IOModule):
self._print()
return
buff_start = addr - 4
self.buff[buff_start:buff_start + size] = data[0:size]
self.buff[buff_start : buff_start + size] = data[0:size]
def _print(self):
buff = self.buff
self.buff = bytearray(self.size)
if b'\x00' in buff:
buff = buff.split(b'\x00')[0]
text = buff.decode('ascii')
if '\n' in text:
if b"\x00" in buff:
buff = buff.split(b"\x00")[0]
text = buff.decode("ascii")
if "\n" in text:
lines = text.split("\n")
lines[0] = self.current_line + lines[0]
for line in lines[:-1]:

@ -8,8 +8,15 @@ from typing import Dict, List, Optional, Union
from .colors import *
from .helpers import align_addr
from .types import Instruction, MemorySection, MemoryFlags, T_AbsoluteAddress, \
Program, InstructionContext, Int32
from .types import (
Instruction,
MemorySection,
MemoryFlags,
T_AbsoluteAddress,
Program,
InstructionContext,
Int32,
)
from .types.exceptions import InvalidAllocationException, MemoryAccessException
@ -80,8 +87,14 @@ class MMU:
"""
sec = self.get_sec_containing(addr)
if sec is None:
print(FMT_MEM + "[MMU] Trying to read instruction form invalid region! (read at {}) ".format(addr)
+ "Have you forgotten an exit syscall or ret statement?" + FMT_NONE)
print(
FMT_MEM
+ "[MMU] Trying to read instruction form invalid region! (read at {}) ".format(
addr
)
+ "Have you forgotten an exit syscall or ret statement?"
+ FMT_NONE
)
raise RuntimeError("No next instruction available!")
return sec.read_ins(addr - sec.base)
@ -97,8 +110,16 @@ class MMU:
addr = addr.unsigned_value
sec = self.get_sec_containing(addr)
if sec is None:
print(FMT_MEM + "[MMU] Trying to read data form invalid region at 0x{:x}! ".format(addr) + FMT_NONE)
raise MemoryAccessException("region is non-initialized!", addr, size, 'read')
print(
FMT_MEM
+ "[MMU] Trying to read data form invalid region at 0x{:x}! ".format(
addr
)
+ FMT_NONE
)
raise MemoryAccessException(
"region is non-initialized!", addr, size, "read"
)
return sec.read(addr - sec.base, size)
def write(self, addr: int, size: int, data: bytearray):
@ -111,8 +132,16 @@ class MMU:
"""
sec = self.get_sec_containing(addr)
if sec is None:
print(FMT_MEM + '[MMU] Invalid write into non-initialized region at 0x{:08X}'.format(addr) + FMT_NONE)
raise MemoryAccessException("region is non-initialized!", addr, size, 'write')
print(
FMT_MEM
+ "[MMU] Invalid write into non-initialized region at 0x{:08X}".format(
addr
)
+ FMT_NONE
)
raise MemoryAccessException(
"region is non-initialized!", addr, size, "write"
)
return sec.write(addr - sec.base, size, data)
@ -126,7 +155,11 @@ class MMU:
"""
sec = self.get_sec_containing(addr)
if sec is None:
print(FMT_MEM + "[MMU] No section containing addr 0x{:08X}".format(addr) + FMT_NONE)
print(
FMT_MEM
+ "[MMU] No section containing addr 0x{:08X}".format(addr)
+ FMT_NONE
)
return
sec.dump(addr - sec.base, *args, **kwargs)
@ -138,10 +171,18 @@ class MMU:
"""
print(FMT_MEM + "[MMU] Lookup for symbol {}:".format(symb) + FMT_NONE)
if symb in self.global_symbols:
print(" Found global symbol {}: 0x{:X}".format(symb, self.global_symbols[symb]))
print(
" Found global symbol {}: 0x{:X}".format(
symb, self.global_symbols[symb]
)
)
for bin in self.programs:
if symb in bin.context.labels:
print(" Found local labels {}: 0x{:X} in {}".format(symb, bin.context.labels[symb], bin.name))
print(
" Found local labels {}: 0x{:X} in {}".format(
symb, bin.context.labels[symb], bin.name
)
)
def read_int(self, addr: int) -> Int32:
return Int32(self.read(addr, 4))
@ -154,17 +195,27 @@ class MMU:
bin = self.get_bin_containing(address)
secs = set(sec.name for sec in bin.sections) if bin else []
elf_markers = {
'__global_pointer$', '_fdata', '_etext', '_gp',
'_bss_start', '_bss_end', '_ftext', '_edata', '_end', '_fbss'
"__global_pointer$",
"_fdata",
"_etext",
"_gp",
"_bss_start",
"_bss_end",
"_ftext",
"_edata",
"_end",
"_fbss",
}
def key(x):
name, val = x
return address - val
best_fit = sorted(filter(lambda x: x[1] <= address, sec.context.labels.items()), key=key)
best_fit = sorted(
filter(lambda x: x[1] <= address, sec.context.labels.items()), key=key
)
best = ('', float('inf'))
best = ("", float("inf"))
for name, val in best_fit:
if address - val < best[1]:
best = (name, val)
@ -178,13 +229,14 @@ class MMU:
if not name:
return "{}:{} + 0x{:x} (0x{:x})".format(
sec.owner, sec.name,
address - sec.base, address
sec.owner, sec.name, address - sec.base, address
)
return str('{}:{} at {} (0x{:0x}) + 0x{:0x}'.format(
sec.owner, sec.name, name, val, address - val
))
return str(
"{}:{} at {} (0x{:0x}) + 0x{:0x}".format(
sec.owner, sec.name, name, val, address - val
)
)
def has_continous_free_region(self, start: int, end: int) -> bool:
# if we have no sections we are all good
@ -210,13 +262,24 @@ class MMU:
def load_program(self, program: Program, align_to: int = 4):
if program.base is not None:
if not self.has_continous_free_region(program.base, program.base + program.size):
print(FMT_MEM + "Cannot load program {} into desired space (0x{:0x}-0x{:0x}), area occupied.".format(
program.name, program.base, program.base + program.size
) + FMT_NONE)
raise InvalidAllocationException("Area occupied".format(
program.name, program.base, program.base + program.size
), program.name, program.size, MemoryFlags(False, True))
if not self.has_continous_free_region(
program.base, program.base + program.size
):
print(
FMT_MEM
+ "Cannot load program {} into desired space (0x{:0x}-0x{:0x}), area occupied.".format(
program.name, program.base, program.base + program.size
)
+ FMT_NONE
)
raise InvalidAllocationException(
"Area occupied".format(
program.name, program.base, program.base + program.size
),
program.name,
program.size,
MemoryFlags(False, True),
)
at_addr = program.base
else:
@ -244,7 +307,12 @@ class MMU:
self.sections.append(sec)
self._update_state()
else:
print(FMT_MEM + '[MMU] Cannot place section {} at {}, space is occupied!'.format(sec, sec.base))
print(
FMT_MEM
+ "[MMU] Cannot place section {} at {}, space is occupied!".format(
sec, sec.base
)
)
return False
else:
at_addr = align_addr(self.get_guaranteed_free_address(), 8)
@ -269,8 +337,7 @@ class MMU:
def __repr__(self):
return "{}(\n\t{}\n)".format(
self.__class__.__name__,
"\n\t".join(repr(x) for x in self.programs)
self.__class__.__name__, "\n\t".join(repr(x) for x in self.programs)
)
def context_for(self, addr: T_AbsoluteAddress) -> InstructionContext:

@ -8,8 +8,17 @@ This package aims at providing an all-round usable RISC-V emulator and debugger
It contains everything needed to run assembly files, so you don't need any custom compilers or toolchains
"""
from .types.exceptions import RiscemuBaseException, LaunchDebuggerException, InvalidSyscallException, LinkerException, \
ParseException, NumberFormatException, InvalidRegisterException, MemoryAccessException, OutOfMemoryException
from .types.exceptions import (
RiscemuBaseException,
LaunchDebuggerException,
InvalidSyscallException,
LinkerException,
ParseException,
NumberFormatException,
InvalidRegisterException,
MemoryAccessException,
OutOfMemoryException,
)
from .instructions import *
@ -25,4 +34,4 @@ from .parser import tokenize, parse_tokens, AssemblyFileLoader
__author__ = "Anton Lydike <Anton@Lydike.com>"
__copyright__ = "Copyright 2022 Anton Lydike"
__version__ = '2.0.5'
__version__ = "2.0.5"

@ -8,7 +8,7 @@ This file holds the logic for starting the emulator from the CLI
from riscemu import RiscemuBaseException, __copyright__, __version__
from riscemu.CPU import UserModeCPU
if __name__ == '__main__':
if __name__ == "__main__":
from .config import RunConfig
from .instructions import InstructionSetDict
from .colors import FMT_BOLD, FMT_MAGENTA
@ -18,11 +18,12 @@ if __name__ == '__main__':
all_ins_names = list(InstructionSetDict.keys())
if '--version' in sys.argv:
print("riscemu version {}\n{}\n\nAvailable ISA: {}".format(
__version__, __copyright__,
", ".join(InstructionSetDict.keys())
))
if "--version" in sys.argv:
print(
"riscemu version {}\n{}\n\nAvailable ISA: {}".format(
__version__, __copyright__, ", ".join(InstructionSetDict.keys())
)
)
sys.exit()
class OptionStringAction(argparse.Action):
@ -49,56 +50,98 @@ if __name__ == '__main__':
d = {}
if not self.omit_empty:
d.update(self.keys)
for x in values.split(','):
for x in values.split(","):
if x in self.keys:
d[x] = True
else:
raise ValueError('Invalid parameter supplied: ' + x)
raise ValueError("Invalid parameter supplied: " + x)
setattr(namespace, self.dest, d)
parser = argparse.ArgumentParser(
description="RISC-V Userspace parser and emulator",
prog="riscemu",
formatter_class=argparse.RawTextHelpFormatter,
)
parser.add_argument(
"files",
metavar="file.asm",
type=str,
nargs="+",
help="The assembly files to load, the last one will be run",
)
parser = argparse.ArgumentParser(description='RISC-V Userspace parser and emulator', prog='riscemu',
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('files', metavar='file.asm', type=str, nargs='+',
help='The assembly files to load, the last one will be run')
parser.add_argument('--options', '-o', action=OptionStringAction,
keys=('disable_debug', 'no_syscall_symbols', 'fail_on_ex', 'add_accept_imm', 'unlimited_regs'),
help="""Toggle options. Available options are:
parser.add_argument(
"--options",
"-o",
action=OptionStringAction,
keys=(
"disable_debug",
"no_syscall_symbols",
"fail_on_ex",
"add_accept_imm",
"unlimited_regs",
),
help="""Toggle options. Available options are:
disable_debug: Disable ebreak instructions
no_syscall_symbols: Don't add symbols for SCALL_EXIT and others
fail_on_ex: If set, exceptions won't trigger the debugger
add_accept_imm: Accept "add rd, rs, imm" instruction (instead of addi)
unlimited_regs: Allow an unlimited number of registers""")
unlimited_regs: Allow an unlimited number of registers""",
)
parser.add_argument('--syscall-opts', '-so', action=OptionStringAction,
keys=('fs_access', 'disable_input'))
parser.add_argument(
"--syscall-opts",
"-so",
action=OptionStringAction,
keys=("fs_access", "disable_input"),
)
parser.add_argument('--instruction-sets', '-is', action=OptionStringAction,
help="Instruction sets to load, available are: {}. All are enabled by default"
.format(", ".join(all_ins_names)), keys={k: True for k in all_ins_names}, omit_empty=True)
parser.add_argument(
"--instruction-sets",
"-is",
action=OptionStringAction,
help="Instruction sets to load, available are: {}. All are enabled by default".format(
", ".join(all_ins_names)
),
keys={k: True for k in all_ins_names},
omit_empty=True,
)
parser.add_argument('--stack_size', type=int, help='Stack size of loaded programs, defaults to 8MB', nargs='?')
parser.add_argument(
"--stack_size",
type=int,
help="Stack size of loaded programs, defaults to 8MB",
nargs="?",
)
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,
)
parser.add_argument('--interactive', help="Launch the interactive debugger instantly instead of loading any "
"programs", action='store_true')
parser.add_argument(
"--interactive",
help="Launch the interactive debugger instantly instead of loading any "
"programs",
action="store_true",
)
args = parser.parse_args()
# create a RunConfig from the cli args
cfg_dict = dict(
stack_size=args.stack_size,
debug_instruction=not args.options['disable_debug'],
include_scall_symbols=not args.options['no_syscall_symbols'],
debug_on_exception=not args.options['fail_on_ex'],
add_accept_imm=args.options['add_accept_imm'],
unlimited_registers=args.options['unlimited_regs'],
scall_fs=args.syscall_opts['fs_access'],
scall_input=not args.syscall_opts['disable_input'],
verbosity=args.verbose
debug_instruction=not args.options["disable_debug"],
include_scall_symbols=not args.options["no_syscall_symbols"],
debug_on_exception=not args.options["fail_on_ex"],
add_accept_imm=args.options["add_accept_imm"],
unlimited_registers=args.options["unlimited_regs"],
scall_fs=args.syscall_opts["fs_access"],
scall_input=not args.syscall_opts["disable_input"],
verbosity=args.verbose,
)
for k, v in dict(cfg_dict).items():
if v is None:
@ -106,15 +149,13 @@ unlimited_regs: Allow an unlimited number of registers""")
cfg = RunConfig(**cfg_dict)
if not hasattr(args, 'ins'):
setattr(args, 'ins', {k: True for k in all_ins_names})
if not hasattr(args, "ins"):
setattr(args, "ins", {k: True for k in all_ins_names})
FMT_PRINT = FMT_BOLD + FMT_MAGENTA
# parse required instruction sets
ins_to_load = [
InstructionSetDict[name] for name, b in args.ins.items() if b
]
ins_to_load = [InstructionSetDict[name] for name, b in args.ins.items() if b]
try:
cpu = UserModeCPU(ins_to_load, cfg)

@ -6,10 +6,17 @@ from .colors import FMT_PARSE, FMT_NONE
from riscemu.types.exceptions import ParseException, ASSERT_LEN
from .helpers import parse_numeric_argument, align_addr, get_section_base_name
from .tokenizer import Token
from .types import Program, T_RelativeAddress, InstructionContext, Instruction, BinaryDataMemorySection, \
InstructionMemorySection, Int32
INSTRUCTION_SECTION_NAMES = ('.text', '.init', '.fini')
from .types import (
Program,
T_RelativeAddress,
InstructionContext,
Instruction,
BinaryDataMemorySection,
InstructionMemorySection,
Int32,
)
INSTRUCTION_SECTION_NAMES = (".text", ".init", ".fini")
"""
A tuple containing all section names which contain executable code (instead of data)
@ -47,8 +54,7 @@ class CurrentSection:
def __repr__(self):
return "{}(name={},data={},type={})".format(
self.__class__.__name__, self.name,
self.data, self.type.name
self.__class__.__name__, self.name, self.data, self.type.name
)
@ -72,12 +78,20 @@ class ParseContext:
if self.section.type == MemorySectionType.Data:
section = BinaryDataMemorySection(
self.section.data, self.section.name, self.context, self.program.name, self.section.base
self.section.data,
self.section.name,
self.context,
self.program.name,
self.section.base,
)
self.program.add_section(section)
elif self.section.type == MemorySectionType.Instructions:
section = InstructionMemorySection(
self.section.data, self.section.name, self.context, self.program.name, self.section.base
self.section.data,
self.section.name,
self.context,
self.program.name,
self.section.base,
)
self.program.add_section(section)
@ -89,7 +103,9 @@ class ParseContext:
self._finalize_section()
self.section = CurrentSection(name, type, base)
def add_label(self, name: str, value: int, is_global: bool = False, is_relative: bool = False):
def add_label(
self, name: str, value: int, is_global: bool = False, is_relative: bool = False
):
self.context.labels[name] = value
if is_global:
self.program.global_labels.add(name)
@ -109,10 +125,16 @@ class ParseContext:
def ASSERT_IN_SECTION_TYPE(context: ParseContext, type: MemorySectionType):
if context.section is None:
raise ParseException('Error, expected to be in {} section, but no section is present...'.format(type.name))
raise ParseException(
"Error, expected to be in {} section, but no section is present...".format(
type.name
)
)
if context.section.type != type:
raise ParseException(
'Error, expected to be in {} section, but currently in {}...'.format(type.name, context.section)
"Error, expected to be in {} section, but currently in {}...".format(
type.name, context.section
)
)
@ -174,7 +196,9 @@ class AssemblerDirectives:
cls.add_bytes(size, bytearray(size), context)
@classmethod
def add_bytes(cls, size: int, content: Union[None, int, bytearray], context: ParseContext):
def add_bytes(
cls, size: int, content: Union[None, int, bytearray], context: ParseContext
):
ASSERT_IN_SECTION_TYPE(context, MemorySectionType.Data)
if content is None:
@ -187,9 +211,9 @@ class AssemblerDirectives:
@classmethod
def add_text(cls, text: str, context: ParseContext, zero_terminate: bool = True):
# replace '\t' and '\n' escape sequences
text = text.replace('\\n', '\n').replace('\\t', '\t')
text = text.replace("\\n", "\n").replace("\\t", "\t")
encoded_bytes = bytearray(text.encode('ascii'))
encoded_bytes = bytearray(text.encode("ascii"))
if zero_terminate:
encoded_bytes += bytearray(1)
cls.add_bytes(len(encoded_bytes), encoded_bytes, context)
@ -197,24 +221,36 @@ class AssemblerDirectives:
@classmethod
def handle_instruction(cls, token: Token, args: Tuple[str], context: ParseContext):
op = token.value[1:]
if hasattr(cls, 'op_' + op):
getattr(cls, 'op_' + op)(token, args, context)
elif op in ('text', 'data', 'rodata', 'bss', 'sbss'):
if hasattr(cls, "op_" + op):
getattr(cls, "op_" + op)(token, args, context)
elif op in ("text", "data", "rodata", "bss", "sbss"):
cls.op_section(token, (token.value,), context)
elif op in ('string', 'asciiz', 'asciz', 'ascii'):
elif op in ("string", "asciiz", "asciz", "ascii"):
ASSERT_LEN(args, 1)
cls.add_text(args[0], context, op == 'ascii')
cls.add_text(args[0], context, op == "ascii")
elif op in DATA_OP_SIZES:
size = DATA_OP_SIZES[op]
for arg in args:
cls.add_bytes(size, parse_numeric_argument(arg), context)
else:
print(FMT_PARSE + "Unknown assembler directive: {} {} in {}".format(token, args, context) + FMT_NONE)
print(
FMT_PARSE
+ "Unknown assembler directive: {} {} in {}".format(
token, args, context
)
+ FMT_NONE
)
DATA_OP_SIZES = {
'byte': 1,
'2byte': 2, 'half': 2, 'short': 2,
'4byte': 4, 'word': 4, 'long': 4,
'8byte': 8, 'dword': 8, 'quad': 8,
"byte": 1,
"2byte": 2,
"half": 2,
"short": 2,
"4byte": 4,
"word": 4,
"long": 4,
"8byte": 8,
"dword": 8,
"quad": 8,
}

@ -6,18 +6,18 @@ SPDX-License-Identifier: MIT
# Colors
FMT_RED = '\033[31m'
FMT_ORANGE = '\033[33m'
FMT_GRAY = '\033[37m'
FMT_CYAN = '\033[36m'
FMT_GREEN = '\033[32m'
FMT_MAGENTA = '\033[35m'
FMT_BLUE = '\033[34m'
FMT_YELLOW = '\033[93m'
FMT_RED = "\033[31m"
FMT_ORANGE = "\033[33m"
FMT_GRAY = "\033[37m"
FMT_CYAN = "\033[36m"
FMT_GREEN = "\033[32m"
FMT_MAGENTA = "\033[35m"
FMT_BLUE = "\033[34m"
FMT_YELLOW = "\033[93m"
FMT_BOLD = '\033[1m'
FMT_NONE = '\033[0m'
FMT_UNDERLINE = '\033[4m'
FMT_BOLD = "\033[1m"
FMT_NONE = "\033[0m"
FMT_UNDERLINE = "\033[4m"
FMT_ERROR = FMT_RED + FMT_BOLD
@ -26,4 +26,4 @@ FMT_PARSE = FMT_CYAN + FMT_BOLD
FMT_CPU = FMT_BLUE + FMT_BOLD
FMT_SYSCALL = FMT_YELLOW + FMT_BOLD
FMT_DEBUG = FMT_MAGENTA + FMT_BOLD
FMT_CSR = FMT_ORANGE + FMT_BOLD
FMT_CSR = FMT_ORANGE + FMT_BOLD

@ -11,10 +11,10 @@ from .helpers import *
if typing.TYPE_CHECKING:
from riscemu import CPU, Registers
HIST_FILE = os.path.join(os.path.expanduser('~'), '.riscemu_history')
HIST_FILE = os.path.join(os.path.expanduser("~"), ".riscemu_history")
def launch_debug_session(cpu: 'CPU', prompt=""):
def launch_debug_session(cpu: "CPU", prompt=""):
if cpu.debugger_active:
return
import code
@ -39,7 +39,7 @@ def launch_debug_session(cpu: 'CPU', prompt=""):
mmu.dump(what, *args, **kwargs)
def dump_stack(*args, **kwargs):
mmu.dump(regs.get('sp'), *args, **kwargs)
mmu.dump(regs.get("sp"), *args, **kwargs)
def ins():
print("Current instruction at 0x{:08X}:".format(cpu.pc))
@ -51,11 +51,7 @@ def launch_debug_session(cpu: 'CPU', prompt=""):
return
context = mmu.context_for(cpu.pc)
ins = SimpleInstruction(
name,
tuple(args),
context,
cpu.pc)
ins = SimpleInstruction(name, tuple(args), context, cpu.pc)
print(FMT_DEBUG + "Running instruction {}".format(ins) + FMT_NONE)
cpu.run_instruction(ins)
@ -63,7 +59,7 @@ def launch_debug_session(cpu: 'CPU', prompt=""):
try:
cpu.run(verbose)
except LaunchDebuggerException:
print(FMT_DEBUG + 'Returning to debugger...')
print(FMT_DEBUG + "Returning to debugger...")
return
def step():
@ -92,4 +88,3 @@ def launch_debug_session(cpu: 'CPU', prompt=""):
finally:
cpu.debugger_active = False
readline.write_history_file(HIST_FILE)

@ -1,2 +1,2 @@
from .decoder import decode, RISCV_REGS
from .formatter import format_ins
from .formatter import format_ins

@ -1,4 +1,4 @@
if __name__ == '__main__':
if __name__ == "__main__":
import code
import readline
import rlcompleter
@ -14,4 +14,6 @@ if __name__ == '__main__':
readline.set_completer(rlcompleter.Completer(sess_vars).complete)
readline.set_completer(rlcompleter.Completer(sess_vars).complete)
readline.parse_and_bind("tab: complete")
code.InteractiveConsole(sess_vars).interact(banner="Interaktive decoding session started...", exitmsg="Closing...")
code.InteractiveConsole(sess_vars).interact(
banner="Interaktive decoding session started...", exitmsg="Closing..."
)

@ -5,13 +5,14 @@ from typing import Tuple, List
def print_ins(ins: int):
print(" f7 rs2 rs1 f3 rd op")
print(
f"0b{ins >> 25 :07b}_{(ins >> 20) & 0b11111:05b}_{(ins >> 15) & 0b11111:05b}_{(ins >> 12) & 0b111:03b}_{(ins >> 7) & 0b11111:05b}_{ins & 0b1111111:07b}");
f"0b{ins >> 25 :07b}_{(ins >> 20) & 0b11111:05b}_{(ins >> 15) & 0b11111:05b}_{(ins >> 12) & 0b111:03b}_{(ins >> 7) & 0b11111:05b}_{ins & 0b1111111:07b}"
)
STATIC_INSN: Dict[int, Tuple[str, List[int], int]] = {
0x00000013: ("nop", [], 0x00000013),
0x00008067: ("ret", [], 0x00008067),
0xfe010113: ("addi", [2, 2, -32], 0xfe010113),
0xFE010113: ("addi", [2, 2, -32], 0xFE010113),
0x02010113: ("addi", [2, 2, 32], 0x02010113),
0x00100073: ("ebreak", [], 0x00100073),
0x00000073: ("ecall", [], 0x00000073),
@ -23,7 +24,7 @@ STATIC_INSN: Dict[int, Tuple[str, List[int], int]] = {
def int_from_ins(insn: bytearray):
return int.from_bytes(insn, 'little')
return int.from_bytes(insn, "little")
def name_from_insn(ins: int):
@ -45,7 +46,7 @@ def name_from_insn(ins: int):
if isinstance(dec, str):
return dec
if opcode == 0x1c and fun3 == 0:
if opcode == 0x1C and fun3 == 0:
# we have ecall/ebreak
token = imm110(ins)
if token in dec:

@ -1,6 +1,7 @@
from typing import Dict, Callable, List, Union
from .regs import RISCV_REGS
def op(ins: int):
return (ins >> 2) & 31
@ -46,7 +47,12 @@ def imm_b(ins: int):
lower = rd(ins)
higher = funct7(ins)
num = (lower & 0b11110) + ((higher & 0b0111111) << 5) + ((lower & 1) << 11) + ((higher >> 6) << 12)
num = (
(lower & 0b11110)
+ ((higher & 0b0111111) << 5)
+ ((lower & 1) << 11)
+ ((higher >> 6) << 12)
)
return sign_extend(num, 13)
@ -56,10 +62,11 @@ def imm_u(ins: int):
def imm_j(ins: int):
return sign_extend(
(((ins >> 21) & 0b1111111111) << 1) +
(((ins >> 20) & 1) << 11) +
(((ins >> 12) & 0b11111111) << 12) +
(((ins >> 31) & 1) << 20), 21
(((ins >> 21) & 0b1111111111) << 1)
+ (((ins >> 20) & 1) << 11)
+ (((ins >> 12) & 0b11111111) << 12)
+ (((ins >> 31) & 1) << 20),
21,
)
@ -111,7 +118,7 @@ INSTRUCTION_ARGS_DECODER: Dict[int, Callable[[int], List[int]]] = {
0x0D: decode_u,
0x18: decode_b,
0x19: decode_i,
0x1b: decode_j,
0x1c: decode_i_unsigned,
0b1011: decode_r
0x1B: decode_j,
0x1C: decode_i_unsigned,
0b1011: decode_r,
}

@ -1,5 +1,15 @@
from .formats import INSTRUCTION_ARGS_DECODER, op, decode_i, decode_r, decode_u, decode_b, decode_j, decode_s, \
decode_i_shamt, decode_i_unsigned
from .formats import (
INSTRUCTION_ARGS_DECODER,
op,
decode_i,
decode_r,
decode_u,
decode_b,
decode_j,
decode_s,
decode_i_shamt,
decode_i_unsigned,
)
from .regs import RISCV_REGS
@ -9,9 +19,9 @@ def int_to_hex(num: int):
return f"0x{num:x}"
def format_ins(ins: int, name: str, fmt: str = 'int'):
def format_ins(ins: int, name: str, fmt: str = "int"):
opcode = op(ins)
if fmt == 'hex':
if fmt == "hex":
fmt = int_to_hex
else:
fmt = str
@ -20,7 +30,7 @@ def format_ins(ins: int, name: str, fmt: str = 'int'):
return f"{name} <unknown op>"
decoder = INSTRUCTION_ARGS_DECODER[opcode]
if name in ('ecall', 'ebreak', 'mret', 'sret', 'uret'):
if name in ("ecall", "ebreak", "mret", "sret", "uret"):
return name
if opcode in (0x8, 0x0):
r1, r2, imm = decoder(ins)

@ -4,7 +4,7 @@ from .formats import *
tbl = lambda: defaultdict(tbl)
RV32 = tbl()
RV32[0x1b] = "jal"
RV32[0x1B] = "jal"
RV32[0x0D] = "lui"
RV32[0x05] = "auipc"
RV32[0x19][0] = "jalr"
@ -36,26 +36,26 @@ RV32[0x08][0] = "sb"
RV32[0x08][1] = "sh"
RV32[0x08][2] = "sw"
RV32[0x1c][1] = "csrrw"
RV32[0x1c][2] = "csrrs"
RV32[0x1c][3] = "csrrc"
RV32[0x1c][5] = "csrrwi"
RV32[0x1c][6] = "csrrsi"
RV32[0x1c][7] = "csrrci"
RV32[0x1C][1] = "csrrw"
RV32[0x1C][2] = "csrrs"
RV32[0x1C][3] = "csrrc"
RV32[0x1C][5] = "csrrwi"
RV32[0x1C][6] = "csrrsi"
RV32[0x1C][7] = "csrrci"
RV32[0x1c][0][0] = "ecall"
RV32[0x1c][0][1] = "ebreak"
RV32[0x1C][0][0] = "ecall"
RV32[0x1C][0][1] = "ebreak"
RV32[0x0C][0][0] = "add"
RV32[0x0C][0][0] = "add"
RV32[0x0C][0][32] = "sub"
RV32[0x0C][1][0] = "sll"
RV32[0x0C][2][0] = "slt"
RV32[0x0C][3][0] = "sltu"
RV32[0x0C][4][0] = "xor"
RV32[0x0C][5][0] = "srl"
RV32[0x0C][1][0] = "sll"
RV32[0x0C][2][0] = "slt"
RV32[0x0C][3][0] = "sltu"
RV32[0x0C][4][0] = "xor"
RV32[0x0C][5][0] = "srl"
RV32[0x0C][5][32] = "sra"
RV32[0x0C][6][0] = "or"
RV32[0x0C][7][0] = "and"
RV32[0x0C][6][0] = "or"
RV32[0x0C][7][0] = "and"
# rv32m
RV32[0x0C][0][1] = "mul"

@ -1,6 +1,34 @@
RISCV_REGS = [
'zero', 'ra', 'sp', 'gp', 'tp', 't0', 't1', 't2',
's0', 's1', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7',
's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11',
't3', 't4', 't5', 't6'
"zero",
"ra",
"sp",
"gp",
"tp",
"t0",
"t1",
"t2",
"s0",
"s1",
"a0",
"a1",
"a2",
"a3",
"a4",
"a5",
"a6",
"a7",
"s2",
"s3",
"s4",
"s5",
"s6",
"s7",
"s8",
"s9",
"s10",
"s11",
"t3",
"t4",
"t5",
"t6",
]

@ -25,16 +25,19 @@ def parse_numeric_argument(arg: str) -> int:
parse hex or int strings
"""
try:
if arg.lower().startswith('0x'):
if arg.lower().startswith("0x"):
return int(arg, 16)
return int(arg)
except ValueError as ex:
raise ParseException('Invalid immediate argument \"{}\", maybe missing symbol?'.format(arg), (arg, ex))
raise ParseException(
'Invalid immediate argument "{}", maybe missing symbol?'.format(arg),
(arg, ex),
)
def create_chunks(my_list, chunk_size):
"""Split a list like [a,b,c,d,e,f,g,h,i,j,k,l,m] into e.g. [[a,b,c,d],[e,f,g,h],[i,j,k,l],[m]]"""
return [my_list[i:i + chunk_size] for i in range(0, len(my_list), chunk_size)]
return [my_list[i : i + chunk_size] for i in range(0, len(my_list), chunk_size)]
def apply_highlight(item, ind, hi_ind):
@ -47,26 +50,31 @@ def apply_highlight(item, ind, hi_ind):
def highlight_in_list(items, hi_ind, joiner=" "):
return joiner.join([apply_highlight(item, i, hi_ind) for i, item in enumerate(items)])
return joiner.join(
[apply_highlight(item, i, hi_ind) for i, item in enumerate(items)]
)
def format_bytes(byte_arr: bytearray, fmt: str, group: int = 1, highlight: int = -1):
"""Format byte array as per fmt. Group into groups of size `group`, and highlight index `highlight`."""
chunks = create_chunks(byte_arr, group)
if fmt == 'hex':
return highlight_in_list(['0x{}'.format(ch.hex()) for ch in chunks], highlight)
if fmt == 'int':
if fmt == "hex":
return highlight_in_list(["0x{}".format(ch.hex()) for ch in chunks], highlight)
if fmt == "int":
spc = str(ceil(log10(2 ** (group * 8 - 1))) + 1)
return highlight_in_list([('{:0' + spc + 'd}').format(Int32(ch)) for ch in chunks], highlight)
if fmt == 'uint':
return highlight_in_list(
[("{:0" + spc + "d}").format(Int32(ch)) for ch in chunks], highlight
)
if fmt == "uint":
spc = str(ceil(log10(2 ** (group * 8))))
return highlight_in_list([('{:0' + spc + 'd}').format(UInt32(ch)) for ch in chunks],
highlight)
if fmt == 'char':
return highlight_in_list(
[("{:0" + spc + "d}").format(UInt32(ch)) for ch in chunks], highlight
)
if fmt == "char":
return highlight_in_list((repr(chr(b))[1:-1] for b in byte_arr), highlight, "")
T = TypeVar('T')
T = TypeVar("T")
class Peekable(Generic[T], Iterator[T]):
@ -100,6 +108,10 @@ class Peekable(Generic[T], Iterator[T]):
def get_section_base_name(section_name: str) -> str:
if '.' not in section_name:
print(FMT_PARSE + f"Invalid section {section_name}, not starting with a dot!" + FMT_NONE)
return '.' + section_name.split('.')[1]
if "." not in section_name:
print(
FMT_PARSE
+ f"Invalid section {section_name}, not starting with a dot!"
+ FMT_NONE
)
return "." + section_name.split(".")[1]

@ -10,52 +10,52 @@ class RV32A(InstructionSet):
for this?
"""
def instruction_lr_w(self, ins: 'Instruction'):
def instruction_lr_w(self, ins: "Instruction"):
INS_NOT_IMPLEMENTED(ins)
def instruction_sc_w(self, ins: 'Instruction'):
def instruction_sc_w(self, ins: "Instruction"):
INS_NOT_IMPLEMENTED(ins)
def instruction_amoswap_w(self, ins: 'Instruction'):
def instruction_amoswap_w(self, ins: "Instruction"):
dest, addr, val = self.parse_rd_rs_rs(ins)
if dest == 'zero':
if dest == "zero":
self.mmu.write(addr, val.to_bytes())
else:
old = Int32(self.mmu.read(addr, 4))
self.mmu.write(addr, val.to_bytes())
self.regs.set(dest, old)
def instruction_amoadd_w(self, ins: 'Instruction'):
def instruction_amoadd_w(self, ins: "Instruction"):
dest, addr, val = self.parse_rd_rs_rs(ins)
old = Int32(self.mmu.read(addr, 4))
self.mmu.write(addr, (old + val).to_bytes(4))
self.regs.set(dest, old)
def instruction_amoand_w(self, ins: 'Instruction'):
def instruction_amoand_w(self, ins: "Instruction"):
dest, addr, val = self.parse_rd_rs_rs(ins)
old = Int32(self.mmu.read(addr, 4))
self.mmu.write(addr, (old & val).to_bytes(4))
self.regs.set(dest, old)
def instruction_amoor_w(self, ins: 'Instruction'):
def instruction_amoor_w(self, ins: "Instruction"):
dest, addr, val = self.parse_rd_rs_rs(ins)
old = Int32(self.mmu.read(addr, 4))
self.mmu.write(addr, (old | val).to_bytes(4))
self.regs.set(dest, old)
def instruction_amoxor_w(self, ins: 'Instruction'):
def instruction_amoxor_w(self, ins: "Instruction"):
dest, addr, val = self.parse_rd_rs_rs(ins)
old = Int32(self.mmu.read(addr, 4))
self.mmu.write(addr, (old ^ val).to_bytes(4))
self.regs.set(dest, old)
def instruction_amomax_w(self, ins: 'Instruction'):
def instruction_amomax_w(self, ins: "Instruction"):
dest, addr, val = self.parse_rd_rs_rs(ins)
old = Int32(self.mmu.read(addr, 4))
self.mmu.write(addr, max(old, val).to_bytes(4))
self.regs.set(dest, old)
def instruction_amomaxu_w(self, ins: 'Instruction'):
def instruction_amomaxu_w(self, ins: "Instruction"):
val: UInt32
dest, addr, val = self.parse_rd_rs_rs(ins, signed=False)
old = UInt32(self.mmu.read(addr, 4))
@ -63,13 +63,13 @@ class RV32A(InstructionSet):
self.mmu.write(addr, max(old, val).to_bytes())
self.regs.set(dest, old)
def instruction_amomin_w(self, ins: 'Instruction'):
def instruction_amomin_w(self, ins: "Instruction"):
dest, addr, val = self.parse_rd_rs_rs(ins)
old = Int32(self.mmu.read(addr, 4))
self.mmu.write(addr, min(old, val).to_bytes(4))
self.regs.set(dest, old)
def instruction_amominu_w(self, ins: 'Instruction'):
def instruction_amominu_w(self, ins: "Instruction"):
val: UInt32
dest, addr, val = self.parse_rd_rs_rs(ins, signed=False)
old = UInt32(self.mmu.read(addr, 4))

@ -22,241 +22,186 @@ class RV32I(InstructionSet):
See https://maxvytech.com/images/RV32I-11-2018.pdf for a more detailed overview
"""
def instruction_lb(self, ins: 'Instruction'):
def instruction_lb(self, ins: "Instruction"):
rd, addr = self.parse_mem_ins(ins)
self.regs.set(rd, Int32.sign_extend(self.mmu.read(addr.unsigned_value, 1), 8))
def instruction_lh(self, ins: 'Instruction'):
def instruction_lh(self, ins: "Instruction"):
rd, addr = self.parse_mem_ins(ins)
self.regs.set(rd, Int32.sign_extend(self.mmu.read(addr.unsigned_value, 2), 16))
def instruction_lw(self, ins: 'Instruction'):
def instruction_lw(self, ins: "Instruction"):
rd, addr = self.parse_mem_ins(ins)
self.regs.set(rd, Int32(self.mmu.read(addr.unsigned_value, 4)))
def instruction_lbu(self, ins: 'Instruction'):
def instruction_lbu(self, ins: "Instruction"):
rd, addr = self.parse_mem_ins(ins)
self.regs.set(rd, Int32(self.mmu.read(addr.unsigned_value, 1)))
def instruction_lhu(self, ins: 'Instruction'):
def instruction_lhu(self, ins: "Instruction"):
rd, addr = self.parse_mem_ins(ins)
self.regs.set(rd, Int32(self.mmu.read(addr.unsigned_value, 2)))
def instruction_sb(self, ins: 'Instruction'):
def instruction_sb(self, ins: "Instruction"):
rd, addr = self.parse_mem_ins(ins)
self.mmu.write(addr.unsigned_value, 1, self.regs.get(rd).to_bytes(1))
def instruction_sh(self, ins: 'Instruction'):
def instruction_sh(self, ins: "Instruction"):
rd, addr = self.parse_mem_ins(ins)
self.mmu.write(addr.unsigned_value, 2, self.regs.get(rd).to_bytes(2))
def instruction_sw(self, ins: 'Instruction'):
def instruction_sw(self, ins: "Instruction"):
rd, addr = self.parse_mem_ins(ins)
self.mmu.write(addr.unsigned_value, 4, self.regs.get(rd).to_bytes(4))
def instruction_sll(self, ins: 'Instruction'):
def instruction_sll(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 3)
dst = ins.get_reg(0)
src1 = ins.get_reg(1)
src2 = ins.get_reg(2)
self.regs.set(
dst,
self.regs.get(src1) << (self.regs.get(src2) & 0b11111)
)
self.regs.set(dst, self.regs.get(src1) << (self.regs.get(src2) & 0b11111))
def instruction_slli(self, ins: 'Instruction'):
def instruction_slli(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 3)
dst = ins.get_reg(0)
src1 = ins.get_reg(1)
imm = ins.get_imm(2)
self.regs.set(
dst,
self.regs.get(src1) << (imm & 0b11111)
)
self.regs.set(dst, self.regs.get(src1) << (imm & 0b11111))
def instruction_srl(self, ins: 'Instruction'):
def instruction_srl(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 3)
dst = ins.get_reg(0)
src1 = ins.get_reg(1)
src2 = ins.get_reg(2)
self.regs.set(
dst,
self.regs.get(src1).shift_right_logical(self.regs.get(src2) & 0b11111)
dst, self.regs.get(src1).shift_right_logical(self.regs.get(src2) & 0b11111)
)
def instruction_srli(self, ins: 'Instruction'):
def instruction_srli(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 3)
dst = ins.get_reg(0)
src1 = ins.get_reg(1)
imm = ins.get_imm(2)
self.regs.set(
dst,
self.regs.get(src1).shift_right_logical(imm & 0b11111)
)
self.regs.set(dst, self.regs.get(src1).shift_right_logical(imm & 0b11111))
def instruction_sra(self, ins: 'Instruction'):
def instruction_sra(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 3)
dst = ins.get_reg(0)
src1 = ins.get_reg(1)
src2 = ins.get_reg(2)
self.regs.set(
dst,
self.regs.get(src1) >> (self.regs.get(src2) & 0b11111)
)
self.regs.set(dst, self.regs.get(src1) >> (self.regs.get(src2) & 0b11111))
def instruction_srai(self, ins: 'Instruction'):
def instruction_srai(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 3)
dst = ins.get_reg(0)
src1 = ins.get_reg(1)
imm = ins.get_imm(2)
self.regs.set(
dst,
self.regs.get(src1) >> (imm & 0b11111)
)
self.regs.set(dst, self.regs.get(src1) >> (imm & 0b11111))
def instruction_add(self, ins: 'Instruction'):
def instruction_add(self, ins: "Instruction"):
# FIXME: once configuration is figured out, add flag to support immediate arg in add instruction
dst, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(
dst,
rs1 + rs2
)
self.regs.set(dst, rs1 + rs2)
def instruction_addi(self, ins: 'Instruction'):
def instruction_addi(self, ins: "Instruction"):
dst, rs1, imm = self.parse_rd_rs_imm(ins)
self.regs.set(
dst,
rs1 + imm
)
self.regs.set(dst, rs1 + imm)
def instruction_sub(self, ins: 'Instruction'):
def instruction_sub(self, ins: "Instruction"):
dst, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(
dst,
rs1 - rs2
)
self.regs.set(dst, rs1 - rs2)
def instruction_lui(self, ins: 'Instruction'):
def instruction_lui(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 2)
reg = ins.get_reg(0)
imm = UInt32(ins.get_imm(1) << 12)
self.regs.set(reg, Int32(imm))
def instruction_auipc(self, ins: 'Instruction'):
def instruction_auipc(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 2)
reg = ins.get_reg(0)
imm = UInt32(ins.get_imm(1) << 12)
self.regs.set(reg, imm.signed() + self.pc)
def instruction_xor(self, ins: 'Instruction'):
def instruction_xor(self, ins: "Instruction"):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(
rd,
rs1 ^ rs2
)
self.regs.set(rd, rs1 ^ rs2)
def instruction_xori(self, ins: 'Instruction'):
def instruction_xori(self, ins: "Instruction"):
rd, rs1, imm = self.parse_rd_rs_imm(ins)
self.regs.set(
rd,
rs1 ^ imm
)
self.regs.set(rd, rs1 ^ imm)
def instruction_or(self, ins: 'Instruction'):
def instruction_or(self, ins: "Instruction"):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(
rd,
rs1 | rs2
)
self.regs.set(rd, rs1 | rs2)
def instruction_ori(self, ins: 'Instruction'):
def instruction_ori(self, ins: "Instruction"):
rd, rs1, imm = self.parse_rd_rs_imm(ins)
self.regs.set(
rd,
rs1 | imm
)
self.regs.set(rd, rs1 | imm)
def instruction_and(self, ins: 'Instruction'):
def instruction_and(self, ins: "Instruction"):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(
rd,
rs1 & rs2
)
self.regs.set(rd, rs1 & rs2)
def instruction_andi(self, ins: 'Instruction'):
def instruction_andi(self, ins: "Instruction"):
rd, rs1, imm = self.parse_rd_rs_imm(ins)
self.regs.set(
rd,
rs1 & imm
)
self.regs.set(rd, rs1 & imm)
def instruction_slt(self, ins: 'Instruction'):
def instruction_slt(self, ins: "Instruction"):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(
rd,
Int32(int(rs1 < rs2))
)
self.regs.set(rd, Int32(int(rs1 < rs2)))
def instruction_slti(self, ins: 'Instruction'):
def instruction_slti(self, ins: "Instruction"):
rd, rs1, imm = self.parse_rd_rs_imm(ins)
self.regs.set(
rd,
Int32(int(rs1 < imm))
)
self.regs.set(rd, Int32(int(rs1 < imm)))
def instruction_sltu(self, ins: 'Instruction'):
def instruction_sltu(self, ins: "Instruction"):
dst, rs1, rs2 = self.parse_rd_rs_rs(ins, signed=False)
self.regs.set(
dst,
Int32(int(rs1 < rs2))
)
self.regs.set(dst, Int32(int(rs1 < rs2)))
def instruction_sltiu(self, ins: 'Instruction'):
def instruction_sltiu(self, ins: "Instruction"):
dst, rs1, imm = self.parse_rd_rs_imm(ins, signed=False)
self.regs.set(
dst,
Int32(int(rs1 < imm))
)
self.regs.set(dst, Int32(int(rs1 < imm)))
def instruction_beq(self, ins: 'Instruction'):
def instruction_beq(self, ins: "Instruction"):
rs1, rs2, dst = self.parse_rs_rs_imm(ins)
if rs1 == rs2:
self.pc = dst.unsigned_value
def instruction_bne(self, ins: 'Instruction'):
def instruction_bne(self, ins: "Instruction"):
rs1, rs2, dst = self.parse_rs_rs_imm(ins)
if rs1 != rs2:
self.pc = dst.unsigned_value
def instruction_blt(self, ins: 'Instruction'):
def instruction_blt(self, ins: "Instruction"):
rs1, rs2, dst = self.parse_rs_rs_imm(ins)
if rs1 < rs2:
self.pc = dst.unsigned_value
def instruction_bge(self, ins: 'Instruction'):
def instruction_bge(self, ins: "Instruction"):
rs1, rs2, dst = self.parse_rs_rs_imm(ins)
if rs1 >= rs2:
self.pc = dst.unsigned_value
def instruction_bltu(self, ins: 'Instruction'):
def instruction_bltu(self, ins: "Instruction"):
rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False)
if rs1 < rs2:
self.pc = dst.unsigned_value
def instruction_bgeu(self, ins: 'Instruction'):
def instruction_bgeu(self, ins: "Instruction"):
rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False)
if rs1 >= rs2:
self.pc = dst.unsigned_value
# technically deprecated
def instruction_j(self, ins: 'Instruction'):
def instruction_j(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 1)
addr = ins.get_imm(0)
self.pc = addr
def instruction_jal(self, ins: 'Instruction'):
reg = 'ra' # default register is ra
def instruction_jal(self, ins: "Instruction"):
reg = "ra" # default register is ra
if len(ins.args) == 1:
addr = ins.get_imm(0)
else:
@ -266,58 +211,60 @@ class RV32I(InstructionSet):
self.regs.set(reg, Int32(self.pc))
self.pc = addr
def instruction_jalr(self, ins: 'Instruction'):
def instruction_jalr(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 2)
reg = ins.get_reg(0)
addr = ins.get_imm(1)
self.regs.set(reg, Int32(self.pc))
self.pc = addr
def instruction_ret(self, ins: 'Instruction'):
def instruction_ret(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 0)
self.pc = self.regs.get('ra').value
self.pc = self.regs.get("ra").value
def instruction_ecall(self, ins: 'Instruction'):
def instruction_ecall(self, ins: "Instruction"):
self.instruction_scall(ins)
def instruction_ebreak(self, ins: 'Instruction'):
def instruction_ebreak(self, ins: "Instruction"):
self.instruction_sbreak(ins)
def instruction_scall(self, ins: 'Instruction'):
def instruction_scall(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 0)
if not isinstance(self.cpu, UserModeCPU):
# FIXME: add exception for syscall not supported or something
raise
syscall = Syscall(self.regs.get('a7'), self.cpu)
syscall = Syscall(self.regs.get("a7"), self.cpu)
self.cpu.syscall_int.handle_syscall(syscall)
def instruction_sbreak(self, ins: 'Instruction'):
def instruction_sbreak(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 0)
if self.cpu.conf.debug_instruction:
print(FMT_DEBUG + "Debug instruction encountered at 0x{:08X}".format(self.pc - 1) + FMT_NONE)
print(
FMT_DEBUG
+ "Debug instruction encountered at 0x{:08X}".format(self.pc - 1)
+ FMT_NONE
)
raise LaunchDebuggerException()
def instruction_nop(self, ins: 'Instruction'):
def instruction_nop(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 0)
pass
def instruction_li(self, ins: 'Instruction'):
def instruction_li(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 2)
reg = ins.get_reg(0)
immediate = ins.get_imm(1)
self.regs.set(reg, Int32(immediate))
def instruction_la(self, ins: 'Instruction'):
def instruction_la(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 2)
reg = ins.get_reg(0)
immediate = ins.get_imm(1)
self.regs.set(reg, Int32(immediate))
def instruction_mv(self, ins: 'Instruction'):
def instruction_mv(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 2)
rd, rs = ins.get_reg(0), ins.get_reg(1)
self.regs.set(rd, self.regs.get(rs))

@ -12,50 +12,33 @@ class RV32M(InstructionSet):
"""
The RV32M Instruction set, containing multiplication and division instructions
"""
def instruction_mul(self, ins: 'Instruction'):
def instruction_mul(self, ins: "Instruction"):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(
rd,
rs1 * rs2
)
self.regs.set(rd, rs1 * rs2)
def instruction_mulh(self, ins: 'Instruction'):
def instruction_mulh(self, ins: "Instruction"):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(
rd,
(rs1 * rs2) >> 32
)
self.regs.set(rd, (rs1 * rs2) >> 32)
def instruction_mulhsu(self, ins: 'Instruction'):
def instruction_mulhsu(self, ins: "Instruction"):
INS_NOT_IMPLEMENTED(ins)
def instruction_mulhu(self, ins: 'Instruction'):
def instruction_mulhu(self, ins: "Instruction"):
INS_NOT_IMPLEMENTED(ins)
def instruction_div(self, ins: 'Instruction'):
def instruction_div(self, ins: "Instruction"):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(
rd,
rs1 // rs2
)
self.regs.set(rd, rs1 // rs2)
def instruction_divu(self, ins: 'Instruction'):
def instruction_divu(self, ins: "Instruction"):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins, signed=False)
self.regs.set(
rd,
rs1 // rs2
)
self.regs.set(rd, rs1 // rs2)
def instruction_rem(self, ins: 'Instruction'):
def instruction_rem(self, ins: "Instruction"):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(
rd,
rs1 % rs2
)
self.regs.set(rd, rs1 % rs2)
def instruction_remu(self, ins: 'Instruction'):
def instruction_remu(self, ins: "Instruction"):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins, signed=False)
self.regs.set(
rd,
rs1 % rs2
)
self.regs.set(rd, rs1 % rs2)

@ -8,7 +8,11 @@ class RV_Debug(InstructionSet):
def instruction_print_uint(self, ins: Instruction):
reg = ins.get_reg(0)
print("register {} contains value {}".format(reg, self.regs.get(reg).unsigned_value))
print(
"register {} contains value {}".format(
reg, self.regs.get(reg).unsigned_value
)
)
def instruction_print_hex(self, ins: Instruction):
reg = ins.get_reg(0)
@ -16,4 +20,8 @@ class RV_Debug(InstructionSet):
def instruction_print_uhex(self, ins: Instruction):
reg = ins.get_reg(0)
print("register {} contains value {}".format(reg, hex(self.regs.get(reg).unsigned_value)))
print(
"register {} contains value {}".format(
reg, hex(self.regs.get(reg).unsigned_value)
)
)

@ -12,6 +12,4 @@ from .RV32I import RV32I
from .RV32A import RV32A
from .RV_Debug import RV_Debug
InstructionSetDict = {
v.__name__: v for v in [RV32I, RV32M, RV32A, RV_Debug]
}
InstructionSetDict = {v.__name__: v for v in [RV32I, RV32M, RV32A, RV_Debug]}

@ -23,23 +23,21 @@ class InstructionSet(ABC):
instructions containing a dot '.' should replace it with an underscore.
"""
def __init__(self, cpu: 'CPU'):
def __init__(self, cpu: "CPU"):
"""Create a new instance of the Instruction set. This requires access to a CPU, and grabs vertain things
from it such as access to the MMU and registers.
"""
self.name = self.__class__.__name__
self.cpu = cpu
def load(self) -> Dict[str, Callable[['Instruction'], None]]:
def load(self) -> Dict[str, Callable[["Instruction"], None]]:
"""
This is called by the CPU once it instantiates this instruction set
It returns a dictionary of all instructions in this instruction set,
pointing to the correct handler for it
"""
return {
name: ins for name, ins in self.get_instructions()
}
return {name: ins for name, ins in self.get_instructions()}
def get_instructions(self):
"""
@ -48,10 +46,10 @@ class InstructionSet(ABC):
converts underscores in names to dots
"""
for member in dir(self):
if member.startswith('instruction_'):
yield member[12:].replace('_', '.'), getattr(self, member)
if member.startswith("instruction_"):
yield member[12:].replace("_", "."), getattr(self, member)
def parse_mem_ins(self, ins: 'Instruction') -> Tuple[str, Int32]:
def parse_mem_ins(self, ins: "Instruction") -> Tuple[str, Int32]:
"""
parses both rd, rs, imm and rd, imm(rs) argument format and returns (rd, imm+rs1)
(so a register and address tuple for memory instructions)
@ -69,51 +67,69 @@ class InstructionSet(ABC):
rd = ins.get_reg(0)
return rd, rs + imm
def parse_rd_rs_rs(self, ins: 'Instruction', signed=True) -> Tuple[str, Int32, Int32]:
def parse_rd_rs_rs(
self, ins: "Instruction", signed=True
) -> Tuple[str, Int32, Int32]:
"""
Assumes the command is in <name> rd, rs1, rs2 format
Returns the name of rd, and the values in rs1 and rs2
"""
ASSERT_LEN(ins.args, 3)
if signed:
return ins.get_reg(0), \
Int32(self.get_reg_content(ins, 1)), \
Int32(self.get_reg_content(ins, 2))
return (
ins.get_reg(0),
Int32(self.get_reg_content(ins, 1)),
Int32(self.get_reg_content(ins, 2)),
)
else:
return ins.get_reg(0), \
UInt32(self.get_reg_content(ins, 1)), \
UInt32(self.get_reg_content(ins, 2))
return (
ins.get_reg(0),
UInt32(self.get_reg_content(ins, 1)),
UInt32(self.get_reg_content(ins, 2)),
)
def parse_rd_rs_imm(self, ins: 'Instruction', signed=True) -> Tuple[str, Int32, Int32]:
def parse_rd_rs_imm(
self, ins: "Instruction", signed=True
) -> Tuple[str, Int32, Int32]:
"""
Assumes the command is in <name> rd, rs, imm format
Returns the name of rd, the value in rs and the immediate imm
"""
ASSERT_LEN(ins.args, 3)
if signed:
return ins.get_reg(0), \
Int32(self.get_reg_content(ins, 1)), \
Int32(ins.get_imm(2))
return (
ins.get_reg(0),
Int32(self.get_reg_content(ins, 1)),
Int32(ins.get_imm(2)),
)
else:
return ins.get_reg(0), \
UInt32(self.get_reg_content(ins, 1)), \
UInt32(ins.get_imm(2))
return (
ins.get_reg(0),
UInt32(self.get_reg_content(ins, 1)),
UInt32(ins.get_imm(2)),
)
def parse_rs_rs_imm(self, ins: 'Instruction', signed=True) -> Tuple[Int32, Int32, Int32]:
def parse_rs_rs_imm(
self, ins: "Instruction", signed=True
) -> Tuple[Int32, Int32, Int32]:
"""
Assumes the command is in <name> rs1, rs2, imm format
Returns the values in rs1, rs2 and the immediate imm
"""
if signed:
return Int32(self.get_reg_content(ins, 0)), \
Int32(self.get_reg_content(ins, 1)), \
Int32(ins.get_imm(2))
return (
Int32(self.get_reg_content(ins, 0)),
Int32(self.get_reg_content(ins, 1)),
Int32(ins.get_imm(2)),
)
else:
return UInt32(self.get_reg_content(ins, 0)), \
UInt32(self.get_reg_content(ins, 1)), \
UInt32(ins.get_imm(2))
return (
UInt32(self.get_reg_content(ins, 0)),
UInt32(self.get_reg_content(ins, 1)),
UInt32(ins.get_imm(2)),
)
def get_reg_content(self, ins: 'Instruction', ind: int) -> Int32:
def get_reg_content(self, ins: "Instruction", ind: int) -> Int32:
"""
get the register name from ins and then return the register contents
"""
@ -140,6 +156,5 @@ class InstructionSet(ABC):
def __repr__(self):
return "InstructionSet[{}] with {} instructions".format(
self.__class__.__name__,
len(list(self.get_instructions()))
self.__class__.__name__, len(list(self.get_instructions()))
)

@ -3,21 +3,29 @@ import sys
from riscemu import RunConfig
from riscemu.types import InstructionMemorySection, SimpleInstruction, Program
if __name__ == '__main__':
if __name__ == "__main__":
from .CPU import UserModeCPU
from .instructions import InstructionSetDict
from .debug import launch_debug_session
cpu = UserModeCPU(list(InstructionSetDict.values()), RunConfig(verbosity=4))
program = Program('interactive session', base=0x100)
program = Program("interactive session", base=0x100)
context = program.context
program.add_section(InstructionMemorySection([
SimpleInstruction('ebreak', (), context, 0x100),
SimpleInstruction('addi', ('a0', 'zero', '0'), context, 0x104),
SimpleInstruction('addi', ('a7', 'zero', '93'), context, 0x108),
SimpleInstruction('scall', (), context, 0x10C),
], '.text', context, program.name, 0x100))
program.add_section(
InstructionMemorySection(
[
SimpleInstruction("ebreak", (), context, 0x100),
SimpleInstruction("addi", ("a0", "zero", "0"), context, 0x104),
SimpleInstruction("addi", ("a7", "zero", "93"), context, 0x108),
SimpleInstruction("scall", (), context, 0x10C),
],
".text",
context,
program.name,
0x100,
)
)
cpu.load_program(program)

@ -16,28 +16,32 @@ from .types.exceptions import ParseException
def parse_instruction(token: Token, args: Tuple[str], context: ParseContext):
if context.section is None:
context.new_section('.text', MemorySectionType.Instructions)
context.new_section(".text", MemorySectionType.Instructions)
if context.section.type != MemorySectionType.Instructions:
raise ParseException("{} {} encountered in invalid context: {}".format(token, args, context))
ins = SimpleInstruction(token.value, args, context.context, context.current_address())
raise ParseException(
"{} {} encountered in invalid context: {}".format(token, args, context)
)
ins = SimpleInstruction(
token.value, args, context.context, context.current_address()
)
context.section.data.append(ins)
def parse_label(token: Token, args: Tuple[str], context: ParseContext):
name = token.value[:-1]
if re.match(r'^\d+$', name):
if re.match(r"^\d+$", name):
# relative label:
context.context.numbered_labels[name].append(context.current_address())
else:
if name in context.context.labels:
print(FMT_PARSE + 'Warn: Symbol {} defined twice!'.format(name))
print(FMT_PARSE + "Warn: Symbol {} defined twice!".format(name))
context.add_label(name, context.current_address(), is_relative=True)
PARSERS: Dict[TokenType, Callable[[Token, Tuple[str], ParseContext], None]] = {
TokenType.PSEUDO_OP: AssemblerDirectives.handle_instruction,
TokenType.LABEL: parse_label,
TokenType.INSTRUCTION_NAME: parse_instruction
TokenType.INSTRUCTION_NAME: parse_instruction,
}
@ -58,7 +62,9 @@ def parse_tokens(name: str, tokens_iter: Iterable[Token]) -> Program:
return context.finalize()
def composite_tokenizer(tokens_iter: Iterable[Token]) -> Iterable[Tuple[Token, Tuple[str]]]:
def composite_tokenizer(
tokens_iter: Iterable[Token],
) -> Iterable[Tuple[Token, Tuple[str]]]:
"""
Convert an iterator over tokens into an iterator over tuples: (token, list(token))
@ -71,7 +77,11 @@ def composite_tokenizer(tokens_iter: Iterable[Token]) -> Iterable[Tuple[Token, T
while not tokens.is_empty():
token = next(tokens)
if token.type in (TokenType.PSEUDO_OP, TokenType.LABEL, TokenType.INSTRUCTION_NAME):
if token.type in (
TokenType.PSEUDO_OP,
TokenType.LABEL,
TokenType.INSTRUCTION_NAME,
):
yield token, tuple(take_arguments(tokens))
@ -106,8 +116,9 @@ class AssemblyFileLoader(ProgramLoader):
The AssemblyFileLoader loads .asm, .S and .s files by default, and acts as a weak fallback to all other filetypes.
"""
def parse(self) -> Program:
with open(self.source_path, 'r') as f:
with open(self.source_path, "r") as f:
return parse_tokens(self.filename, tokenize(f))
def parse_io(self, io):
@ -123,7 +134,7 @@ class AssemblyFileLoader(ProgramLoader):
:return:
"""
# gcc recognizes these line endings as assembly. So we will do too.
if source_path.split('.')[-1] in ('asm', 'S', 's'):
if source_path.split(".")[-1] in ("asm", "S", "s"):
return 1
return 0.01

@ -12,6 +12,7 @@ class CSR:
"""
This holds all Control and Status Registers (CSR)
"""
regs: Dict[int, UInt32]
"""
All Control and Status Registers are stored here
@ -52,7 +53,9 @@ class CSR:
return self.virtual_regs[addr]()
return self.regs[addr]
def set_listener(self, addr: Union[str, int], listener: Callable[[UInt32, UInt32], None]):
def set_listener(
self, addr: Union[str, int], listener: Callable[[UInt32, UInt32], None]
):
addr = self._name_to_addr(addr)
if addr is None:
print("unknown csr address name: {}".format(addr))
@ -73,11 +76,11 @@ class CSR:
"""
size = 2 if name in MSTATUS_LEN_2 else 1
off = MSTATUS_OFFSETS[name]
mask = (2 ** size - 1) << off
old_val = self.get('mstatus')
mask = (2**size - 1) << off
old_val = self.get("mstatus")
erased = old_val & (~mask)
new_val = erased | (val << off)
self.set('mstatus', new_val)
self.set("mstatus", new_val)
def get_mstatus(self, name) -> UInt32:
if not self.mstatus_cache_dirty and name in self.mstatus_cache:
@ -85,8 +88,8 @@ class CSR:
size = 2 if name in MSTATUS_LEN_2 else 1
off = MSTATUS_OFFSETS[name]
mask = (2 ** size - 1) << off
val = (self.get('mstatus') & mask) >> off
mask = (2**size - 1) << off
val = (self.get("mstatus") & mask) >> off
if self.mstatus_cache_dirty:
self.mstatus_cache = dict(name=val)
else:

@ -1,81 +1,81 @@
from typing import Dict, Tuple
MCAUSE_TRANSLATION: Dict[Tuple[int, int], str]= {
(1, 0): 'User software interrupt',
(1, 1): 'Supervisor software interrupt',
(1, 3): 'Machine software interrupt',
(1, 4): 'User timer interrupt',
(1, 5): 'Supervisor timer interrupt',
(1, 7): 'Machine timer interrupt',
(1, 8): 'User external interrupt',
(1, 9): 'Supervisor external interrupt',
(1, 11): 'Machine external interrupt',
(0, 0): 'Instruction address misaligned',
(0, 1): 'Instruction access fault',
(0, 2): 'Illegal instruction',
(0, 3): 'Breakpoint',
(0, 4): 'Load address misaligned',
(0, 5): 'Load access fault',
(0, 6): 'Store/AMO address misaligned',
(0, 7): 'Store/AMO access fault',
(0, 8): 'environment call from user mode',
(0, 9): 'environment call from supervisor mode',
(0, 11): 'environment call from machine mode',
(0, 12): 'Instruction page fault',
(0, 13): 'Load page fault',
(0, 15): 'Store/AMO page fault',
MCAUSE_TRANSLATION: Dict[Tuple[int, int], str] = {
(1, 0): "User software interrupt",
(1, 1): "Supervisor software interrupt",
(1, 3): "Machine software interrupt",
(1, 4): "User timer interrupt",
(1, 5): "Supervisor timer interrupt",
(1, 7): "Machine timer interrupt",
(1, 8): "User external interrupt",
(1, 9): "Supervisor external interrupt",
(1, 11): "Machine external interrupt",
(0, 0): "Instruction address misaligned",
(0, 1): "Instruction access fault",
(0, 2): "Illegal instruction",
(0, 3): "Breakpoint",
(0, 4): "Load address misaligned",
(0, 5): "Load access fault",
(0, 6): "Store/AMO address misaligned",
(0, 7): "Store/AMO access fault",
(0, 8): "environment call from user mode",
(0, 9): "environment call from supervisor mode",
(0, 11): "environment call from machine mode",
(0, 12): "Instruction page fault",
(0, 13): "Load page fault",
(0, 15): "Store/AMO page fault",
}
"""
Assigns tuple (interrupt bit, exception code) to their respective readable names
"""
MSTATUS_OFFSETS: Dict[str, int] = {
'uie': 0,
'sie': 1,
'mie': 3,
'upie': 4,
'spie': 5,
'mpie': 7,
'spp': 8,
'mpp': 11,
'fs': 13,
'xs': 15,
'mpriv': 17,
'sum': 18,
'mxr': 19,
'tvm': 20,
'tw': 21,
'tsr': 22,
'sd': 31
"uie": 0,
"sie": 1,
"mie": 3,
"upie": 4,
"spie": 5,
"mpie": 7,
"spp": 8,
"mpp": 11,
"fs": 13,
"xs": 15,
"mpriv": 17,
"sum": 18,
"mxr": 19,
"tvm": 20,
"tw": 21,
"tsr": 22,
"sd": 31,
}
"""
Offsets for all mstatus bits
"""
MSTATUS_LEN_2 = ('mpp', 'fs', 'xs')
MSTATUS_LEN_2 = ("mpp", "fs", "xs")
"""
All mstatus parts that have length 2. All other mstatus parts have length 1
"""
CSR_NAME_TO_ADDR: Dict[str, int] = {
'mstatus': 0x300,
'misa': 0x301,
'mie': 0x304,
'mtvec': 0x305,
'mepc': 0x341,
'mcause': 0x342,
'mtval': 0x343,
'mip': 0x344,
'mvendorid': 0xF11,
'marchid': 0xF12,
'mimpid': 0xF13,
'mhartid': 0xF14,
'time': 0xc01,
'timeh': 0xc81,
'halt': 0x789,
'mtimecmp': 0x780,
'mtimecmph': 0x781,
"mstatus": 0x300,
"misa": 0x301,
"mie": 0x304,
"mtvec": 0x305,
"mepc": 0x341,
"mcause": 0x342,
"mtval": 0x343,
"mip": 0x344,
"mvendorid": 0xF11,
"marchid": 0xF12,
"mimpid": 0xF13,
"mhartid": 0xF14,
"time": 0xC01,
"timeh": 0xC81,
"halt": 0x789,
"mtimecmp": 0x780,
"mtimecmph": 0x781,
}
"""
Translation for named registers
"""
"""

@ -11,7 +11,7 @@ if typing.TYPE_CHECKING:
from elftools.elf.elffile import ELFFile
from elftools.elf.sections import Section, SymbolTableSection
INCLUDE_SEC = ('.text', '.stack', '.bss', '.sdata', '.sbss')
INCLUDE_SEC = (".text", ".stack", ".bss", ".sdata", ".sbss")
class ElfBinaryFileLoader(ProgramLoader):
@ -20,6 +20,7 @@ class ElfBinaryFileLoader(ProgramLoader):
This loader respects local and global symbols.
"""
program: Program
def __init__(self, source_path: str, options: T_ParserOpts):
@ -28,8 +29,8 @@ class ElfBinaryFileLoader(ProgramLoader):
@classmethod
def can_parse(cls, source_path: str) -> float:
with open(source_path, 'rb') as f:
if f.read(4) == b'\x7f\x45\x4c\x46':
with open(source_path, "rb") as f:
if f.read(4) == b"\x7f\x45\x4c\x46":
return 1
return 0
@ -42,23 +43,33 @@ class ElfBinaryFileLoader(ProgramLoader):
from elftools.elf.elffile import ELFFile
from elftools.elf.sections import Section, SymbolTableSection
with open(self.source_path, 'rb') as f:
print(FMT_ELF + "[ElfLoader] Loading elf executable from: {}".format(self.source_path) + FMT_NONE)
with open(self.source_path, "rb") as f:
print(
FMT_ELF
+ "[ElfLoader] Loading elf executable from: {}".format(
self.source_path
)
+ FMT_NONE
)
self._read_elf(ELFFile(f))
except ImportError as e:
print(FMT_PARSE + "[ElfLoader] Cannot load elf files without PyElfTools package! You can install them "
"using pip install pyelftools!" + FMT_NONE)
print(
FMT_PARSE
+ "[ElfLoader] Cannot load elf files without PyElfTools package! You can install them "
"using pip install pyelftools!" + FMT_NONE
)
raise e
return self.program
def _read_elf(self, elf: 'ELFFile'):
if not elf.header.e_machine == 'EM_RISCV':
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':
if not elf.header.e_ident.EI_CLASS == "ELFCLASS32":
raise InvalidElfException("Only 32bit executables are supported!")
from elftools.elf.sections import SymbolTableSection
for sec in elf.iter_sections():
if isinstance(sec, SymbolTableSection):
self._parse_symtab(sec)
@ -69,18 +80,22 @@ class ElfBinaryFileLoader(ProgramLoader):
self._add_sec(self._lms_from_elf_sec(sec, self.filename))
def _lms_from_elf_sec(self, sec: 'Section', owner: str):
is_code = sec.name in ('.text',)
def _lms_from_elf_sec(self, sec: "Section", owner: str):
is_code = sec.name in (".text",)
data = bytearray(sec.data())
if len(data) < sec.data_size:
data += bytearray(len(data) - sec.data_size)
flags = MemoryFlags(is_code, is_code)
print(FMT_ELF + "[ElfLoader] Section {} at: {:X}".format(sec.name, sec.header.sh_addr) + FMT_NONE)
print(
FMT_ELF
+ "[ElfLoader] Section {} at: {:X}".format(sec.name, sec.header.sh_addr)
+ FMT_NONE
)
return ElfMemorySection(
data, sec.name, self.program.context, owner, sec.header.sh_addr, flags
)
def _parse_symtab(self, symtab: 'SymbolTableSection'):
def _parse_symtab(self, symtab: "SymbolTableSection"):
from elftools.elf.enums import ENUM_ST_VISIBILITY
for sym in symtab.iter_symbols():
@ -88,18 +103,26 @@ class ElfBinaryFileLoader(ProgramLoader):
continue
self.program.context.labels[sym.name] = sym.entry.st_value
# check if it has st_visibility bit set
if sym.entry.st_info.bind == 'STB_GLOBAL':
if sym.entry.st_info.bind == "STB_GLOBAL":
self.program.global_labels.add(sym.name)
print(FMT_PARSE + "LOADED GLOBAL SYMBOL {}: {}".format(sym.name, sym.entry.st_value) + FMT_NONE)
print(
FMT_PARSE
+ "LOADED GLOBAL SYMBOL {}: {}".format(sym.name, sym.entry.st_value)
+ FMT_NONE
)
def _add_sec(self, new_sec: 'ElfMemorySection'):
def _add_sec(self, new_sec: "ElfMemorySection"):
for sec in self.program.sections:
if sec.base < sec.end <= new_sec.base or sec.end > sec.base >= new_sec.end:
continue
else:
print(FMT_ELF + "[ElfLoader] Invalid elf layout: Two sections overlap: \n\t{}\n\t{}".format(
sec, new_sec
) + FMT_NONE)
print(
FMT_ELF
+ "[ElfLoader] Invalid elf layout: Two sections overlap: \n\t{}\n\t{}".format(
sec, new_sec
)
+ FMT_NONE
)
raise RuntimeError("Cannot load elf with overlapping sections!")
self.program.add_section(new_sec)

@ -45,7 +45,9 @@ class CpuTrap(BaseException):
The privilege level this trap targets
"""
def __init__(self, code: int, mtval, type: CpuTrapType, priv: PrivModes = PrivModes.MACHINE):
def __init__(
self, code: int, mtval, type: CpuTrapType, priv: PrivModes = PrivModes.MACHINE
):
self.interrupt = 0 if type == CpuTrapType.EXCEPTION else 1
self.code = code
self.mtval = UInt32(mtval)
@ -63,7 +65,9 @@ class CpuTrap(BaseException):
name = "Reserved interrupt({}, {})".format(self.interrupt, self.code)
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={:x}}} {}".format(
name, self.priv.name, self.type.name, self.mtval, self.message()
@ -74,7 +78,7 @@ class CpuTrap(BaseException):
class IllegalInstructionTrap(CpuTrap):
def __init__(self, ins: 'ElfInstruction'):
def __init__(self, ins: "ElfInstruction"):
super().__init__(2, ins.encoded, CpuTrapType.EXCEPTION)
@ -104,7 +108,9 @@ class InvalidElfException(RiscemuBaseException):
self.msg = msg
def message(self):
return FMT_PARSE + "{}(\"{}\")".format(self.__class__.__name__, self.msg) + FMT_NONE
return (
FMT_PARSE + '{}("{}")'.format(self.__class__.__name__, self.msg) + FMT_NONE
)
class LoadAccessFault(CpuTrap):
@ -117,8 +123,5 @@ class LoadAccessFault(CpuTrap):
def message(self):
return "(During {} at 0x{:08x} of size {}: {})".format(
self.op,
self.addr,
self.size,
self.msg
self.op, self.addr, self.size, self.msg
)

@ -14,10 +14,9 @@ from ..types import MemoryFlags, ProgramLoader, Program, T_ParserOpts
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
@ -26,14 +25,14 @@ class MemoryImageLoader(ProgramLoader):
return argv, {}
def parse(self) -> Iterable[Program]:
if 'debug' not in self.options:
if "debug" not in self.options:
yield self.parse_no_debug()
return
with open(self.options.get('debug'), 'r') as debug_file:
with open(self.options.get("debug"), "r") as debug_file:
debug_info = MemoryImageDebugInfos.load(debug_file.read())
with open(self.source_path, 'rb') as source_file:
with open(self.source_path, "rb") as source_file:
data: bytearray = bytearray(source_file.read())
for name, sections in debug_info.sections.items():
@ -43,11 +42,15 @@ class MemoryImageLoader(ProgramLoader):
if program.base is None:
program.base = start
#in_code_sec = get_section_base_name(sec_name) in INSTRUCTION_SECTION_NAMES
# in_code_sec = get_section_base_name(sec_name) in INSTRUCTION_SECTION_NAMES
program.add_section(
ElfMemorySection(
data[start:start+size], sec_name, program.context,
name, start, MemoryFlags(False, True)
data[start : start + size],
sec_name,
program.context,
name,
start,
MemoryFlags(False, True),
)
)
@ -57,19 +60,27 @@ class MemoryImageLoader(ProgramLoader):
yield program
def parse_no_debug(self) -> Program:
print(FMT_PARSE + "[MemoryImageLoader] Warning: loading memory image without debug information!" + FMT_NONE)
print(
FMT_PARSE
+ "[MemoryImageLoader] Warning: loading memory image without debug information!"
+ FMT_NONE
)
with open(self.source_path, 'rb') as source_file:
with open(self.source_path, "rb") as source_file:
data: bytes = source_file.read()
p = Program(self.filename)
p.add_section(ElfMemorySection(
bytearray(data), '.text', p.context, p.name, 0, MemoryFlags(False, True)
))
p.add_section(
ElfMemorySection(
bytearray(data), ".text", p.context, p.name, 0, MemoryFlags(False, True)
)
)
return p
@classmethod
def instantiate(cls, source_path: str, options: T_ParserOpts) -> 'ProgramLoader':
if os.path.isfile(source_path + '.dbg'):
return MemoryImageLoader(source_path, dict(**options, debug=source_path + '.dbg'))
def instantiate(cls, source_path: str, options: T_ParserOpts) -> "ProgramLoader":
if os.path.isfile(source_path + ".dbg"):
return MemoryImageLoader(
source_path, dict(**options, debug=source_path + ".dbg")
)
return MemoryImageLoader(source_path, options)

@ -87,7 +87,11 @@ class PrivCPU(CPU):
if self.halted:
print()
print(FMT_CPU + "[CPU] System halted with code {}".format(self.exit_code) + FMT_NONE)
print(
FMT_CPU
+ "[CPU] System halted with code {}".format(self.exit_code)
+ FMT_NONE
)
sys.exit(self.exit_code)
elif launch_debug:
@ -96,55 +100,68 @@ class PrivCPU(CPU):
self.run(verbose)
else:
print()
print(FMT_CPU + "[CPU] System stopped without halting - perhaps you stopped the debugger?" + FMT_NONE)
print(
FMT_CPU
+ "[CPU] System stopped without halting - perhaps you stopped the debugger?"
+ FMT_NONE
)
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)
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':
if program.name == "kernel":
self.pc = program.entrypoint
super().load_program(program)
def _init_csr(self):
# set up CSR
self.csr = CSR()
self.csr.set('mhartid', UInt32(0)) # core id
self.csr.set("mhartid", UInt32(0)) # core id
# TODO: set correct value
self.csr.set('mimpid', UInt32(0)) # implementation id
self.csr.set("mimpid", UInt32(0)) # implementation id
# set mxl to 1 (32 bit) and set bits for i and m isa
self.csr.set('misa', UInt32((1 << 30) + (1 << 8) + (1 << 12))) # available ISA
self.csr.set("misa", UInt32((1 << 30) + (1 << 8) + (1 << 12))) # available ISA
# CSR write callbacks:
@self.csr.callback('halt')
@self.csr.callback("halt")
def halt(old: UInt32, new: UInt32):
if new != 0:
self.halted = True
self.exit_code = new.value
@self.csr.callback('mtimecmp')
@self.csr.callback("mtimecmp")
def mtimecmp(old: UInt32, new: UInt32):
self._time_timecmp = (self.csr.get('mtimecmph') << 32) + new
self._time_timecmp = (self.csr.get("mtimecmph") << 32) + new
self._time_interrupt_enabled = True
@self.csr.callback('mtimecmph')
@self.csr.callback("mtimecmph")
def mtimecmph(old: UInt32, new: UInt32):
self._time_timecmp = (new << 32) + self.csr.get('mtimecmp')
self._time_timecmp = (new << 32) + self.csr.get("mtimecmp")
self._time_interrupt_enabled = True
# virtual CSR registers:
@self.csr.virtual_register('time')
@self.csr.virtual_register("time")
def get_time():
return UInt32(time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start)
return UInt32(
time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start
)
@self.csr.virtual_register('timeh')
@self.csr.virtual_register("timeh")
def get_timeh():
return UInt32((time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start) >> 32)
return UInt32(
(time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start)
>> 32
)
# add minstret and mcycle counters
@ -160,17 +177,21 @@ class PrivCPU(CPU):
self._check_interrupt()
ins = self.mmu.read_ins(self.pc)
if verbose and (self.mode == PrivModes.USER or self.conf.verbosity > 4):
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.run_instruction(ins)
self.pc += self.INS_XLEN
except CpuTrap as trap:
self._handle_trap(trap)
if trap.interrupt == 0 and not isinstance(trap, EcallTrap):
print(FMT_CPU + "[CPU] Trap {} encountered at {} (0x{:x})".format(
trap,
self.mmu.translate_address(self.pc),
self.pc
) + FMT_NONE)
print(
FMT_CPU
+ "[CPU] Trap {} encountered at {} (0x{:x})".format(
trap, self.mmu.translate_address(self.pc), self.pc
)
+ FMT_NONE
)
breakpoint()
if self.conf.debug_on_exception:
raise LaunchDebuggerException()
@ -179,12 +200,15 @@ class PrivCPU(CPU):
def _timer_step(self):
if not self._time_interrupt_enabled:
return
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._time_interrupt_enabled = False
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")):
return
# select best interrupt
# FIXME: actually select based on the official ranking
@ -194,24 +218,30 @@ class PrivCPU(CPU):
self.regs.dump_reg_a()
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:
print(FMT_CPU + "[CPU] Trap triggered outside of user mode?!" + FMT_NONE)
self.csr.set_mstatus('mpie', self.csr.get_mstatus('mie'))
self.csr.set_mstatus('mpp', self.mode.value)
self.csr.set_mstatus('mie', UInt32(0))
self.csr.set('mcause', trap.mcause)
self.csr.set('mepc', self.pc - self.INS_XLEN)
self.csr.set('mtval', trap.mtval)
self.csr.set_mstatus("mpie", self.csr.get_mstatus("mie"))
self.csr.set_mstatus("mpp", self.mode.value)
self.csr.set_mstatus("mie", UInt32(0))
self.csr.set("mcause", trap.mcause)
self.csr.set("mepc", self.pc - self.INS_XLEN)
self.csr.set("mtval", trap.mtval)
self.mode = trap.priv
mtvec = self.csr.get('mtvec')
mtvec = self.csr.get("mtvec")
if mtvec & 0b11 == 0:
self.pc = mtvec.value
if mtvec & 0b11 == 1:
self.pc = ((mtvec & 0b11111111111111111111111111111100) + (trap.code * 4)).value
self.pc = (
(mtvec & 0b11111111111111111111111111111100) + (trap.code * 4)
).value
self.record_perf_profile()
if len(self._perf_counters) > 100:
self.show_perf()
@ -232,7 +262,10 @@ class PrivCPU(CPU):
cycled = cycle
timed = time_ns
cps_list.append(cps)
print(" on average {:.0f} instructions/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()
def record_perf_profile(self):
@ -240,6 +273,4 @@ class PrivCPU(CPU):
@classmethod
def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]:
return [
AssemblyFileLoader, MemoryImageLoader, ElfBinaryFileLoader
]
return [AssemblyFileLoader, MemoryImageLoader, ElfBinaryFileLoader]

@ -9,7 +9,6 @@ if typing.TYPE_CHECKING:
class PrivMMU(MMU):
def get_sec_containing(self, addr: T_AbsoluteAddress) -> MemorySection:
# try to get an existing section
existing_sec = super().get_sec_containing(addr)
@ -18,7 +17,9 @@ class PrivMMU(MMU):
return existing_sec
# get section preceding empty space at addr
sec_before = next((sec for sec in reversed(self.sections) if sec.end < addr), None)
sec_before = next(
(sec for sec in reversed(self.sections) if sec.end < addr), None
)
# get sec succeeding empty space at addr
sec_after = next((sec for sec in self.sections if sec.base > addr), None)
@ -31,7 +32,14 @@ class PrivMMU(MMU):
# end at the start of the next section, or address + 0xFFFF (aligned to 16 byte boundary)
end = min(next_sec_start, align_addr(addr + 0xFFFF, 16))
sec = ElfMemorySection(bytearray(end - start), '.empty', self.global_instruction_context(), '', start, MemoryFlags(False, True))
sec = ElfMemorySection(
bytearray(end - start),
".empty",
self.global_instruction_context(),
"",
start,
MemoryFlags(False, True),
)
self.sections.append(sec)
self._update_state()
@ -40,4 +48,4 @@ class PrivMMU(MMU):
def global_instruction_context(self) -> InstructionContext:
context = InstructionContext()
context.global_symbol_dict = self.global_symbols
return context
return context

@ -16,155 +16,159 @@ if typing.TYPE_CHECKING:
class PrivRV32I(RV32I):
cpu: 'PrivCPU'
cpu: "PrivCPU"
"""
This is an extension of RV32I, written for the PrivCPU class
"""
def instruction_csrrw(self, ins: 'Instruction'):
def instruction_csrrw(self, ins: "Instruction"):
rd, rs, csr_addr = self.parse_crs_ins(ins)
old_val = None
if rd != 'zero':
if rd != "zero":
self.cpu.csr.assert_can_read(self.cpu.mode, csr_addr)
old_val = self.cpu.csr.get(csr_addr)
if rs != 'zero':
if rs != "zero":
new_val = self.regs.get(rs)
self.cpu.csr.assert_can_write(self.cpu.mode, csr_addr)
self.cpu.csr.set(csr_addr, new_val)
if old_val is not None:
self.regs.set(rd, old_val)
def instruction_csrrs(self, ins: 'Instruction'):
def instruction_csrrs(self, ins: "Instruction"):
rd, rs, csr_addr = self.parse_crs_ins(ins)
if rs != 'zero':
if rs != "zero":
# oh no, this should not happen!
INS_NOT_IMPLEMENTED(ins)
if rd != 'zero':
if rd != "zero":
self.cpu.csr.assert_can_read(self.cpu.mode, csr_addr)
old_val = self.cpu.csr.get(csr_addr)
self.regs.set(rd, old_val)
def instruction_csrrc(self, ins: 'Instruction'):
def instruction_csrrc(self, ins: "Instruction"):
INS_NOT_IMPLEMENTED(ins)
def instruction_csrrsi(self, ins: 'Instruction'):
def instruction_csrrsi(self, ins: "Instruction"):
INS_NOT_IMPLEMENTED(ins)
def instruction_csrrwi(self, ins: 'Instruction'):
def instruction_csrrwi(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 3)
rd, imm, addr = ins.get_reg(0), ins.get_imm(1), ins.get_imm(2)
if rd != 'zero':
if rd != "zero":
self.cpu.csr.assert_can_read(self.cpu.mode, addr)
old_val = self.cpu.csr.get(addr)
self.regs.set(rd, old_val)
self.cpu.csr.assert_can_write(self.cpu.mode, addr)
self.cpu.csr.set(addr, imm)
def instruction_csrrci(self, ins: 'Instruction'):
def instruction_csrrci(self, ins: "Instruction"):
INS_NOT_IMPLEMENTED(ins)
def instruction_mret(self, ins: 'Instruction'):
def instruction_mret(self, ins: "Instruction"):
if self.cpu.mode != PrivModes.MACHINE:
print("MRET not inside machine level code!")
raise IllegalInstructionTrap(ins)
# retore mie
mpie = self.cpu.csr.get_mstatus('mpie')
self.cpu.csr.set_mstatus('mie', mpie)
mpie = self.cpu.csr.get_mstatus("mpie")
self.cpu.csr.set_mstatus("mie", mpie)
# restore priv
mpp = self.cpu.csr.get_mstatus('mpp')
mpp = self.cpu.csr.get_mstatus("mpp")
self.cpu.mode = PrivModes(mpp)
# restore pc
mepc = self.cpu.csr.get('mepc')
mepc = self.cpu.csr.get("mepc")
self.cpu.pc = (mepc - self.cpu.INS_XLEN).value
if self.cpu.conf.verbosity > 0:
sec = self.mmu.get_sec_containing(mepc.value)
if sec is not None:
print(FMT_CPU + "[CPU] returning to mode {} in {} (0x{:x})".format(
PrivModes(mpp).name,
self.mmu.translate_address(mepc),
mepc
) + FMT_NONE)
print(
FMT_CPU
+ "[CPU] returning to mode {} in {} (0x{:x})".format(
PrivModes(mpp).name, self.mmu.translate_address(mepc), mepc
)
+ FMT_NONE
)
if self.cpu.conf.verbosity > 1:
self.regs.dump_reg_a()
def instruction_uret(self, ins: 'Instruction'):
def instruction_uret(self, ins: "Instruction"):
raise IllegalInstructionTrap(ins)
def instruction_sret(self, ins: 'Instruction'):
def instruction_sret(self, ins: "Instruction"):
raise IllegalInstructionTrap(ins)
def instruction_scall(self, ins: 'Instruction'):
def instruction_scall(self, ins: "Instruction"):
"""
Overwrite the scall from userspace RV32I
"""
raise EcallTrap(self.cpu.mode)
def instruction_beq(self, ins: 'Instruction'):
def instruction_beq(self, ins: "Instruction"):
rs1, rs2, dst = self.parse_rs_rs_imm(ins)
if rs1 == rs2:
self.pc += dst.value - 4
def instruction_bne(self, ins: 'Instruction'):
def instruction_bne(self, ins: "Instruction"):
rs1, rs2, dst = self.parse_rs_rs_imm(ins)
if rs1 != rs2:
self.pc += dst.value - 4
def instruction_blt(self, ins: 'Instruction'):
def instruction_blt(self, ins: "Instruction"):
rs1, rs2, dst = self.parse_rs_rs_imm(ins)
if rs1 < rs2:
self.pc += dst.value - 4
def instruction_bge(self, ins: 'Instruction'):
def instruction_bge(self, ins: "Instruction"):
rs1, rs2, dst = self.parse_rs_rs_imm(ins)
if rs1 >= rs2:
self.pc += dst.value - 4
def instruction_bltu(self, ins: 'Instruction'):
def instruction_bltu(self, ins: "Instruction"):
rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False)
if rs1 < rs2:
self.pc += dst.value - 4
def instruction_bgeu(self, ins: 'Instruction'):
def instruction_bgeu(self, ins: "Instruction"):
rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False)
if rs1 >= rs2:
self.pc += dst.value - 4
# technically deprecated
def instruction_j(self, ins: 'Instruction'):
def instruction_j(self, ins: "Instruction"):
raise NotImplementedError("Should never be reached!")
def instruction_jal(self, ins: 'Instruction'):
def instruction_jal(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 2)
reg = ins.get_reg(0)
addr = ins.get_imm(1)
if reg == 'ra' and (
(self.cpu.mode == PrivModes.USER and self.cpu.conf.verbosity > 1) or
(self.cpu.conf.verbosity > 3)
if reg == "ra" and (
(self.cpu.mode == PrivModes.USER and self.cpu.conf.verbosity > 1)
or (self.cpu.conf.verbosity > 3)
):
print(FMT_CPU + 'Jumping from 0x{:x} to {} (0x{:x})'.format(
self.pc,
self.mmu.translate_address(self.pc + addr),
self.pc + addr
) + FMT_NONE)
print(
FMT_CPU
+ "Jumping from 0x{:x} to {} (0x{:x})".format(
self.pc, self.mmu.translate_address(self.pc + addr), self.pc + addr
)
+ FMT_NONE
)
self.regs.dump_reg_a()
self.regs.set(reg, Int32(self.pc))
self.pc += addr - 4
def instruction_jalr(self, ins: 'Instruction'):
def instruction_jalr(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 3)
rd, rs, imm = self.parse_rd_rs_imm(ins)
self.regs.set(rd, Int32(self.pc))
self.pc = rs.value + imm.value - 4
def instruction_sbreak(self, ins: 'Instruction'):
def instruction_sbreak(self, ins: "Instruction"):
raise LaunchDebuggerException()
def parse_crs_ins(self, ins: 'Instruction'):
def parse_crs_ins(self, ins: "Instruction"):
ASSERT_LEN(ins.args, 3)
return ins.get_reg(0), ins.get_reg(1), ins.get_imm(2)
def parse_mem_ins(self, ins: 'Instruction') -> Tuple[str, int]:
def parse_mem_ins(self, ins: "Instruction") -> Tuple[str, int]:
ASSERT_LEN(ins.args, 3)
addr = self.get_reg_content(ins, 1) + ins.get_imm(2)
reg = ins.get_reg(0)

@ -12,4 +12,4 @@ Syscalls will have to be intercepted by your assembly code.
The PrivCPU Implements the Risc-V M/U Model, meaning there is machine mode and user mode. No PMP or paging is available.
"""
"""

@ -4,27 +4,55 @@ from .PrivCPU import PrivCPU
import sys
if __name__ == '__main__':
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description='RISC-V privileged architecture emulator', prog='riscemu')
parser = argparse.ArgumentParser(
description="RISC-V privileged architecture emulator", prog="riscemu"
)
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(
"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,
)
parser.add_argument('--slowdown', help="Slow down the emulated CPU clock by a factor", type=float, default=1)
parser.add_argument(
"--slowdown",
help="Slow down the emulated CPU clock by a factor",
type=float,
default=1,
)
args = parser.parse_args()
cpu = PrivCPU(RunConfig(verbosity=args.verbose, debug_on_exception=args.debug_exceptions, slowdown=args.slowdown))
cpu = PrivCPU(
RunConfig(
verbosity=args.verbose,
debug_on_exception=args.debug_exceptions,
slowdown=args.slowdown,
)
)
for source_path in args.source:
loader = max((loader for loader in cpu.get_loaders()), key=lambda l: l.can_parse(source_path))
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):

@ -6,9 +6,19 @@ from typing import Tuple, Dict, Set
from riscemu.colors import FMT_NONE, FMT_PARSE
from riscemu.decoder import format_ins, RISCV_REGS, decode
from riscemu.priv.Exceptions import InstructionAccessFault, InstructionAddressMisalignedTrap, LoadAccessFault
from riscemu.types import Instruction, InstructionContext, T_RelativeAddress, MemoryFlags, T_AbsoluteAddress, \
BinaryDataMemorySection
from riscemu.priv.Exceptions import (
InstructionAccessFault,
InstructionAddressMisalignedTrap,
LoadAccessFault,
)
from riscemu.types import (
Instruction,
InstructionContext,
T_RelativeAddress,
MemoryFlags,
T_AbsoluteAddress,
BinaryDataMemorySection,
)
@dataclass(frozen=True)
@ -27,34 +37,45 @@ class ElfInstruction(Instruction):
return RISCV_REGS[self.args[num]]
def __repr__(self) -> str:
if self.name == 'jal' and self.args[0] == 0:
if self.name == "jal" and self.args[0] == 0:
return "j {}".format(self.args[1])
if self.name == 'addi' and self.args[2] == 0:
if self.name == "addi" and self.args[2] == 0:
return "mv {}, {}".format(self.get_reg(0), self.get_reg(1))
if self.name == 'addi' and self.args[1] == 0:
if self.name == "addi" and self.args[1] == 0:
return "li {}, {}".format(self.get_reg(0), self.args[2])
if self.name == 'ret' and len(self.args) == 0:
if self.name == "ret" and len(self.args) == 0:
return "ret"
return format_ins(self.encoded, self.name)
class ElfMemorySection(BinaryDataMemorySection):
def __init__(self, data: bytearray, name: str, context: InstructionContext, owner: str, base: int,
flags: MemoryFlags):
def __init__(
self,
data: bytearray,
name: str,
context: InstructionContext,
owner: str,
base: int,
flags: MemoryFlags,
):
super().__init__(data, name, context, owner, base=base, flags=flags)
self.read_ins = lru_cache(maxsize=self.size // 4)(self.read_ins)
def read_ins(self, offset):
if not self.flags.executable:
print(FMT_PARSE + "Reading instruction from non-executable memory!" + FMT_NONE)
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.data[offset:offset + 4]))
return ElfInstruction(*decode(self.data[offset : offset + 4]))
def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
if self.flags.read_only:
raise LoadAccessFault('read-only section', offset + self.base, size, 'write')
raise LoadAccessFault(
"read-only section", offset + self.base, size, "write"
)
self.read_ins.cache_clear()
return super(ElfMemorySection, self).write(offset, size, data)
@ -64,7 +85,7 @@ class ElfMemorySection(BinaryDataMemorySection):
class MemoryImageDebugInfos:
VERSION = '1.0.0'
VERSION = "1.0.0"
"""
Schema version
"""
@ -89,12 +110,13 @@ class MemoryImageDebugInfos:
This dictionary contains the list of all global symbols of a given program
"""
def __init__(self,
sections: Dict[str, Dict[str, Tuple[int, int]]],
symbols: Dict[str, Dict[str, int]],
globals: Dict[str, Set[str]],
base: int = 0
):
def __init__(
self,
sections: Dict[str, Dict[str, Tuple[int, int]]],
symbols: Dict[str, Dict[str, int]],
globals: Dict[str, Set[str]],
base: int = 0,
):
self.sections = sections
self.symbols = symbols
self.globals = globals
@ -108,7 +130,9 @@ class MemoryImageDebugInfos:
return json.dumps(dict(obj), default=serialize)
if isinstance(obj, (set, tuple)):
return json.dumps(list(obj), default=serialize)
return "<<unserializable {}>>".format(getattr(obj, '__qualname__', '{unknown}'))
return "<<unserializable {}>>".format(
getattr(obj, "__qualname__", "{unknown}")
)
return json.dumps(
dict(
@ -116,22 +140,25 @@ class MemoryImageDebugInfos:
symbols=self.symbols,
globals=self.globals,
base=self.base,
VERSION=self.VERSION
VERSION=self.VERSION,
),
default=serialize
default=serialize,
)
@classmethod
def load(cls, serialized_str: str) -> 'MemoryImageDebugInfos':
def load(cls, serialized_str: str) -> "MemoryImageDebugInfos":
json_obj: dict = json.loads(serialized_str)
if 'VERSION' not in json_obj:
if "VERSION" not in json_obj:
raise RuntimeError("Unknown MemoryImageDebugInfo version!")
version: str = json_obj.pop('VERSION')
version: str = json_obj.pop("VERSION")
# compare major version
if version != cls.VERSION and version.split('.')[0] != cls.VERSION.split('.')[0]:
if (
version != cls.VERSION
and version.split(".")[0] != cls.VERSION.split(".")[0]
):
raise RuntimeError(
"Unknown MemoryImageDebugInfo version! This emulator expects version {}, debug info version {}".format(
cls.VERSION, version
@ -141,7 +168,7 @@ class MemoryImageDebugInfos:
return MemoryImageDebugInfos(**json_obj)
@classmethod
def builder(cls) -> 'MemoryImageDebugInfos':
def builder(cls) -> "MemoryImageDebugInfos":
return MemoryImageDebugInfos(
defaultdict(dict), defaultdict(dict), defaultdict(set)
)

@ -18,17 +18,72 @@ class Registers:
"""
valid_regs = {
'zero', 'ra', 'sp', 'gp', 'tp', 's0', 'fp',
't0', 't1', 't2', 't3', 't4', 't5', 't6',
's1', 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11',
'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7',
'ft0', 'ft1', 'ft2', 'ft3', 'ft4', 'ft5', 'ft6', 'ft7',
'fs0', 'fs1', 'fs2', 'fs3', 'fs4', 'fs5', 'fs6', 'fs7', 'fs8', 'fs9', 'fs10', 'fs11',
'fa0', 'fa1', 'fa2', 'fa3', 'fa4', 'fa5', 'fa6', 'fa7'
"zero",
"ra",
"sp",
"gp",
"tp",
"s0",
"fp",
"t0",
"t1",
"t2",
"t3",
"t4",
"t5",
"t6",
"s1",
"s2",
"s3",
"s4",
"s5",
"s6",
"s7",
"s8",
"s9",
"s10",
"s11",
"a0",
"a1",
"a2",
"a3",
"a4",
"a5",
"a6",
"a7",
"ft0",
"ft1",
"ft2",
"ft3",
"ft4",
"ft5",
"ft6",
"ft7",
"fs0",
"fs1",
"fs2",
"fs3",
"fs4",
"fs5",
"fs6",
"fs7",
"fs8",
"fs9",
"fs10",
"fs11",
"fa0",
"fa1",
"fa2",
"fa3",
"fa4",
"fa5",
"fa6",
"fa7",
}
def __init__(self, infinite_regs: bool = False):
from .types import Int32
self.vals = defaultdict(lambda: Int32(0))
self.last_set = None
self.last_read = None
@ -43,28 +98,30 @@ class Registers:
lines = [[] for i in range(12)]
if not full:
regs = [('a', 8), ('s', 12), ('t', 7)]
regs = [("a", 8), ("s", 12), ("t", 7)]
else:
regs = [
('a', 8),
('s', 12),
('t', 7),
('ft', 8),
('fa', 8),
('fs', 12),
("a", 8),
("s", 12),
("t", 7),
("ft", 8),
("fa", 8),
("fs", 12),
]
for name, s in regs:
for i in range(12):
if i >= s:
lines[i].append(" " * 15)
else:
reg = '{}{}'.format(name, i)
reg = "{}{}".format(name, i)
lines[i].append(self._reg_repr(reg))
print("Registers[{},{}](".format(
FMT_ORANGE + FMT_UNDERLINE + 'read' + FMT_NONE,
FMT_ORANGE + FMT_BOLD + 'written' + FMT_NONE
))
print(
"Registers[{},{}](".format(
FMT_ORANGE + FMT_UNDERLINE + "read" + FMT_NONE,
FMT_ORANGE + FMT_BOLD + "written" + FMT_NONE,
)
)
if not full:
print("\t" + " ".join(named_regs[0:3]))
print("\t" + " ".join(named_regs[3:]))
@ -80,23 +137,26 @@ class Registers:
"""
Dump the a registers
"""
print("Registers[a]:" + " ".join(self._reg_repr('a{}'.format(i)) for i in range(8)))
print(
"Registers[a]:"
+ " ".join(self._reg_repr("a{}".format(i)) for i in range(8))
)
def _reg_repr(self, reg):
txt = '{:4}=0x{:08X}'.format(reg, self.get(reg, False))
if reg == 'fp':
reg = 's0'
txt = "{:4}=0x{:08X}".format(reg, self.get(reg, False))
if reg == "fp":
reg = "s0"
if reg == self.last_set:
return FMT_ORANGE + FMT_BOLD + txt + FMT_NONE
if reg == self.last_read:
return FMT_ORANGE + FMT_UNDERLINE + txt + FMT_NONE
if reg == 'zero':
if reg == "zero":
return txt
if self.get(reg, False) == 0 and reg not in Registers.named_registers():
return FMT_GRAY + txt + FMT_NONE
return txt
def set(self, reg, val: 'Int32', mark_set=True) -> bool:
def set(self, reg, val: "Int32", mark_set=True) -> bool:
"""
Set a register content to val
:param reg: The register to set
@ -106,17 +166,20 @@ class Registers:
"""
from .types import Int32
# remove after refactoring is complete
if not isinstance(val, Int32):
raise RuntimeError("Setting register to non-Int32 value! Please refactor your code!")
raise RuntimeError(
"Setting register to non-Int32 value! Please refactor your code!"
)
if reg == 'zero':
if reg == "zero":
return False
# if reg not in Registers.all_registers():
# raise InvalidRegisterException(reg)
# replace fp register with s1, as these are the same register
if reg == 'fp':
reg = 's1'
if reg == "fp":
reg = "s1"
if mark_set:
self.last_set = reg
@ -126,7 +189,7 @@ class Registers:
self.vals[reg] = val.unsigned()
return True
def get(self, reg, mark_read=True) -> 'Int32':
def get(self, reg, mark_read=True) -> "Int32":
"""
Retuns the contents of register reg
:param reg: The register name
@ -135,8 +198,8 @@ class Registers:
"""
# if reg not in Registers.all_registers():
# raise InvalidRegisterException(reg)
if reg == 'fp':
reg = 's0'
if reg == "fp":
reg = "s0"
if not self.infinite_regs and reg not in self.valid_regs:
raise RuntimeError("Invalid register: {}".format(reg))
@ -151,13 +214,69 @@ class Registers:
Return a list of all valid registers
:return: The list
"""
return ['zero', 'ra', 'sp', 'gp', 'tp', 's0', 'fp',
't0', 't1', 't2', 't3', 't4', 't5', 't6',
's1', 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11',
'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7',
'ft0', 'ft1', 'ft2', 'ft3', 'ft4', 'ft5', 'ft6', 'ft7',
'fs0', 'fs1', 'fs2', 'fs3', 'fs4', 'fs5', 'fs6', 'fs7', 'fs8', 'fs9', 'fs10', 'fs11',
'fa0', 'fa1', 'fa2', 'fa3', 'fa4', 'fa5', 'fa6', 'fa7']
return [
"zero",
"ra",
"sp",
"gp",
"tp",
"s0",
"fp",
"t0",
"t1",
"t2",
"t3",
"t4",
"t5",
"t6",
"s1",
"s2",
"s3",
"s4",
"s5",
"s6",
"s7",
"s8",
"s9",
"s10",
"s11",
"a0",
"a1",
"a2",
"a3",
"a4",
"a5",
"a6",
"a7",
"ft0",
"ft1",
"ft2",
"ft3",
"ft4",
"ft5",
"ft6",
"ft7",
"fs0",
"fs1",
"fs2",
"fs3",
"fs4",
"fs5",
"fs6",
"fs7",
"fs8",
"fs9",
"fs10",
"fs11",
"fa0",
"fa1",
"fa2",
"fa3",
"fa4",
"fa5",
"fa6",
"fa7",
]
@staticmethod
def named_registers():
@ -165,4 +284,4 @@ class Registers:
Return all named registers
:return: The list
"""
return ['zero', 'ra', 'sp', 'gp', 'tp', 'fp']
return ["zero", "ra", "sp", "gp", "tp", "fp"]

@ -13,11 +13,11 @@ from .types import Int32, CPU
from .types.exceptions import InvalidSyscallException
SYSCALLS = {
63: 'read',
64: 'write',
93: 'exit',
1024: 'open',
1025: 'close',
63: "read",
64: "write",
93: "exit",
1024: "open",
1025: "close",
}
"""
This dict contains a mapping for all available syscalls (code->name)
@ -28,11 +28,11 @@ class.
"""
OPEN_MODES = {
0: 'rb',
1: 'wb',
2: 'r+b',
3: 'x',
4: 'ab',
0: "rb",
1: "wb",
2: "r+b",
3: "x",
4: "ab",
}
"""All available file open modes"""
@ -42,6 +42,7 @@ class Syscall:
"""
Represents a syscall
"""
id: int
"""The syscall number (e.g. 64 - write)"""
cpu: CPU
@ -52,12 +53,10 @@ class Syscall:
return SYSCALLS.get(self.id, "unknown")
def __repr__(self):
return "Syscall(id={}, name={})".format(
self.id, self.name
)
return "Syscall(id={}, name={})".format(self.id, self.name)
def ret(self, code):
self.cpu.regs.set('a0', Int32(code))
self.cpu.regs.set("a0", Int32(code))
def get_syscall_symbols():
@ -66,25 +65,20 @@ def get_syscall_symbols():
:return: dictionary of all syscall symbols (SCALL_<name> -> id)
"""
return {
('SCALL_' + name.upper()): num for num, name in SYSCALLS.items()
}
return {("SCALL_" + name.upper()): num for num, name in SYSCALLS.items()}
class SyscallInterface:
"""
Handles syscalls
"""
open_files: Dict[int, IO]
next_open_handle: int
def handle_syscall(self, scall: Syscall):
self.next_open_handle = 3
self.open_files = {
0: sys.stdin,
1: sys.stdout,
2: sys.stderr
}
self.open_files = {0: sys.stdin, 1: sys.stdout, 2: sys.stderr}
if getattr(self, scall.name):
getattr(self, scall.name)(scall)
@ -96,21 +90,25 @@ class SyscallInterface:
read syscall (63): read from file no a0, into addr a1, at most a2 bytes
on return a0 will be the number of read bytes or -1 if an error occured
"""
fileno = scall.cpu.regs.get('a0').unsigned_value
addr = scall.cpu.regs.get('a1').unsigned_value
size = scall.cpu.regs.get('a2').unsigned_value
fileno = scall.cpu.regs.get("a0").unsigned_value
addr = scall.cpu.regs.get("a1").unsigned_value
size = scall.cpu.regs.get("a2").unsigned_value
if fileno not in self.open_files:
scall.ret(-1)
return
chars = self.open_files[fileno].readline(size)
try:
data = bytearray(chars, 'ascii')
data = bytearray(chars, "ascii")
scall.cpu.mmu.write(addr, len(data), data)
return scall.ret(len(data))
except UnicodeEncodeError:
print(FMT_SYSCALL + '[Syscall] read: UnicodeError - invalid input "{}"'.format(chars) + FMT_NONE)
print(
FMT_SYSCALL
+ '[Syscall] read: UnicodeError - invalid input "{}"'.format(chars)
+ FMT_NONE
)
return scall.ret(-1)
def write(self, scall: Syscall):
@ -118,19 +116,23 @@ class SyscallInterface:
write syscall (64): write a2 bytes from addr a1 into fileno a0
on return a0 will hold the number of bytes written or -1 if an error occured
"""
fileno = scall.cpu.regs.get('a0').unsigned_value
addr = scall.cpu.regs.get('a1').unsigned_value
size = scall.cpu.regs.get('a2').unsigned_value
fileno = scall.cpu.regs.get("a0").unsigned_value
addr = scall.cpu.regs.get("a1").unsigned_value
size = scall.cpu.regs.get("a2").unsigned_value
if fileno not in self.open_files:
return scall.ret(-1)
data = scall.cpu.mmu.read(addr, size)
if not isinstance(data, bytearray):
print(FMT_SYSCALL + '[Syscall] write: writing from .text region not supported.' + FMT_NONE)
print(
FMT_SYSCALL
+ "[Syscall] write: writing from .text region not supported."
+ FMT_NONE
)
return scall.ret(-1)
self.open_files[fileno].write(data.decode('ascii'))
self.open_files[fileno].write(data.decode("ascii"))
return scall.ret(size)
def open(self, scall: Syscall):
@ -149,19 +151,29 @@ class SyscallInterface:
"""
# FIXME: this should be toggleable in a global setting or something
if True:
print(FMT_SYSCALL + '[Syscall] open: opening files not supported without scall-fs flag!' + FMT_NONE)
print(
FMT_SYSCALL
+ "[Syscall] open: opening files not supported without scall-fs flag!"
+ FMT_NONE
)
return scall.ret(-1)
mode = scall.cpu.regs.get('a0').unsigned_value
addr = scall.cpu.regs.get('a1').unsigned_value
size = scall.cpu.regs.get('a2').unsigned_value
mode = scall.cpu.regs.get("a0").unsigned_value
addr = scall.cpu.regs.get("a1").unsigned_value
size = scall.cpu.regs.get("a2").unsigned_value
mode_st = OPEN_MODES.get(mode, )
mode_st = OPEN_MODES.get(
mode,
)
if mode_st == -1:
print(FMT_SYSCALL + '[Syscall] open: unknown opening mode {}!'.format(mode) + FMT_NONE)
print(
FMT_SYSCALL
+ "[Syscall] open: unknown opening mode {}!".format(mode)
+ FMT_NONE
)
return scall.ret(-1)
path = scall.cpu.mmu.read(addr, size).decode('ascii')
path = scall.cpu.mmu.read(addr, size).decode("ascii")
fileno = self.next_open_handle
self.next_open_handle += 1
@ -169,10 +181,18 @@ class SyscallInterface:
try:
self.open_files[fileno] = open(path, mode_st)
except OSError as err:
print(FMT_SYSCALL + '[Syscall] open: encountered error during {}!'.format(err.strerror) + FMT_NONE)
print(
FMT_SYSCALL
+ "[Syscall] open: encountered error during {}!".format(err.strerror)
+ FMT_NONE
)
return scall.ret(-1)
print(FMT_SYSCALL + '[Syscall] open: opened fd {} to {}!'.format(fileno, path) + FMT_NONE)
print(
FMT_SYSCALL
+ "[Syscall] open: opened fd {} to {}!".format(fileno, path)
+ FMT_NONE
)
return scall.ret(fileno)
def close(self, scall: Syscall):
@ -181,13 +201,17 @@ class SyscallInterface:
return -1 if an error was encountered, otherwise returns 0
"""
fileno = scall.cpu.regs.get('a0').unsigned_value
fileno = scall.cpu.regs.get("a0").unsigned_value
if fileno not in self.open_files:
print(FMT_SYSCALL + '[Syscall] close: unknown fileno {}!'.format(fileno) + FMT_NONE)
print(
FMT_SYSCALL
+ "[Syscall] close: unknown fileno {}!".format(fileno)
+ FMT_NONE
)
return scall.ret(-1)
self.open_files[fileno].close()
print(FMT_SYSCALL + '[Syscall] close: closed fd {}!'.format(fileno) + FMT_NONE)
print(FMT_SYSCALL + "[Syscall] close: closed fd {}!".format(fileno) + FMT_NONE)
del self.open_files[fileno]
return scall.ret(0)
@ -196,10 +220,7 @@ class SyscallInterface:
Exit syscall. Exits the system with status code a0
"""
scall.cpu.halted = True
scall.cpu.exit_code = scall.cpu.regs.get('a0').value
scall.cpu.exit_code = scall.cpu.regs.get("a0").value
def __repr__(self):
return "{}(\n\tfiles={}\n)".format(
self.__class__.__name__,
self.open_files
)
return "{}(\n\tfiles={}\n)".format(self.__class__.__name__, self.open_files)

@ -12,9 +12,11 @@ from typing import List, Iterable
from riscemu.decoder import RISCV_REGS
from riscemu.types.exceptions import ParseException
LINE_COMMENT_STARTERS = ('#', ';', '//')
WHITESPACE_PATTERN = re.compile(r'\s+')
MEMORY_ADDRESS_PATTERN = re.compile(r'^(0[xX][A-f0-9]+|\d+|0b[0-1]+|[A-z0-9_-]+)\(([A-z]+[0-9]{0,2})\)$')
LINE_COMMENT_STARTERS = ("#", ";", "//")
WHITESPACE_PATTERN = re.compile(r"\s+")
MEMORY_ADDRESS_PATTERN = re.compile(
r"^(0[xX][A-f0-9]+|\d+|0b[0-1]+|[A-z0-9_-]+)\(([A-z]+[0-9]{0,2})\)$"
)
REGISTER_NAMES = RISCV_REGS
@ -34,22 +36,22 @@ class Token:
def __str__(self):
if self.type == TokenType.NEWLINE:
return '\\n'
return "\\n"
if self.type == TokenType.COMMA:
return ', '
return '{}({})'.format(self.type.name[0:3], self.value)
return ", "
return "{}({})".format(self.type.name[0:3], self.value)
NEWLINE = Token(TokenType.NEWLINE, '\n')
COMMA = Token(TokenType.COMMA, ',')
NEWLINE = Token(TokenType.NEWLINE, "\n")
COMMA = Token(TokenType.COMMA, ",")
def tokenize(input: Iterable[str]) -> Iterable[Token]:
for line in input:
for line_comment_start in LINE_COMMENT_STARTERS:
if line_comment_start in line:
line = line[:line.index(line_comment_start)]
line.strip(' \t\n')
line = line[: line.index(line_comment_start)]
line.strip(" \t\n")
if not line:
continue
@ -64,9 +66,9 @@ def parse_line(parts: List[str]) -> Iterable[Token]:
return ()
first_token = parts[0]
if first_token[0] == '.':
if first_token[0] == ".":
yield Token(TokenType.PSEUDO_OP, first_token)
elif first_token[-1] == ':':
elif first_token[-1] == ":":
yield Token(TokenType.LABEL, first_token)
yield from parse_line(parts[1:])
return
@ -74,14 +76,14 @@ def parse_line(parts: List[str]) -> Iterable[Token]:
yield Token(TokenType.INSTRUCTION_NAME, first_token)
for part in parts[1:]:
if part == ',':
if part == ",":
yield COMMA
continue
yield from parse_arg(part)
def parse_arg(arg: str) -> Iterable[Token]:
comma = arg[-1] == ','
comma = arg[-1] == ","
arg = arg[:-1] if comma else arg
mem_match_resul = re.match(MEMORY_ADDRESS_PATTERN, arg)
if mem_match_resul:
@ -98,7 +100,7 @@ def parse_arg(arg: str) -> Iterable[Token]:
def print_tokens(tokens: Iterable[Token]):
for token in tokens:
print(token, end='\n' if token == NEWLINE else '')
print(token, end="\n" if token == NEWLINE else "")
print("", flush=True, end="")
@ -123,7 +125,7 @@ def split_whitespace_respecting_quotes(line: str) -> Iterable[str]:
part = ""
continue
if c in ' \t\n':
if c in " \t\n":
if part:
yield part
part = ""

@ -8,7 +8,7 @@ T_AbsoluteAddress = int
# parser options are just dictionaries with arbitrary values
T_ParserOpts = Dict[str, any]
NUMBER_SYMBOL_PATTERN = re.compile(r'^\d+[fb]$')
NUMBER_SYMBOL_PATTERN = re.compile(r"^\d+[fb]$")
# base classes
from .flags import MemoryFlags
@ -24,6 +24,16 @@ from .instruction_memory_section import InstructionMemorySection
from .binary_data_memory_section import BinaryDataMemorySection
# exceptions
from .exceptions import ParseException, NumberFormatException, MemoryAccessException, OutOfMemoryException, \
LinkerException, LaunchDebuggerException, RiscemuBaseException, InvalidRegisterException, \
InvalidAllocationException, InvalidSyscallException, UnimplementedInstruction
from .exceptions import (
ParseException,
NumberFormatException,
MemoryAccessException,
OutOfMemoryException,
LinkerException,
LaunchDebuggerException,
RiscemuBaseException,
InvalidRegisterException,
InvalidAllocationException,
InvalidSyscallException,
UnimplementedInstruction,
)

@ -1,9 +1,23 @@
from . import MemorySection, InstructionContext, MemoryFlags, T_RelativeAddress, Instruction
from . import (
MemorySection,
InstructionContext,
MemoryFlags,
T_RelativeAddress,
Instruction,
)
from ..types.exceptions import MemoryAccessException
class BinaryDataMemorySection(MemorySection):
def __init__(self, data: bytearray, name: str, context: InstructionContext, owner: str, base: int = 0, flags: MemoryFlags = None):
def __init__(
self,
data: bytearray,
name: str,
context: InstructionContext,
owner: str,
base: int = 0,
flags: MemoryFlags = None,
):
self.name = name
self.base = base
self.context = context
@ -14,16 +28,26 @@ class BinaryDataMemorySection(MemorySection):
def read(self, offset: T_RelativeAddress, size: int) -> bytearray:
if offset + size > self.size:
raise MemoryAccessException("Out of bounds access in {}".format(self), offset, size, 'read')
return self.data[offset:offset + size]
raise MemoryAccessException(
"Out of bounds access in {}".format(self), offset, size, "read"
)
return self.data[offset : offset + size]
def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
if offset + size > self.size:
raise MemoryAccessException("Out of bounds access in {}".format(self), offset, size, 'write')
raise MemoryAccessException(
"Out of bounds access in {}".format(self), offset, size, "write"
)
if len(data[0:size]) != size:
raise MemoryAccessException("Invalid write parameter sizing", offset, size, 'write')
self.data[offset:offset + size] = data[0:size]
raise MemoryAccessException(
"Invalid write parameter sizing", offset, size, "write"
)
self.data[offset : offset + size] = data[0:size]
def read_ins(self, offset: T_RelativeAddress) -> Instruction:
raise MemoryAccessException("Tried reading instruction on non-executable section {}".format(self),
offset, 4, 'instruction fetch')
raise MemoryAccessException(
"Tried reading instruction on non-executable section {}".format(self),
offset,
4,
"instruction fetch",
)

@ -14,7 +14,7 @@ class CPU(ABC):
# housekeeping variables
regs: Registers
mmu: 'MMU'
mmu: "MMU"
pc: T_AbsoluteAddress
cycle: int
halted: bool
@ -24,12 +24,17 @@ class CPU(ABC):
# instruction information
instructions: Dict[str, Callable[[Instruction], None]]
instruction_sets: Set['InstructionSet']
instruction_sets: Set["InstructionSet"]
# configuration
conf: RunConfig
def __init__(self, mmu: 'MMU', instruction_sets: List[Type['InstructionSet']], conf: RunConfig):
def __init__(
self,
mmu: "MMU",
instruction_sets: List[Type["InstructionSet"]],
conf: RunConfig,
):
self.mmu = mmu
self.regs = Registers(conf.unlimited_registers)
self.conf = conf
@ -71,7 +76,7 @@ class CPU(ABC):
self.pc,
self.cycle,
self.halted,
" ".join(s.name for s in self.instruction_sets)
" ".join(s.name for s in self.instruction_sets),
)
@abstractmethod
@ -84,12 +89,18 @@ class CPU(ABC):
def launch(self, program: Program, verbose: bool = False):
if program not in self.mmu.programs:
print(FMT_ERROR + '[CPU] Cannot launch program that\'s not loaded!' + FMT_NONE)
print(
FMT_ERROR + "[CPU] Cannot launch program that's not loaded!" + FMT_NONE
)
return
if self.conf.verbosity > 0:
print(FMT_CPU + "[CPU] Started running from {}".format(
self.mmu.translate_address(program.entrypoint)
) + FMT_NONE)
print(
FMT_CPU
+ "[CPU] Started running from {}".format(
self.mmu.translate_address(program.entrypoint)
)
+ FMT_NONE
)
print(program)
self.pc = program.entrypoint
self.run(verbose)

@ -19,10 +19,13 @@ class RiscemuBaseException(BaseException):
def print_stacktrace(self):
import traceback
traceback.print_exception(type(self), self, self.__traceback__)
# Parsing exceptions:
class ParseException(RiscemuBaseException):
def __init__(self, msg, data=None):
super().__init__(msg, data)
@ -30,32 +33,47 @@ class ParseException(RiscemuBaseException):
self.data = data
def message(self):
return FMT_PARSE + "{}(\"{}\", data={})".format(self.__class__.__name__, self.msg, self.data) + FMT_NONE
return (
FMT_PARSE
+ '{}("{}", data={})'.format(self.__class__.__name__, self.msg, self.data)
+ FMT_NONE
)
def ASSERT_EQ(a1, a2):
if a1 != a2:
raise ParseException("ASSERTION_FAILED: Expected elements to be equal!", (a1, a2))
raise ParseException(
"ASSERTION_FAILED: Expected elements to be equal!", (a1, a2)
)
def ASSERT_LEN(a1, size):
if len(a1) != size:
raise ParseException("ASSERTION_FAILED: Expected {} to be of length {}".format(a1, size), (a1, size))
raise ParseException(
"ASSERTION_FAILED: Expected {} to be of length {}".format(a1, size),
(a1, size),
)
def ASSERT_NOT_NULL(a1):
if a1 is None:
raise ParseException("ASSERTION_FAILED: Expected {} to be non null".format(a1), (a1,))
raise ParseException(
"ASSERTION_FAILED: Expected {} to be non null".format(a1), (a1,)
)
def ASSERT_NOT_IN(a1, a2):
if a1 in a2:
raise ParseException("ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2))
raise ParseException(
"ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2)
)
def ASSERT_IN(a1, a2):
if a1 not in a2:
raise ParseException("ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2))
raise ParseException(
"ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2)
)
class LinkerException(RiscemuBaseException):
@ -64,11 +82,16 @@ class LinkerException(RiscemuBaseException):
self.data = data
def message(self):
return FMT_PARSE + "{}(\"{}\", data={})".format(self.__class__.__name__, self.msg, self.data) + FMT_NONE
return (
FMT_PARSE
+ '{}("{}", data={})'.format(self.__class__.__name__, self.msg, self.data)
+ FMT_NONE
)
# MMU Exceptions
class MemoryAccessException(RiscemuBaseException):
def __init__(self, msg, addr, size, op):
super(MemoryAccessException, self).__init__()
@ -78,13 +101,13 @@ class MemoryAccessException(RiscemuBaseException):
self.op = op
def message(self):
return FMT_MEM + "{}(During {} at 0x{:08x} of size {}: {})".format(
self.__class__.__name__,
self.op,
self.addr,
self.size,
self.msg
) + FMT_NONE
return (
FMT_MEM
+ "{}(During {} at 0x{:08x} of size {}: {})".format(
self.__class__.__name__, self.op, self.addr, self.size, self.msg
)
+ FMT_NONE
)
class OutOfMemoryException(RiscemuBaseException):
@ -92,10 +115,13 @@ class OutOfMemoryException(RiscemuBaseException):
self.action = action
def message(self):
return FMT_MEM + '{}(Ran out of memory during {})'.format(
self.__class__.__name__,
self.action
) + FMT_NONE
return (
FMT_MEM
+ "{}(Ran out of memory during {})".format(
self.__class__.__name__, self.action
)
+ FMT_NONE
)
class InvalidAllocationException(RiscemuBaseException):
@ -106,28 +132,29 @@ class InvalidAllocationException(RiscemuBaseException):
self.flags = flags
def message(self):
return FMT_MEM + '{}[{}](name={}, size={}, flags={})'.format(
self.__class__.__name__,
self.msg,
self.name,
self.size,
self.flags
return FMT_MEM + "{}[{}](name={}, size={}, flags={})".format(
self.__class__.__name__, self.msg, self.name, self.size, self.flags
)
# CPU Exceptions
class UnimplementedInstruction(RiscemuBaseException):
def __init__(self, ins: 'Instruction', context = None):
def __init__(self, ins: "Instruction", context=None):
self.ins = ins
self.context = context
def message(self):
return FMT_CPU + "{}({}{})".format(
self.__class__.__name__,
repr(self.ins),
', context={}'.format(self.context) if self.context is not None else ''
) + FMT_NONE
return (
FMT_CPU
+ "{}({}{})".format(
self.__class__.__name__,
repr(self.ins),
", context={}".format(self.context) if self.context is not None else "",
)
+ FMT_NONE
)
class InvalidRegisterException(RiscemuBaseException):
@ -135,10 +162,11 @@ class InvalidRegisterException(RiscemuBaseException):
self.reg = reg
def message(self):
return FMT_CPU + "{}(Invalid register {})".format(
self.__class__.__name__,
self.reg
) + FMT_NONE
return (
FMT_CPU
+ "{}(Invalid register {})".format(self.__class__.__name__, self.reg)
+ FMT_NONE
)
class InvalidSyscallException(RiscemuBaseException):
@ -146,10 +174,11 @@ class InvalidSyscallException(RiscemuBaseException):
self.scall = scall
def message(self):
return FMT_SYSCALL + "{}(Invalid syscall: {})".format(
self.__class__.__name__,
self.scall
) + FMT_NONE
return (
FMT_SYSCALL
+ "{}(Invalid syscall: {})".format(self.__class__.__name__, self.scall)
+ FMT_NONE
)
def INS_NOT_IMPLEMENTED(ins):
@ -162,10 +191,7 @@ class NumberFormatException(RiscemuBaseException):
self.msg = msg
def message(self):
return "{}({})".format(
self.__class__.__name__,
self.msg
)
return "{}({})".format(self.__class__.__name__, self.msg)
# this exception is not printed and simply signals that an interactive debugging session is

@ -8,6 +8,5 @@ class MemoryFlags:
def __repr__(self):
return "r{}{}".format(
'-' if self.read_only else 'w',
'x' if self.executable else '-'
"-" if self.read_only else "w", "x" if self.executable else "-"
)

@ -33,21 +33,32 @@ class InstructionContext:
self.base_address = 0
self.global_symbol_dict = dict()
def resolve_label(self, symbol: str, address_at: Optional[T_RelativeAddress] = None) -> Optional[T_AbsoluteAddress]:
def resolve_label(
self, symbol: str, address_at: Optional[T_RelativeAddress] = None
) -> Optional[T_AbsoluteAddress]:
if NUMBER_SYMBOL_PATTERN.match(symbol):
if address_at is None:
raise ParseException("Cannot resolve relative symbol {} without an address!".format(symbol))
raise ParseException(
"Cannot resolve relative symbol {} without an address!".format(
symbol
)
)
direction = symbol[-1]
values = self.numbered_labels.get(symbol[:-1], [])
if direction == 'b':
return max((addr + self.base_address for addr in values if addr < address_at), default=None)
if direction == "b":
return max(
(addr + self.base_address for addr in values if addr < address_at),
default=None,
)
else:
return min((addr + self.base_address for addr in values if addr > address_at), default=None)
return min(
(addr + self.base_address for addr in values if addr > address_at),
default=None,
)
else:
# if it's not a local symbol, try the globals
if symbol not in self.labels:
return self.global_symbol_dict.get(symbol, None)
# otherwise return the local symbol
return self.labels.get(symbol, None)

@ -1,11 +1,24 @@
from typing import List
from . import MemorySection, Instruction, InstructionContext, MemoryFlags, T_RelativeAddress
from . import (
MemorySection,
Instruction,
InstructionContext,
MemoryFlags,
T_RelativeAddress,
)
from .exceptions import MemoryAccessException
class InstructionMemorySection(MemorySection):
def __init__(self, instructions: List[Instruction], name: str, context: InstructionContext, owner: str, base: int = 0):
def __init__(
self,
instructions: List[Instruction],
name: str,
context: InstructionContext,
owner: str,
base: int = 0,
):
self.name = name
self.base = base
self.context = context
@ -15,13 +28,27 @@ class InstructionMemorySection(MemorySection):
self.owner = owner
def read(self, offset: T_RelativeAddress, size: int) -> bytearray:
raise MemoryAccessException("Cannot read raw bytes from instruction section", self.base + offset, size, 'read')
raise MemoryAccessException(
"Cannot read raw bytes from instruction section",
self.base + offset,
size,
"read",
)
def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
raise MemoryAccessException("Cannot write raw bytes to instruction section", self.base + offset, size, 'write')
raise MemoryAccessException(
"Cannot write raw bytes to instruction section",
self.base + offset,
size,
"write",
)
def read_ins(self, offset: T_RelativeAddress) -> Instruction:
if offset % 4 != 0:
raise MemoryAccessException("Unaligned instruction fetch!", self.base + offset, 4, 'instruction fetch')
raise MemoryAccessException(
"Unaligned instruction fetch!",
self.base + offset,
4,
"instruction fetch",
)
return self.instructions[offset // 4]

@ -11,13 +11,18 @@ class Int32:
You can use it just like you would any other integer, just be careful when passing it
to functions which actually expect an integer and not a Int32.
"""
_type = c_int32
__slots__ = ('_val',)
__slots__ = ("_val",)
def __init__(self, val: Union[int, c_int32, c_uint32, 'Int32', bytes, bytearray] = 0):
def __init__(
self, val: Union[int, c_int32, c_uint32, "Int32", bytes, bytearray] = 0
):
if isinstance(val, (bytes, bytearray)):
signed = len(val) == 4 and self._type == c_int32
self._val = self.__class__._type(int.from_bytes(val, 'little', signed=signed))
self._val = self.__class__._type(
int.from_bytes(val, "little", signed=signed)
)
elif isinstance(val, self.__class__._type):
self._val = val
elif isinstance(val, (c_uint32, c_int32, Int32)):
@ -26,21 +31,23 @@ class Int32:
self._val = self.__class__._type(val)
else:
raise RuntimeError(
"Unknonw {} input type: {} ({})".format(self.__class__.__name__, type(val), val)
"Unknonw {} input type: {} ({})".format(
self.__class__.__name__, type(val), val
)
)
def __add__(self, other: Union['Int32', int]):
def __add__(self, other: Union["Int32", int]):
if isinstance(other, Int32):
other = other.value
return self.__class__(self._val.value + other)
def __sub__(self, other: Union['Int32', int]):
def __sub__(self, other: Union["Int32", int]):
if isinstance(other, Int32):
other = other.value
return self.__class__(self._val.value - other)
def __mul__(self, other: Union['Int32', int]):
def __mul__(self, other: Union["Int32", int]):
if isinstance(other, Int32):
other = other.value
return self.__class__(self._val.value * other)
@ -53,37 +60,37 @@ class Int32:
other = other.value
return self.__class__(self.value // other)
def __mod__(self, other: Union['Int32', int]):
def __mod__(self, other: Union["Int32", int]):
if isinstance(other, Int32):
other = other.value
return self.__class__(self._val.value % other)
def __and__(self, other: Union['Int32', int]):
def __and__(self, other: Union["Int32", int]):
if isinstance(other, Int32):
other = other.value
return self.__class__(self._val.value & other)
def __or__(self, other: Union['Int32', int]):
def __or__(self, other: Union["Int32", int]):
if isinstance(other, Int32):
other = other.value
return self.__class__(self._val.value | other)
def __xor__(self, other: Union['Int32', int]):
def __xor__(self, other: Union["Int32", int]):
if isinstance(other, Int32):
other = other.value
return self.__class__(self._val.value ^ other)
def __lshift__(self, other: Union['Int32', int]):
def __lshift__(self, other: Union["Int32", int]):
if isinstance(other, Int32):
other = other.value
return self.__class__(self.value << other)
def __rshift__(self, other: Union['Int32', int]):
def __rshift__(self, other: Union["Int32", int]):
if isinstance(other, Int32):
other = other.value
return self.__class__(self.value >> other)
def __eq__(self, other: Union['Int32', int]):
def __eq__(self, other: Union["Int32", int]):
if isinstance(other, Int32):
other = other.value
return self.value == other
@ -98,7 +105,7 @@ class Int32:
return self.to_bytes(4)
def __repr__(self):
return '{}({})'.format(self.__class__.__name__, self.value)
return "{}({})".format(self.__class__.__name__, self.value)
def __str__(self):
return str(self.value)
@ -174,7 +181,7 @@ class Int32:
"""
return self._val.value
def unsigned(self) -> 'UInt32':
def unsigned(self) -> "UInt32":
"""
Convert to an unsigned representation. See :class:Uint32
:return:
@ -188,9 +195,9 @@ class Int32:
:param bytes: The length of the bytearray
:return: A little-endian representation of the contained integer
"""
return bytearray(self.unsigned_value.to_bytes(4, 'little'))[0:bytes]
return bytearray(self.unsigned_value.to_bytes(4, "little"))[0:bytes]
def signed(self) -> 'Int32':
def signed(self) -> "Int32":
"""
Convert to a signed representation. See :class:Int32
:return:
@ -207,7 +214,7 @@ class Int32:
"""
return c_uint32(self.value).value
def shift_right_logical(self, ammount: Union['Int32', int]) -> 'Int32':
def shift_right_logical(self, ammount: Union["Int32", int]) -> "Int32":
"""
This function implements logical right shifts, meaning that the sign bit is shifted as well.
@ -237,12 +244,12 @@ class Int32:
:return: An instance of Int32, holding the sign-extended value
"""
if isinstance(data, (bytes, bytearray)):
data = int.from_bytes(data, 'little')
data = int.from_bytes(data, "little")
sign = data >> (bits - 1)
if sign > 1:
print("overflow in Int32.sext!")
if sign:
data = (data & (2 ** (bits - 1) - 1)) - 2**(bits-1)
data = (data & (2 ** (bits - 1) - 1)) - 2 ** (bits - 1)
return cls(data)
@ -250,9 +257,10 @@ class UInt32(Int32):
"""
An unsigned version of :class:Int32.
"""
_type = c_uint32
def unsigned(self) -> 'UInt32':
def unsigned(self) -> "UInt32":
"""
Return a new instance representing the same bytes, but signed
:return:
@ -263,7 +271,7 @@ class UInt32(Int32):
def unsigned_value(self) -> int:
return self._val.value
def shift_right_logical(self, ammount: Union['Int32', int]) -> 'UInt32':
def shift_right_logical(self, ammount: Union["Int32", int]) -> "UInt32":
"""
see :meth:`Int32.shift_right_logical <Int32.shift_right_logical>`

@ -4,7 +4,14 @@ from typing import Optional
from ..colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_ERROR
from ..helpers import format_bytes
from . import MemoryFlags, T_AbsoluteAddress, InstructionContext, T_RelativeAddress, Instruction, Int32
from . import (
MemoryFlags,
T_AbsoluteAddress,
InstructionContext,
T_RelativeAddress,
Instruction,
Int32,
)
@dataclass
@ -32,8 +39,16 @@ class MemorySection(ABC):
def read_ins(self, offset: T_RelativeAddress) -> Instruction:
pass
def dump(self, start: T_RelativeAddress, end: Optional[T_RelativeAddress] = None, fmt: str = None,
bytes_per_row: int = None, rows: int = 10, group: int = None, highlight: int = None):
def dump(
self,
start: T_RelativeAddress,
end: Optional[T_RelativeAddress] = None,
fmt: str = None,
bytes_per_row: int = None,
rows: int = 10,
group: int = None,
highlight: int = None,
):
"""
Dump the section. If no end is given, the rows around start are printed and start is highlighted.
@ -54,11 +69,11 @@ class MemorySection(ABC):
if fmt is None:
if self.flags.executable and self.flags.read_only:
bytes_per_row = 4
fmt = 'asm'
fmt = "asm"
else:
fmt = 'hex'
fmt = "hex"
if fmt == 'char':
if fmt == "char":
if bytes_per_row is None:
bytes_per_row = 4
if group is None:
@ -70,47 +85,72 @@ class MemorySection(ABC):
if bytes_per_row is None:
bytes_per_row = 4
if fmt not in ('asm', 'hex', 'int', 'char'):
print(FMT_ERROR + '[MemorySection] Unknown format {}, known formats are {}'.format(
fmt, ", ".join(('asm', 'hex', 'int', 'char'))
) + FMT_NONE)
if fmt not in ("asm", "hex", "int", "char"):
print(
FMT_ERROR
+ "[MemorySection] Unknown format {}, known formats are {}".format(
fmt, ", ".join(("asm", "hex", "int", "char"))
)
+ FMT_NONE
)
if end is None:
end = min(start + (bytes_per_row * (rows // 2)), self.size)
highlight = start
start = max(0, start - (bytes_per_row * (rows // 2)))
if fmt == 'asm':
print(FMT_MEM + "{}, viewing {} instructions:".format(
self, (end - start) // 4
) + FMT_NONE)
if fmt == "asm":
print(
FMT_MEM
+ "{}, viewing {} instructions:".format(self, (end - start) // 4)
+ FMT_NONE
)
for addr in range(start, end, 4):
if addr == highlight:
print(FMT_UNDERLINE + FMT_ORANGE, end='')
print("0x{:04x}: {}{}".format(
self.base + addr, self.read_ins(addr), FMT_NONE
))
print(FMT_UNDERLINE + FMT_ORANGE, end="")
print(
"0x{:04x}: {}{}".format(
self.base + addr, self.read_ins(addr), FMT_NONE
)
)
else:
print(FMT_MEM + "{}, viewing {} bytes:".format(
self, (end - start)
) + FMT_NONE)
print(
FMT_MEM + "{}, viewing {} bytes:".format(self, (end - start)) + FMT_NONE
)
aligned_end = end - (end % bytes_per_row) if end % bytes_per_row != 0 else end
aligned_end = (
end - (end % bytes_per_row) if end % bytes_per_row != 0 else end
)
for addr in range(start, aligned_end, bytes_per_row):
hi_ind = (highlight - addr) // group if highlight is not None else -1
print("0x{:04x}: {}{}".format(
self.base + addr, format_bytes(self.read(addr, bytes_per_row), fmt, group, hi_ind), FMT_NONE
))
print(
"0x{:04x}: {}{}".format(
self.base + addr,
format_bytes(
self.read(addr, bytes_per_row), fmt, group, hi_ind
),
FMT_NONE,
)
)
if aligned_end != end:
hi_ind = (highlight - aligned_end) // group if highlight is not None else -1
print("0x{:04x}: {}{}".format(
self.base + aligned_end, format_bytes(
self.read(aligned_end, end % bytes_per_row), fmt, group, hi_ind
), FMT_NONE
))
hi_ind = (
(highlight - aligned_end) // group if highlight is not None else -1
)
print(
"0x{:04x}: {}{}".format(
self.base + aligned_end,
format_bytes(
self.read(aligned_end, end % bytes_per_row),
fmt,
group,
hi_ind,
),
FMT_NONE,
)
)
def dump_all(self, *args, **kwargs):
self.dump(0, self.size, *args, **kwargs)
@ -122,5 +162,5 @@ class MemorySection(ABC):
self.base,
self.size,
self.flags,
self.owner
self.owner,
)

@ -14,6 +14,7 @@ class Program:
the offset in the program, and everything will be taken care of for you.
"""
name: str
context: InstructionContext
global_labels: Set[str]
@ -44,9 +45,13 @@ class Program:
if self.base is not None:
if sec.base < self.base:
print(
FMT_RED + FMT_BOLD + "WARNING: memory section {} in {} is placed before program base (0x{:x})".format(
FMT_RED
+ FMT_BOLD
+ "WARNING: memory section {} in {} is placed before program base (0x{:x})".format(
sec, self.name, self.base
) + FMT_NONE)
)
+ FMT_NONE
)
self.sections.append(sec)
# keep section list ordered
@ -54,18 +59,21 @@ class Program:
def __repr__(self):
return "{}(name={},sections={},base={})".format(
self.__class__.__name__, self.name, self.global_labels,
[s.name for s in self.sections], self.base
self.__class__.__name__,
self.name,
self.global_labels,
[s.name for s in self.sections],
self.base,
)
@property
def entrypoint(self):
if '_start' in self.context.labels:
return self.context.labels.get('_start')
if 'main' in self.context.labels:
return self.context.labels.get('main')
if "_start" in self.context.labels:
return self.context.labels.get("_start")
if "main" in self.context.labels:
return self.context.labels.get("main")
for sec in self.sections:
if get_section_base_name(sec.name) == '.text' and sec.flags.executable:
if get_section_base_name(sec.name) == ".text" and sec.flags.executable:
return sec.base
def loaded_trigger(self, at_addr: T_AbsoluteAddress):
@ -81,12 +89,17 @@ class Program:
"""
if self.is_loaded:
if at_addr != self.base:
raise RuntimeError("Program loaded twice at different addresses! This will probably break things!")
raise RuntimeError(
"Program loaded twice at different addresses! This will probably break things!"
)
return
if self.base is not None and self.base != at_addr:
print(FMT_MEM + 'WARNING: Program loaded at different address then expected! (loaded at {}, '
'but expects to be loaded at {})'.format(at_addr, self.base) + FMT_NONE)
print(
FMT_MEM
+ "WARNING: Program loaded at different address then expected! (loaded at {}, "
"but expects to be loaded at {})".format(at_addr, self.base) + FMT_NONE
)
# check if we are relocating
if self.base != at_addr:

@ -39,7 +39,7 @@ class ProgramLoader(ABC):
pass
@classmethod
def instantiate(cls, source_path: str, options: T_ParserOpts) -> 'ProgramLoader':
def instantiate(cls, source_path: str, options: T_ParserOpts) -> "ProgramLoader":
"""
Instantiate a loader for the given source file with the required arguments

@ -5,8 +5,13 @@ from ..helpers import parse_numeric_argument
class SimpleInstruction(Instruction):
def __init__(self, name: str, args: Union[Tuple[()], Tuple[str], Tuple[str, str], Tuple[str, str, str]],
context: InstructionContext, addr: T_RelativeAddress):
def __init__(
self,
name: str,
args: Union[Tuple[()], Tuple[str], Tuple[str, str], Tuple[str, str, str]],
context: InstructionContext,
addr: T_RelativeAddress,
):
self.context = context
self.name = name
self.args = args
@ -23,4 +28,3 @@ class SimpleInstruction(Instruction):
def get_reg(self, num: int) -> str:
return self.args[num]

@ -1,3 +1,3 @@
from .test_tokenizer import *
from .test_helpers import *
from .test_integers import *
from .test_integers import *

@ -1,6 +1,6 @@
from riscemu.helpers import *
def test_align_address():
assert align_addr(3, 1) == 3
assert align_addr(3, 2) == 4
@ -10,6 +10,6 @@ def test_align_address():
def test_parse_numeric():
assert parse_numeric_argument('13') == 13
assert parse_numeric_argument('0x100') == 256
assert parse_numeric_argument('-13') == -13
assert parse_numeric_argument("13") == 13
assert parse_numeric_argument("0x100") == 256
assert parse_numeric_argument("-13") == -13

@ -38,36 +38,44 @@ def get_arg_from_ins(ins: Instruction, num: int, cpu: CPU):
assert_ops = {
'==': assert_equals,
'!=': _not(assert_equals),
'in': assert_in,
'not_in': _not(assert_in),
"==": assert_equals,
"!=": _not(assert_equals),
"in": assert_in,
"not_in": _not(assert_in),
}
class Z_test(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!')
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)
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)
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))
print(FMT_SUCCESS + "[TestCase] 🟢 passed assertion {}".format(ins))
else:
print(FMT_ERROR + '[TestCase] 🔴 failed assertion {}'.format(ins))
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
print(FMT_ERROR + "[TestCase] 🔴 reached fail instruction! {}".format(ins))
self.cpu.halted = True
self.failed = True

@ -1,7 +1,14 @@
from unittest import TestCase
from riscemu.tokenizer import tokenize, print_tokens, Token, TokenType, NEWLINE, COMMA, \
split_whitespace_respecting_quotes
from riscemu.tokenizer import (
tokenize,
print_tokens,
Token,
TokenType,
NEWLINE,
COMMA,
split_whitespace_respecting_quotes,
)
def ins(name: str) -> Token:
@ -21,41 +28,49 @@ def lbl(name: str) -> Token:
class TestTokenizer(TestCase):
def test_instructions(self):
program = [
'li a0, 144',
'divi a0, a0, 12',
'xori a1, a0, 12'
]
program = ["li a0, 144", "divi a0, a0, 12", "xori a1, a0, 12"]
tokens = [
ins('li'), arg('a0'), COMMA, arg('144'), NEWLINE,
ins('divi'), arg('a0'), COMMA, arg('a0'), COMMA, arg('12'), NEWLINE,
ins('xori'), arg('a1'), COMMA, arg('a0'), COMMA, arg('12'), NEWLINE,
ins("li"),
arg("a0"),
COMMA,
arg("144"),
NEWLINE,
ins("divi"),
arg("a0"),
COMMA,
arg("a0"),
COMMA,
arg("12"),
NEWLINE,
ins("xori"),
arg("a1"),
COMMA,
arg("a0"),
COMMA,
arg("12"),
NEWLINE,
]
self.assertEqual(list(tokenize(program)), tokens)
def test_comments(self):
parsed_res = [
ins('li'), arg('a0'), COMMA, arg('144'), NEWLINE
]
for c in ('#', '//', ';'):
lines = [
c + ' this is a comment',
'li a0, 144'
]
parsed_res = [ins("li"), arg("a0"), COMMA, arg("144"), NEWLINE]
for c in ("#", "//", ";"):
lines = [c + " this is a comment", "li a0, 144"]
self.assertEqual(list(tokenize(lines)), parsed_res)
def test_pseudo_ins(self):
parsed_res = [
Token(TokenType.PSEUDO_OP, '.section'), Token(TokenType.ARGUMENT, '.text'), NEWLINE,
Token(TokenType.PSEUDO_OP, '.type'), Token(TokenType.ARGUMENT, 'init'), COMMA,
Token(TokenType.ARGUMENT, '@function'), NEWLINE
]
input_program = [
'.section .text',
'.type init, @function'
Token(TokenType.PSEUDO_OP, ".section"),
Token(TokenType.ARGUMENT, ".text"),
NEWLINE,
Token(TokenType.PSEUDO_OP, ".type"),
Token(TokenType.ARGUMENT, "init"),
COMMA,
Token(TokenType.ARGUMENT, "@function"),
NEWLINE,
]
input_program = [".section .text", ".type init, @function"]
self.assertEqual(list(tokenize(input_program)), parsed_res)
def test_full_program(self):
@ -71,24 +86,40 @@ section:
sub s0, s0, s0
"""
tokens = [
op('.section'), arg('.text'), NEWLINE,
ins('addi'), arg('sp'), COMMA, arg('sp'), COMMA, arg('-32'), NEWLINE,
ins('sw'), arg('s0'), COMMA, arg('ra'), arg('0'), NEWLINE,
lbl('section:'), NEWLINE,
ins('sub'), arg('s0'), COMMA, arg('s0'), COMMA, arg('s0'), NEWLINE
op(".section"),
arg(".text"),
NEWLINE,
ins("addi"),
arg("sp"),
COMMA,
arg("sp"),
COMMA,
arg("-32"),
NEWLINE,
ins("sw"),
arg("s0"),
COMMA,
arg("ra"),
arg("0"),
NEWLINE,
lbl("section:"),
NEWLINE,
ins("sub"),
arg("s0"),
COMMA,
arg("s0"),
COMMA,
arg("s0"),
NEWLINE,
]
self.assertEqual(list(tokenize(program.splitlines())), tokens)
def test_split_whitespace_respecting_quotes_single(self):
self.assertEqual(
list(split_whitespace_respecting_quotes("test")), ["test"]
)
self.assertEqual(list(split_whitespace_respecting_quotes("test")), ["test"])
def test_split_whitespace_respecting_quotes_empty(self):
self.assertEqual(
list(split_whitespace_respecting_quotes("")), []
)
self.assertEqual(list(split_whitespace_respecting_quotes("")), [])
def test_split_whitespace_respecting_quotes_two_parts(self):
self.assertEqual(
@ -107,20 +138,24 @@ section:
def test_split_whitespace_respecting_quotes_quoted_then_normal(self):
self.assertEqual(
list(split_whitespace_respecting_quotes('"test 123" abc')), ["test 123", "abc"]
list(split_whitespace_respecting_quotes('"test 123" abc')),
["test 123", "abc"],
)
def test_split_whitespace_respecting_quotes_quoted_sorrounded(self):
self.assertEqual(
list(split_whitespace_respecting_quotes('hello "test 123" abc')), ["hello", "test 123", "abc"]
list(split_whitespace_respecting_quotes('hello "test 123" abc')),
["hello", "test 123", "abc"],
)
def test_split_whitespace_respecting_quotes_weird_spaces(self):
self.assertEqual(
list(split_whitespace_respecting_quotes('hello "test 123"\tabc')), ["hello", "test 123", "abc"]
list(split_whitespace_respecting_quotes('hello "test 123"\tabc')),
["hello", "test 123", "abc"],
)
def test_split_whitespace_respecting_quotes_quotes_no_spaces(self):
self.assertEqual(
list(split_whitespace_respecting_quotes('hello"test 123"abc')), ["hello", "test 123", "abc"]
list(split_whitespace_respecting_quotes('hello"test 123"abc')),
["hello", "test 123", "abc"],
)

Loading…
Cancel
Save