Compare commits

...

12 Commits

@ -1,5 +1,16 @@
# Changelog
## 2.0.3 - 2022-04-18
- Syscalls: cleaned up formatting and added instructions for extensions
- Parser: fixed error when labels where used outside of sections
- Cleaned up and improved memory dumping code
- Fixed a bug with hex literal recognition in instruction parse code
- Fixed bug where wrong parts of section would be printed in mmu.dump()
- Removed tests for bind_twos_complement as the function is now redundant with the introduction of Int32
- Fixed address translation error for sections without symbols
- Changed verbosity level at which start and end of CPU are printed, added prints for start and stack loading
## 2.0.2
- Added implicit declaration of .text section when a file starts with assembly instructions without declaring a section first

@ -45,3 +45,6 @@ Requires flag `--scall-fs` to be set to True
* `a0`: file descriptor to close
* `return in a0`: 0 if closed correctly or -1
# Extending these syscalls
You can implement your own syscall by adding its code to the `SYSCALLS` dict in the [riscemu/syscalls.py](../riscemu/syscall.py) file, creating a mapping of a syscall code to a name, and then implementing that syscall name in the SyscallInterface class further down that same file. Each syscall method should have the same signature: `read(self, scall: Syscall)`. The `Syscall` object gives you access to the cpu, through which you can access registers and memory. You can look at the `read` or `write` syscalls for further examples.

@ -12,7 +12,7 @@ from typing import List, Type
import riscemu
from .config import RunConfig
from .MMU import MMU
from .colors import FMT_CPU, FMT_NONE
from .colors import FMT_CPU, FMT_NONE, FMT_ERROR
from .debug import launch_debug_session
from .types.exceptions import RiscemuBaseException, LaunchDebuggerException
from .syscall import SyscallInterface, get_syscall_symbols
@ -87,7 +87,8 @@ class UserModeCPU(CPU):
while not self.halted:
self.step(verbose)
print(FMT_CPU + "[CPU] Program exited with code {}".format(self.exit_code) + FMT_NONE)
if self.conf.verbosity > 0:
print(FMT_CPU + "[CPU] Program exited with code {}".format(self.exit_code) + FMT_NONE)
def setup_stack(self, stack_size=1024 * 4) -> bool:
"""
@ -104,9 +105,16 @@ class UserModeCPU(CPU):
)
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))
if self.conf.verbosity > 1:
print(FMT_CPU + "[CPU] Created stack of size {} at 0x{:x}".format(
stack_size, stack_sec.base
) + FMT_NONE)
return True
@classmethod

@ -129,7 +129,7 @@ class MMU:
if sec is None:
print(FMT_MEM + "[MMU] No section containing addr 0x{:08X}".format(addr) + FMT_NONE)
return
sec.dump(addr, *args, **kwargs)
sec.dump(addr - sec.base, *args, **kwargs)
def label(self, symb: str):
"""
@ -163,7 +163,7 @@ class MMU:
name, val = x
return address - val
best_fit = iter(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'))
for name, val in best_fit:
@ -178,7 +178,10 @@ class MMU:
name, val = best
if not name:
return "unknown at 0x{:0x}".format(address)
return "{}:{} + 0x{:x} (0x{:x})".format(
sec.owner, sec.name,
address - sec.base, address
)
return str('{}:{} at {} (0x{:0x}) + 0x{:0x}'.format(
sec.owner, sec.name, name, val, address - val

@ -25,4 +25,4 @@ from .parser import tokenize, parse_tokens, AssemblyFileLoader
__author__ = "Anton Lydike <Anton@Lydike.com>"
__copyright__ = "Copyright 2022 Anton Lydike"
__version__ = '2.0.2'
__version__ = '2.0.3'

@ -69,6 +69,7 @@ class ParseContext:
def _finalize_section(self):
if self.section is None:
return
if self.section.type == MemorySectionType.Data:
section = BinaryDataMemorySection(
self.section.data, self.section.name, self.context, self.program.name, self.section.base
@ -79,12 +80,12 @@ class ParseContext:
self.section.data, self.section.name, self.context, self.program.name, self.section.base
)
self.program.add_section(section)
self.section = None
def new_section(self, name: str, type: MemorySectionType, alignment: int = 4):
base = 0
if self.section is not None:
base = align_addr(self.section.current_address(), alignment)
base = align_addr(self.current_address(), alignment)
self._finalize_section()
self.section = CurrentSection(name, type, base)
@ -95,6 +96,11 @@ class ParseContext:
if is_relative:
self.program.relative_labels.add(name)
def current_address(self):
if self.section:
return self.section.current_address()
return self.program.base if self.program.base is not None else 0
def __repr__(self):
return "{}(\n\tsetion={},\n\tprogram={}\n)".format(
self.__class__.__name__, self.section, self.program
@ -123,7 +129,7 @@ class AssemblerDirectives:
ASSERT_LEN(args, 1)
ASSERT_IN_SECTION_TYPE(context, MemorySectionType.Data)
align_to = parse_numeric_argument(args[0])
current_mod = context.section.current_address() % align_to
current_mod = context.current_address() % align_to
if current_mod == 0:
return
context.section.data += bytearray(align_to - current_mod)

@ -7,8 +7,8 @@ SPDX-License-Identifier: MIT
from math import log10, ceil
from typing import Iterable, Iterator, TypeVar, Generic, List, Optional
from .types.exceptions import *
from .types import Int32, UInt32
from .types.exceptions import *
def align_addr(addr: int, to_bytes: int = 8) -> int:
@ -23,7 +23,7 @@ def parse_numeric_argument(arg: str) -> int:
parse hex or int strings
"""
try:
if '0x' in arg or '0X' in arg:
if arg.lower().startswith('0x'):
return int(arg, 16)
return int(arg)
except ValueError as ex:
@ -44,8 +44,8 @@ def apply_highlight(item, ind, hi_ind):
return item
def highlight_in_list(items, hi_ind):
return " ".join([apply_highlight(item, i, hi_ind) for i, item in enumerate(items)])
def highlight_in_list(items, hi_ind, joiner=" "):
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):
@ -60,21 +60,8 @@ def format_bytes(byte_arr: bytearray, fmt: str, group: int = 1, highlight: int =
spc = str(ceil(log10(2 ** (group * 8))))
return highlight_in_list([('{:0' + spc + 'd}').format(UInt32(ch)) for ch in chunks],
highlight)
if fmt == 'ascii':
return "".join(repr(chr(b))[1:-1] for b in byte_arr)
def bind_twos_complement(val):
"""
does over/underflows for 32 bit two's complement numbers
:param val:
:return:
"""
if val < -2147483648:
return val + 4294967296
elif val > 2147483647:
return val - 4294967296
return val
if fmt == 'char':
return highlight_in_list((repr(chr(b))[1:-1] for b in byte_arr), highlight, "")
T = TypeVar('T')

@ -295,9 +295,9 @@ class RV32I(InstructionSet):
def instruction_sbreak(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 0)
print(FMT_DEBUG + "Debug instruction encountered at 0x{:08X}".format(self.pc - 1) + FMT_NONE)
raise LaunchDebuggerException()
if self.cpu.conf.debug_instruction:
print(FMT_DEBUG + "Debug instruction encountered at 0x{:08X}".format(self.pc - 1) + FMT_NONE)
raise LaunchDebuggerException()
def instruction_nop(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 0)

@ -19,7 +19,7 @@ def parse_instruction(token: Token, args: Tuple[str], context: ParseContext):
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.section.current_address())
ins = SimpleInstruction(token.value, args, context.context, context.current_address())
context.section.data.append(ins)
@ -27,11 +27,11 @@ def parse_label(token: Token, args: Tuple[str], context: ParseContext):
name = token.value[:-1]
if re.match(r'^\d+$', name):
# relative label:
context.context.numbered_labels[name].append(context.section.current_address())
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))
context.add_label(name, context.section.current_address(), is_relative=True)
context.add_label(name, context.current_address(), is_relative=True)
PARSERS: Dict[TokenType, Callable[[Token, Tuple[str], ParseContext], None]] = {

@ -8,10 +8,9 @@ import sys
from dataclasses import dataclass
from typing import Dict, IO
from .helpers import *
if typing.TYPE_CHECKING:
from riscemu.CPU import UserModeCPU
from .colors import FMT_SYSCALL, FMT_NONE
from .types import Int32, CPU
from .types.exceptions import InvalidSyscallException
SYSCALLS = {
63: 'read',
@ -20,7 +19,13 @@ SYSCALLS = {
1024: 'open',
1025: 'close',
}
"""All available syscalls (mapped id->name)"""
"""
This dict contains a mapping for all available syscalls (code->name)
If you wish to add a syscall to the built-in system, you can extend this
dictionary and implement a method with the same name on the SyscallInterface
class.
"""
OPEN_MODES = {
0: 'rb',
@ -39,7 +44,7 @@ class Syscall:
"""
id: int
"""The syscall number (e.g. 64 - write)"""
cpu: 'UserModeCPU'
cpu: CPU
"""The CPU object that created the syscall"""
@property
@ -95,7 +100,7 @@ class SyscallInterface:
addr = scall.cpu.regs.get('a1').unsigned_value
size = scall.cpu.regs.get('a2').unsigned_value
if fileno not in self.open_files:
scall.cpu.regs.set('a0', -1)
scall.ret(-1)
return
chars = self.open_files[fileno].readline(size)
@ -142,7 +147,7 @@ class SyscallInterface:
Requires running with flag scall-fs
"""
# FIXME: this should be toggleable in a global setting or somethign
# 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)
return scall.ret(-1)

@ -4,7 +4,7 @@ from typing import List, Type, Callable, Set, Dict
from ..registers import Registers
from ..config import RunConfig
from ..colors import FMT_RED, FMT_NONE
from ..colors import FMT_RED, FMT_NONE, FMT_ERROR, FMT_CPU
from . import T_AbsoluteAddress, Instruction, Program, ProgramLoader
@ -84,9 +84,13 @@ class CPU(ABC):
def launch(self, program: Program, verbose: bool = False):
if program not in self.mmu.programs:
print(FMT_RED + '[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(program)
self.pc = program.entrypoint
self.run(verbose)

@ -2,9 +2,9 @@ from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional
from ..colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE
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
from . import MemoryFlags, T_AbsoluteAddress, InstructionContext, T_RelativeAddress, Instruction, Int32
@dataclass
@ -32,17 +32,55 @@ 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 = 'hex',
bytes_per_row: int = 16, rows: int = 10, group: int = 4):
if self.flags.executable:
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.
:param start: The address to start at
:param end: The end of the printed space
:param fmt: either hex, int, char or asm
:param bytes_per_row: the number of bytes displayed per row
:param rows: the number of rows displayed
:param group: Group this many bytes into one when displaying
:param highlight: Highlight the group containing this address
:return:
"""
if isinstance(start, Int32):
start = start.value
if isinstance(end, Int32):
end = end.value
if fmt is None:
if self.flags.executable and self.flags.read_only:
bytes_per_row = 4
fmt = 'asm'
else:
fmt = 'hex'
if fmt == 'char':
if bytes_per_row is None:
bytes_per_row = 4
if group is None:
group = 1
if group is None:
group = 4
if bytes_per_row is None:
bytes_per_row = 4
highlight = 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 - 1)
end = min(start + (bytes_per_row * (rows // 2)), self.size)
highlight = start
start = max(0, start - (bytes_per_row * (rows // 2)))
if self.flags.executable:
if fmt == 'asm':
print(FMT_MEM + "{}, viewing {} instructions:".format(
self, (end - start) // 4
) + FMT_NONE)

@ -4,16 +4,4 @@ from riscemu.helpers import *
class TestHelpers(TestCase):
def test_bind_twos_complement(self):
minval = -(1 << 31)
maxval = ((1 << 31)-1)
self.assertEqual(bind_twos_complement(minval), minval, "minval preserves")
self.assertEqual(bind_twos_complement(minval), minval, )
self.assertEqual(bind_twos_complement(maxval), maxval, "maxval preserves")
self.assertEqual(bind_twos_complement(minval - 1), maxval, "minval-1 wraps")
self.assertEqual(bind_twos_complement(maxval + 1), minval, "maxval+1 wraps")
self.assertEqual(bind_twos_complement(0), 0, "0 is 0")
self.assertEqual(bind_twos_complement(1), 1, "1 is 1")
self.assertEqual(bind_twos_complement(-1), -1, "-1 is -1")
pass
Loading…
Cancel
Save