Compare commits

..

9 Commits

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>

@ -2,10 +2,13 @@
## 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:**
- Add a floating point unit
- Add a crt0.s
- Add `mmap2` syscall with code 192
## 2.0.5

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

@ -6,7 +6,7 @@
.data
_rand_seed:
_rand_seed:
.word 0x76767676
_atexit_calls:
// leave room for 8 atexit handlers here for now
@ -96,7 +96,11 @@ free:
// s0 = &_atexit_count
// s2 = &_atexit_calls
// s1 = updated value of atexit
// s3 = exit code
exit:
// save exit code to s3
mv s3, a0
_exit_start:
la s0, _atexit_count // s0 = &_atexit_count
lw s1, 0(s0) // s1 = *(&_atexit_count)
// exit if no atexit() calls remain
@ -108,12 +112,12 @@ exit:
li s2, _atexit_calls
add s1, s1, s2 // s1 = &_atexit_calls + (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 will call the other function, which will then return back
// to the beginning of exit.
_exit:
li a0, 0
mv a0, s3
li a7, 93
ecall
@ -148,7 +152,7 @@ _atexit_fail:
// rand, srand
@ -172,5 +176,3 @@ srand:
la t1, _rand_seed
sw a0, 0(t1)
ret

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

@ -72,7 +72,7 @@ class MMU:
return sec
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:
if program.base <= addr < program.base + program.size:
return program
@ -192,7 +192,7 @@ class MMU:
if not sec:
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 []
elf_markers = {
"__global_pointer$",
@ -347,3 +347,13 @@ class MMU:
return sec.context
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
__author__ = "Anton Lydike <Anton@Lydike.com>"
__copyright__ = "Copyright 2022 Anton Lydike"
__copyright__ = "Copyright 2023 Anton Lydike"
__version__ = "2.0.5"

@ -5,182 +5,18 @@ SPDX-License-Identifier: MIT
This file holds the logic for starting the emulator from the CLI
"""
from riscemu import RiscemuBaseException, __copyright__, __version__
from riscemu.CPU import UserModeCPU
import sys
if __name__ == "__main__":
from .config import RunConfig
from .instructions import InstructionSetDict
from .colors import FMT_BOLD, FMT_MAGENTA
from .parser import AssemblyFileLoader
import argparse
import sys
from riscemu import RiscemuBaseException
from riscemu.riscemu_main import RiscemuMain
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:
print(
"riscemu version {}\n{}\n\nAvailable ISA: {}".format(
__version__, __copyright__, ", ".join(InstructionSetDict.keys())
)
)
sys.exit()
except RiscemuBaseException as e:
print("Error: {}".format(e.message()))
e.print_stacktrace()
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()))
e.print_stacktrace()
sys.exit(-1)
sys.exit(-1)

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

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

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

@ -142,8 +142,8 @@ class Registers:
+ " ".join(self._reg_repr("a{}".format(i)) for i in range(8))
)
def _reg_repr(self, reg: str):
txt = "{:4}=0x{:08X}".format(reg, self.get(reg, False))
def _reg_repr(self, reg: str, name_len=4, fmt="08X"):
txt = "{:{}}=0x{:{}}".format(reg, name_len, self.get(reg, False), fmt)
if reg == "fp":
reg = "s0"
if reg == self.last_set:
@ -285,3 +285,10 @@ class Registers:
:return: The list
"""
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 = {
'MAP_PRIVATE': 1<<0,
'MAP_SHARED': 1<<1,
'MAP_ANON': 1<<2,
'MAP_ANONYMOUS': 1<<2,
'PROT_READ': 1<<0,
'PROT_WRITE': 1<<1,
"MAP_PRIVATE": 1 << 0,
"MAP_SHARED": 1 << 1,
"MAP_ANON": 1 << 2,
"MAP_ANONYMOUS": 1 << 2,
"PROT_READ": 1 << 0,
"PROT_WRITE": 1 << 1,
}
"""
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)
"""
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)
@ -239,7 +241,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").signed().value
def mmap2(self, scall: Syscall):
"""
@ -255,10 +257,10 @@ class SyscallInterface:
fd = <ignored>
off_t = <ignored>
"""
addr = scall.cpu.regs.get('a0').unsigned_value
size = scall.cpu.regs.get('a1').unsigned_value
prot = scall.cpu.regs.get('a2').unsigned_value
flags = scall.cpu.regs.get('a3').unsigned_value
addr = scall.cpu.regs.get("a0").unsigned_value
size = scall.cpu.regs.get("a1").unsigned_value
prot = scall.cpu.regs.get("a2").unsigned_value
flags = scall.cpu.regs.get("a3").unsigned_value
# error out if prot is not 1 or 3:
# 1 = PROT_READ
@ -270,11 +272,11 @@ class SyscallInterface:
size = 4096 * ceil(size / 4096)
section = BinaryDataMemorySection(
bytearray(size),
'.data.runtime-allocated',
".data.runtime-allocated",
None,
'system',
"system",
base=addr,
flags=MemoryFlags(read_only=prot != 3, executable=False)
flags=MemoryFlags(read_only=prot != 3, executable=False),
)
# 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 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 ..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
if typing.TYPE_CHECKING:
if TYPE_CHECKING:
from ..MMU import MMU
from ..instructions import InstructionSet
@ -91,27 +90,26 @@ class CPU(ABC):
def run(self, verbose: bool = False):
pass
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
)
return
def launch(self, verbose: bool = False):
entrypoint = self.mmu.find_entrypoint()
if entrypoint is None:
entrypoint = self.mmu.programs[0].entrypoint
if self.conf.verbosity > 0:
print(
FMT_CPU
+ "[CPU] Started running from {}".format(
self.mmu.translate_address(program.entrypoint)
self.mmu.translate_address(entrypoint)
)
+ FMT_NONE
)
print(program)
self.pc = program.entrypoint
self.pc = entrypoint
self.run(verbose)
@classmethod
@abstractmethod
def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]:
def get_loaders(cls) -> Iterable[Type[ProgramLoader]]:
pass
def get_best_loader_for(self, file_name: str) -> Type[ProgramLoader]:

@ -10,7 +10,7 @@ setuptools.setup(
version=riscemu.__version__,
author=riscemu.__author__,
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_content_type="text/markdown",
url="https://github.com/antonlydike/riscemu",
@ -31,6 +31,10 @@ setuptools.setup(
"riscemu.priv",
"riscemu.types",
],
python_requires=">=3.6",
package_data={
"riscemu": ["libc/*.s"],
},
scripts=["riscemu/tools/riscemu"],
python_requires=">=3.8",
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
fibs: .space 1024
.text
.text
main:
addi s1, zero, 0 // storage index
addi s2, zero, 1024 // last storage index
@ -15,9 +15,10 @@ loop:
addi t1, t2, 0 // t1 = t2
addi s1, s1, 4 // increment storage pointer
blt s1, s2, loop // loop as long as we did not reach array length
ebreak
// exit gracefully
add a0, zero, t2
addi a7, zero, 93
scall // exit with code 0
scall // exit with code fibs(n) & 2^32
// 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.test_format = lit.formats.ShTest(preamble_commands=[f"cd {xdsl_src}"])
config.suffixes = ['.asm']
config.suffixes = ['.asm', '.s']

Loading…
Cancel
Save