Compare commits

...

9 Commits

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (riscemu)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (riscemu)" project-jdk-type="Python SDK" />
</project> </project>

@ -2,10 +2,13 @@
## Upcoming 2.0.6 ## Upcoming 2.0.6
- Added a very basic libc containing a `crt0.s`, and a few functions
such as `malloc`, `rand`, and `memcpy`.
- Added a subset of the `mmap2` syscall (code 192) to allocate new memory
- Refactored the launching code to improve using riscemu from code
**Planned:** **Planned:**
- Add a floating point unit - Add a floating point unit
- Add a crt0.s
- Add `mmap2` syscall with code 192
## 2.0.5 ## 2.0.5

@ -1,23 +1,23 @@
; Example program (c) by Anton Lydike // Example program (c) by Anton Lydike
; this calculates the fibonacci sequence and stores it in ram // this calculates the fibonacci sequence and stores it in ram
.data .data
fibs: .space 56 fibs: .space 56
.text .text
main: main:
addi s1, zero, 0 ; storage index addi s1, zero, 0 // storage index
addi s2, zero, 56 ; last storage index addi s2, zero, 56 // last storage index
addi t0, zero, 1 ; t0 = F_{i} addi t0, zero, 1 // t0 = F_{i}
addi t1, zero, 1 ; t1 = F_{i+1} addi t1, zero, 1 // t1 = F_{i+1}
loop: loop:
sw t0, fibs(s1) ; save sw t0, fibs(s1) // save
add t2, t1, t0 ; t2 = F_{i+2} add t2, t1, t0 // t2 = F_{i+2}
addi t0, t1, 0 ; t0 = t1 addi t0, t1, 0 // t0 = t1
addi t1, t2, 0 ; t1 = t2 addi t1, t2, 0 // t1 = t2
addi s1, s1, 4 ; increment storage pointer addi s1, s1, 4 // increment storage pointer
blt s1, s2, loop ; loop as long as we did not reach array length blt s1, s2, loop // loop as long as we did not reach array length
; exit gracefully // exit gracefully
addi a0, zero, 0 addi a0, zero, 0
addi a7, zero, 93 addi a7, zero, 93
scall ; exit with code 0 scall // exit with code 0

@ -0,0 +1,44 @@
// example of a simple memory allocation
// we use the mmap2 syscall for this
.text
// call mmap2
li a0, 0 // addr = 0, let OS choose address
li a1, 4096 // size
li a2, 3 // PROT_READ | PROT_WRITE
li a3, 5 // MAP_PRIVATE | MAP_ANONYMOUS
li a7, SCALL_MMAP2
ecall // invoke syscall
li t0, -1 // exit if unsuccessful
beq a0, t0, _exit
// print address
print.uhex a0
# we can look at the state of the mmu here:
ebreak
# > mmu.sections
# InstructionMemorySection[.text] at 0x00000100
# BinaryDataMemorySection[.stack] at 0x00000170
# BinaryDataMemorySection[.data.runtime-allocated] at 0x00080170
sw t0, 144(a0)
sw t0, 0(a0)
sw t0, 8(a0)
sw t0, 16(a0)
sw t0, 32(a0)
sw t0, 64(a0)
sw t0, 128(a0)
sw t0, 256(a0)
sw t0, 512(a0)
sw t0, 1024(a0)
sw t0, 2048(a0)
sw t0, 4000(a0)
lw t1, 128(a0)
print.uhex t0
ebreak
_exit:
li a7, 93
ecall

@ -26,6 +26,4 @@ Somewhat nice implementations of:
## Correctness: ## Correctness:
This library is 100% untested. Feel free to report bugs using github issues. I'm sure ther are many. We are working on testing! This library is only lightly tested, so be careful and report bugs when you find them!

@ -96,7 +96,11 @@ free:
// s0 = &_atexit_count // s0 = &_atexit_count
// s2 = &_atexit_calls // s2 = &_atexit_calls
// s1 = updated value of atexit // s1 = updated value of atexit
// s3 = exit code
exit: exit:
// save exit code to s3
mv s3, a0
_exit_start:
la s0, _atexit_count // s0 = &_atexit_count la s0, _atexit_count // s0 = &_atexit_count
lw s1, 0(s0) // s1 = *(&_atexit_count) lw s1, 0(s0) // s1 = *(&_atexit_count)
// exit if no atexit() calls remain // exit if no atexit() calls remain
@ -108,12 +112,12 @@ exit:
li s2, _atexit_calls li s2, _atexit_calls
add s1, s1, s2 // s1 = &_atexit_calls + (s1) add s1, s1, s2 // s1 = &_atexit_calls + (s1)
lw s1, 0(s1) // s1 = *s1 lw s1, 0(s1) // s1 = *s1
la ra, exit // set ra up to point to exit la ra, _exit_start // set ra up to point to exit
jalr zero, s1, 0 // jump to address in s1 jalr zero, s1, 0 // jump to address in s1
// jalr will call the other function, which will then return back // jalr will call the other function, which will then return back
// to the beginning of exit. // to the beginning of exit.
_exit: _exit:
li a0, 0 mv a0, s3
li a7, 93 li a7, 93
ecall ecall
@ -172,5 +176,3 @@ srand:
la t1, _rand_seed la t1, _rand_seed
sw a0, 0(t1) sw a0, 0(t1)
ret ret

@ -3,8 +3,9 @@
// Copyright (c) 2023 Anton Lydike // Copyright (c) 2023 Anton Lydike
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Create NullPtr constant
.equ NULL, 0x00
.global NULL .global NULL
// A global constant representing zero
.global strlen .global strlen
@ -35,15 +36,13 @@
// copies the character c to the first n characters of str. // copies the character c to the first n characters of str.
// missing implementations
//.global memcmp //.global memcmp
//.global memcpy //.global memcpy
//.global strcat //.global strcat
// Create NullPtr constant
.set NULL, 0x00
.text .text
strlen: strlen:
// size_t strlen(char* str) // size_t strlen(char* str)
// push s1, s2 to the stack // push s1, s2 to the stack
@ -101,7 +100,7 @@ __strncpy_end:
strcpy: strcpy:
// char *strncpy(char *dest, const char *src) // char *strcpy(char *dest, const char *src)
sw s1, sp, -4 // push s1 to the stack sw s1, sp, -4 // push s1 to the stack
sw s2, sp, -8 // push s1 to the stack sw s2, sp, -8 // push s1 to the stack
add s1, a0, zero // save dest pointer for return add s1, a0, zero // save dest pointer for return
@ -124,15 +123,16 @@ __strcpy_loop:
memchr: memchr:
// void *memchr(const void *str, char c, size_t n) // void *memchr(const void *str, char c, size_t n)
sw s1, sp, -4 // push s1 to the stack sw s1, sp, -4 // push s1 to the stack
andi a1, a1, 0xff // trim a1 to be byte-sized
__memchr_loop: __memchr_loop:
beq a2, zero, __memchr_ret_null beq a2, zero, __memchr_ret_null
lb s1, a0, 0 lb s1, a0, 0
addi a0, a0, 1 // let a0 point to the next byte addi a0, a0, 1 // let a0 point to the next byte
addi a2, a2, -1 // decrement bytes to copy by 1 addi a2, a2, -1 // decrement bytes to copy by 1
bne s1, s2, __memchr_loop bne s1, a1, __memchr_loop
// return pointer to prev byte (as the prev byte actually matched a1) // return pointer to prev byte (as the prev byte actually matched a1)
addi a0, a0, -1 addi a0, a0, -1
// pop s1, s2 from stack // pop s1, from stack
lw s1, sp, -4 lw s1, sp, -4
ret ret
__memchr_ret_null: __memchr_ret_null:

@ -72,7 +72,7 @@ class MMU:
return sec return sec
return None return None
def get_bin_containing(self, addr: T_AbsoluteAddress) -> Optional[Program]: def get_program_at_addr(self, addr: T_AbsoluteAddress) -> Optional[Program]:
for program in self.programs: for program in self.programs:
if program.base <= addr < program.base + program.size: if program.base <= addr < program.base + program.size:
return program return program
@ -192,7 +192,7 @@ class MMU:
if not sec: if not sec:
return "unknown at 0x{:0x}".format(address) return "unknown at 0x{:0x}".format(address)
bin = self.get_bin_containing(address) bin = self.get_program_at_addr(address)
secs = set(sec.name for sec in bin.sections) if bin else [] secs = set(sec.name for sec in bin.sections) if bin else []
elf_markers = { elf_markers = {
"__global_pointer$", "__global_pointer$",
@ -347,3 +347,13 @@ class MMU:
return sec.context return sec.context
return InstructionContext() return InstructionContext()
def find_entrypoint(self) -> int | None:
# try to find the global entrypoint
if "_start" in self.global_symbols:
return self.global_symbols["_start"]
# otherwise find a main (that's not necessarily global)
for p in self.programs:
if "main" in p.context.labels:
return p.context.resolve_label("main")
return None

@ -33,5 +33,5 @@ from .config import RunConfig
from .parser import tokenize, parse_tokens, AssemblyFileLoader from .parser import tokenize, parse_tokens, AssemblyFileLoader
__author__ = "Anton Lydike <Anton@Lydike.com>" __author__ = "Anton Lydike <Anton@Lydike.com>"
__copyright__ = "Copyright 2022 Anton Lydike" __copyright__ = "Copyright 2023 Anton Lydike"
__version__ = "2.0.5" __version__ = "2.0.5"

@ -5,181 +5,17 @@ SPDX-License-Identifier: MIT
This file holds the logic for starting the emulator from the CLI This file holds the logic for starting the emulator from the CLI
""" """
from riscemu import RiscemuBaseException, __copyright__, __version__ import sys
from riscemu.CPU import UserModeCPU
if __name__ == "__main__": from riscemu import RiscemuBaseException
from .config import RunConfig from riscemu.riscemu_main import RiscemuMain
from .instructions import InstructionSetDict
from .colors import FMT_BOLD, FMT_MAGENTA
from .parser import AssemblyFileLoader
import argparse
import sys
all_ins_names = list(InstructionSetDict.keys()) try:
main = RiscemuMain()
main.run(sys.argv[1:])
sys.exit(main.cpu.exit_code if not main.cfg.ignore_exit_code else 0)
if "--version" in sys.argv: except RiscemuBaseException as e:
print(
"riscemu version {}\n{}\n\nAvailable ISA: {}".format(
__version__, __copyright__, ", ".join(InstructionSetDict.keys())
)
)
sys.exit()
class OptionStringAction(argparse.Action):
def __init__(self, option_strings, dest, keys=None, omit_empty=False, **kwargs):
if keys is None:
raise ValueError('must define "keys" argument')
if isinstance(keys, dict):
keys_d = keys
elif isinstance(keys, (list, tuple)):
keys_d = {}
for k in keys:
if isinstance(k, tuple):
k, v = k
else:
v = False
keys_d[k] = v
else:
keys_d = dict()
super().__init__(option_strings, dest, default=keys_d, **kwargs)
self.keys = keys_d
self.omit_empty = omit_empty
def __call__(self, parser, namespace, values, option_string=None):
d = {}
if not self.omit_empty:
d.update(self.keys)
for x in values.split(","):
if x in self.keys:
d[x] = True
else:
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.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""",
)
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(
"--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(
"--interactive",
help="Launch the interactive debugger instantly instead of loading any "
"programs",
action="store_true",
)
parser.add_argument(
"--ignore-exit-code",
help="Ignore exit code of the program and always return 0 if the program ran to completion.",
action="store_true",
default=False,
)
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,
)
for k, v in dict(cfg_dict).items():
if v is None:
del cfg_dict[k]
cfg = RunConfig(**cfg_dict)
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]
try:
cpu = UserModeCPU(ins_to_load, cfg)
opts = AssemblyFileLoader.get_options(sys.argv)
for file in args.files:
loader = AssemblyFileLoader.instantiate(file, opts)
cpu.load_program(loader.parse())
# set up a stack
cpu.setup_stack(cfg.stack_size)
# launch the last loaded program
cpu.launch(cpu.mmu.programs[-1], verbose=cfg.verbosity > 1)
sys.exit(cpu.exit_code if not args.ignore_exit_code else 0)
except RiscemuBaseException as e:
print("Error: {}".format(e.message())) print("Error: {}".format(e.message()))
e.print_stacktrace() e.print_stacktrace()

@ -227,7 +227,7 @@ class AssemblerDirectives:
cls.op_section(token, (token.value,), context) 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) ASSERT_LEN(args, 1)
cls.add_text(args[0], context, op == "ascii") cls.add_text(args[0], context, zero_terminate=(op != "ascii"))
elif op in DATA_OP_SIZES: elif op in DATA_OP_SIZES:
size = DATA_OP_SIZES[op] size = DATA_OP_SIZES[op]
for arg in args: for arg in args:

@ -21,6 +21,6 @@ class RunConfig:
verbosity: int = 0 verbosity: int = 0
slowdown: float = 1 slowdown: float = 1
unlimited_registers: bool = False unlimited_registers: bool = False
# runtime config
use_libc: bool = False
CONFIG = RunConfig() ignore_exit_code: bool = False

@ -31,6 +31,6 @@ if __name__ == "__main__":
cpu.setup_stack() cpu.setup_stack()
cpu.launch(program) cpu.launch()
sys.exit(cpu.exit_code) sys.exit(cpu.exit_code)

@ -142,8 +142,8 @@ class Registers:
+ " ".join(self._reg_repr("a{}".format(i)) for i in range(8)) + " ".join(self._reg_repr("a{}".format(i)) for i in range(8))
) )
def _reg_repr(self, reg: str): def _reg_repr(self, reg: str, name_len=4, fmt="08X"):
txt = "{:4}=0x{:08X}".format(reg, self.get(reg, False)) txt = "{:{}}=0x{:{}}".format(reg, name_len, self.get(reg, False), fmt)
if reg == "fp": if reg == "fp":
reg = "s0" reg = "s0"
if reg == self.last_set: if reg == self.last_set:
@ -285,3 +285,10 @@ class Registers:
:return: The list :return: The list
""" """
return ["zero", "ra", "sp", "gp", "tp", "fp"] return ["zero", "ra", "sp", "gp", "tp", "fp"]
def __repr__(self):
return "<Registers{}>".format(
"{"
+ ", ".join(self._reg_repr("a{}".format(i), 2, "0x") for i in range(8))
+ "}"
)

@ -0,0 +1,254 @@
import argparse
import glob
import os
import sys
from typing import List, Type
from riscemu import AssemblyFileLoader, __version__, __copyright__
from riscemu.types import CPU, ProgramLoader, Program
from riscemu.instructions import InstructionSet, InstructionSetDict
from riscemu.config import RunConfig
from riscemu.CPU import UserModeCPU
class RiscemuMain:
"""
This represents the riscemu API exposed to other programs for better
interoperability.
"""
available_ins_sets: dict[str, type[InstructionSet]]
available_file_loaders: list[type[ProgramLoader]]
cfg: RunConfig | None
cpu: CPU | None
input_files: list[str]
selected_ins_sets: list[Type[InstructionSet]]
def __init__(self):
self.available_ins_sets = dict()
self.selected_ins_sets = []
self.available_file_loaders = []
self.cfg: RunConfig | None = None
self.cpu: CPU | None = None
self.input_files = []
self.selected_ins_sets = []
def instantiate_cpu(self):
self.cpu = UserModeCPU(self.selected_ins_sets, self.cfg)
self.configure_cpu()
def configure_cpu(self):
assert self.cfg is not None
if isinstance(self.cpu, UserModeCPU) and self.cfg.stack_size != 0:
self.cpu.setup_stack(self.cfg.stack_size)
def register_all_arguments(self, parser: argparse.ArgumentParser):
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",
"libc",
"ignore_exit_code",
),
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
libc: Load a libc-like runtime (for malloc, etc.)
ignore_exit_code: Don't exit with the programs exit code.""",
)
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(self.available_ins_sets)
),
keys={k: True for k in self.available_ins_sets},
omit_empty=True,
)
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(
"--interactive",
help="Launch the interactive debugger instantly instead of loading any "
"programs",
action="store_true",
)
parser.add_argument(
"--ignore-exit-code",
help="Ignore exit code of the program and always return 0 if the program ran to completion.",
action="store_true",
default=False,
)
def register_all_isas(self):
self.available_ins_sets.update(InstructionSetDict)
def register_all_program_loaders(self):
self.available_file_loaders.append(AssemblyFileLoader)
def parse_argv(self, argv: list[str]):
parser = argparse.ArgumentParser(
description="RISC-V Userspace emulator",
prog="riscemu",
formatter_class=argparse.RawTextHelpFormatter,
)
if "--version" in argv:
print(
"riscemu version {}\n{}\n\nAvailable ISA: {}".format(
__version__, __copyright__, ", ".join(self.available_ins_sets)
)
)
sys.exit()
self.register_all_arguments(parser)
# parse argv
args = parser.parse_args(argv)
# add ins
if not hasattr(args, "ins"):
setattr(args, "ins", {k: True for k in self.available_ins_sets})
# create RunConfig
self.cfg = self.config_from_parsed_args(args)
# set input files
self.input_files = args.files
# get selected ins sets
self.selected_ins_sets = list(
self.available_ins_sets[name]
for name, selected in args.ins.items()
if selected
)
# if use_libc is given, attach libc to path
if self.cfg.use_libc:
libc_path = os.path.join(
os.path.dirname(__file__),
"..",
"libc",
"*.s",
)
for path in glob.iglob(libc_path):
self.input_files.append(path)
def config_from_parsed_args(self, args: argparse.Namespace) -> RunConfig:
# 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,
use_libc=args.options["libc"],
ignore_exit_code=args.options["ignore_exit_code"],
)
for k, v in dict(cfg_dict).items():
if v is None:
del cfg_dict[k]
return RunConfig(**cfg_dict)
def load_programs(self):
for path in self.input_files:
for loader in self.available_file_loaders:
if not loader.can_parse(path):
continue
programs = loader.instantiate(path, {}).parse()
if isinstance(programs, Program):
programs = [programs]
for p in programs:
self.cpu.mmu.load_program(p)
def run(self, argv: list[str]):
# register everything
self.register_all_isas()
self.register_all_program_loaders()
# parse argv and set up cpu
self.parse_argv(argv)
self.instantiate_cpu()
self.load_programs()
# run the program
self.cpu.launch(self.cfg.verbosity > 1)
class OptionStringAction(argparse.Action):
def __init__(self, option_strings, dest, keys=None, omit_empty=False, **kwargs):
if keys is None:
raise ValueError('must define "keys" argument')
if isinstance(keys, dict):
keys_d = keys
elif isinstance(keys, (list, tuple)):
keys_d = {}
for k in keys:
if isinstance(k, tuple):
k, v = k
else:
v = False
keys_d[k] = v
else:
keys_d = dict()
super().__init__(option_strings, dest, default=keys_d, **kwargs)
self.keys = keys_d
self.omit_empty = omit_empty
def __call__(self, parser, namespace, values, option_string=None):
d = {}
if not self.omit_empty:
d.update(self.keys)
for x in values.split(","):
if x in self.keys:
d[x] = True
else:
raise ValueError("Invalid parameter supplied: " + x)
setattr(namespace, self.dest, d)

@ -31,12 +31,12 @@ class.
""" """
ADDITIONAL_SYMBOLS = { ADDITIONAL_SYMBOLS = {
'MAP_PRIVATE': 1<<0, "MAP_PRIVATE": 1 << 0,
'MAP_SHARED': 1<<1, "MAP_SHARED": 1 << 1,
'MAP_ANON': 1<<2, "MAP_ANON": 1 << 2,
'MAP_ANONYMOUS': 1<<2, "MAP_ANONYMOUS": 1 << 2,
'PROT_READ': 1<<0, "PROT_READ": 1 << 0,
'PROT_WRITE': 1<<1, "PROT_WRITE": 1 << 1,
} }
""" """
A set of additional symbols that are used by various syscalls. A set of additional symbols that are used by various syscalls.
@ -80,7 +80,9 @@ def get_syscall_symbols():
:return: dictionary of all syscall symbols (SCALL_<name> -> id) :return: dictionary of all syscall symbols (SCALL_<name> -> id)
""" """
items: Dict[str, int] = {("SCALL_" + name.upper()): num for num, name in SYSCALLS.items()} items: Dict[str, int] = {
("SCALL_" + name.upper()): num for num, name in SYSCALLS.items()
}
items.update(ADDITIONAL_SYMBOLS) items.update(ADDITIONAL_SYMBOLS)
@ -239,7 +241,7 @@ class SyscallInterface:
Exit syscall. Exits the system with status code a0 Exit syscall. Exits the system with status code a0
""" """
scall.cpu.halted = True scall.cpu.halted = True
scall.cpu.exit_code = scall.cpu.regs.get("a0").value scall.cpu.exit_code = scall.cpu.regs.get("a0").signed().value
def mmap2(self, scall: Syscall): def mmap2(self, scall: Syscall):
""" """
@ -255,10 +257,10 @@ class SyscallInterface:
fd = <ignored> fd = <ignored>
off_t = <ignored> off_t = <ignored>
""" """
addr = scall.cpu.regs.get('a0').unsigned_value addr = scall.cpu.regs.get("a0").unsigned_value
size = scall.cpu.regs.get('a1').unsigned_value size = scall.cpu.regs.get("a1").unsigned_value
prot = scall.cpu.regs.get('a2').unsigned_value prot = scall.cpu.regs.get("a2").unsigned_value
flags = scall.cpu.regs.get('a3').unsigned_value flags = scall.cpu.regs.get("a3").unsigned_value
# error out if prot is not 1 or 3: # error out if prot is not 1 or 3:
# 1 = PROT_READ # 1 = PROT_READ
@ -270,11 +272,11 @@ class SyscallInterface:
size = 4096 * ceil(size / 4096) size = 4096 * ceil(size / 4096)
section = BinaryDataMemorySection( section = BinaryDataMemorySection(
bytearray(size), bytearray(size),
'.data.runtime-allocated', ".data.runtime-allocated",
None, None,
'system', "system",
base=addr, base=addr,
flags=MemoryFlags(read_only=prot != 3, executable=False) flags=MemoryFlags(read_only=prot != 3, executable=False),
) )
# try to insert section # try to insert section

@ -0,0 +1,2 @@
#!/usr/bin/env bash
python3 -m riscemu "$@"

@ -1,13 +1,12 @@
import typing
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import List, Type, Callable, Set, Dict, TYPE_CHECKING from typing import List, Type, Callable, Set, Dict, TYPE_CHECKING, Iterable
from ..registers import Registers from ..registers import Registers
from ..config import RunConfig from ..config import RunConfig
from ..colors import FMT_RED, FMT_NONE, FMT_ERROR, FMT_CPU from ..colors import FMT_NONE, FMT_CPU
from . import T_AbsoluteAddress, Instruction, Program, ProgramLoader from . import T_AbsoluteAddress, Instruction, Program, ProgramLoader
if typing.TYPE_CHECKING: if TYPE_CHECKING:
from ..MMU import MMU from ..MMU import MMU
from ..instructions import InstructionSet from ..instructions import InstructionSet
@ -91,27 +90,26 @@ class CPU(ABC):
def run(self, verbose: bool = False): def run(self, verbose: bool = False):
pass pass
def launch(self, program: Program, verbose: bool = False): def launch(self, verbose: bool = False):
if program not in self.mmu.programs: entrypoint = self.mmu.find_entrypoint()
print(
FMT_ERROR + "[CPU] Cannot launch program that's not loaded!" + FMT_NONE if entrypoint is None:
) entrypoint = self.mmu.programs[0].entrypoint
return
if self.conf.verbosity > 0: if self.conf.verbosity > 0:
print( print(
FMT_CPU FMT_CPU
+ "[CPU] Started running from {}".format( + "[CPU] Started running from {}".format(
self.mmu.translate_address(program.entrypoint) self.mmu.translate_address(entrypoint)
) )
+ FMT_NONE + FMT_NONE
) )
print(program) self.pc = entrypoint
self.pc = program.entrypoint
self.run(verbose) self.run(verbose)
@classmethod @classmethod
@abstractmethod @abstractmethod
def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]: def get_loaders(cls) -> Iterable[Type[ProgramLoader]]:
pass pass
def get_best_loader_for(self, file_name: str) -> Type[ProgramLoader]: def get_best_loader_for(self, file_name: str) -> Type[ProgramLoader]:

@ -10,7 +10,7 @@ setuptools.setup(
version=riscemu.__version__, version=riscemu.__version__,
author=riscemu.__author__, author=riscemu.__author__,
author_email="pip@antonlydike.de", author_email="pip@antonlydike.de",
description="RISC-V userspace and privileged emulator", description="RISC-V userspace and machine mode emulator",
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
url="https://github.com/antonlydike/riscemu", url="https://github.com/antonlydike/riscemu",
@ -31,6 +31,10 @@ setuptools.setup(
"riscemu.priv", "riscemu.priv",
"riscemu.types", "riscemu.types",
], ],
python_requires=">=3.6", package_data={
"riscemu": ["libc/*.s"],
},
scripts=["riscemu/tools/riscemu"],
python_requires=">=3.8",
install_requires=["pyelftools~=0.27"], install_requires=["pyelftools~=0.27"],
) )

@ -0,0 +1,2 @@
.lit_test_times.txt
Output

@ -1,2 +0,0 @@
8.096814e-02 hello-world.asm
9.465098e-02 fibs.asm

@ -1,8 +1,8 @@
// RUN: python3 -m riscemu -v --ignore-exit-code %s || true | filecheck %s // RUN: python3 -m riscemu -v -o ignore_exit_code %s | filecheck %s
.data .data
fibs: .space 1024 fibs: .space 1024
.text .text
main: main:
addi s1, zero, 0 // storage index addi s1, zero, 0 // storage index
addi s2, zero, 1024 // last storage index addi s2, zero, 1024 // last storage index
@ -15,9 +15,10 @@ loop:
addi t1, t2, 0 // t1 = t2 addi t1, t2, 0 // t1 = t2
addi s1, s1, 4 // increment storage pointer addi s1, s1, 4 // increment storage pointer
blt s1, s2, loop // loop as long as we did not reach array length blt s1, s2, loop // loop as long as we did not reach array length
ebreak
// exit gracefully // exit gracefully
add a0, zero, t2 add a0, zero, t2
addi a7, zero, 93 addi a7, zero, 93
scall // exit with code 0 scall // exit with code fibs(n) & 2^32
// CHECK: [CPU] Program exited with code 1265227608 // CHECK: [CPU] Program exited with code 1265227608

@ -0,0 +1,114 @@
// RUN: python3 -m riscemu -v %s -o libc | filecheck %s
.data
data:
.byte 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88
.byte 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00
dest:
.byte 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB
.byte 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB
small_str:
.string "test"
.text
.globl main
main:
// test that strlen(data) == 15
addi sp, sp, -4
sw ra, 0(sp)
la a0, data
// call strlen(data)
jal strlen
li t0, 15
bne a0, t0, _fail
// now memcpy strlen(data)+1 bytes from data to dest
la a0, dest
la a1, data
li a2, 16
// call strncpy(dest, data, 16)
jal strncpy
la a1, dest
// fail because strncpy should return pointer to dest
bne a0, a1, _fail
// check that dest and data are the same
jal check_data_dest_is_same
la a0, dest
li a1, 0x11
li a2, 16
// test that memset(dest) workds
// call memset(dest, 0x11, 16)
jal memset
// check that all of dest is 0x11111111
li t1, 0x11111111
la a0, dest
lw t0, 0(a0)
bne t0, t1, _fail
lw t0, 1(a0)
bne t0, t1, _fail
lw t0, 2(a0)
bne t0, t1, _fail
lw t0, 3(a0)
bne t0, t1, _fail
// test memchr
// test memchr
la a0, data
li a1, 0x55
li a2, 16
// memchr(data, 0x55, 16)
jal memchr
la t0, data
addi t0, t0, 4
// fail if a0 != data+4
bne a0, t0, _fail
la a0, data
li a1, 0x12
li a2, 16
// memchr(data, 0x12, 16)
jal memchr
// check that result is NULL
bne a0, zero, _fail
// test strcpy
la a0, dest
la a1, small_str
// call strcpy(dest, small_str)
jal strcpy
la t0, dest
lw t1, 0(a0)
// ascii for "tset", as risc-v is little endian
li t2, 0x74736574
bne t1, t2, _fail
// return to exit() wrapper
lw ra, 0(sp)
addi sp, sp, 4
li a0, 0
ret
_fail:
ebreak
// fail the test run
li a0, -1
jal exit
check_data_dest_is_same:
la a0, data
la a1, dest
li a2, 4
1:
lw t0, 0(a0)
lw t1, 0(a1)
bne t0, t1, _fail
addi a0, a0, 4
addi a1, a1, 4
addi a2, a2, -1
blt zero, a2, 1b
ret
//CHECK: [CPU] Program exited with code 0

@ -6,4 +6,4 @@ xdsl_src = os.path.dirname(os.path.dirname(config.test_source_root))
config.name = "riscemu" config.name = "riscemu"
config.test_format = lit.formats.ShTest(preamble_commands=[f"cd {xdsl_src}"]) config.test_format = lit.formats.ShTest(preamble_commands=[f"cd {xdsl_src}"])
config.suffixes = ['.asm'] config.suffixes = ['.asm', '.s']

Loading…
Cancel
Save