Compare commits

..

12 Commits

@ -1,5 +1,16 @@
# Changelog # 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 ## 2.0.2
- Added implicit declaration of .text section when a file starts with assembly instructions without declaring a section first - 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 * `a0`: file descriptor to close
* `return in a0`: 0 if closed correctly or -1 * `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 import riscemu
from .config import RunConfig from .config import RunConfig
from .MMU import MMU 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 .debug import launch_debug_session
from .types.exceptions import RiscemuBaseException, LaunchDebuggerException from .types.exceptions import RiscemuBaseException, LaunchDebuggerException
from .syscall import SyscallInterface, get_syscall_symbols from .syscall import SyscallInterface, get_syscall_symbols
@ -87,7 +87,8 @@ class UserModeCPU(CPU):
while not self.halted: while not self.halted:
self.step(verbose) 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: 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): if not self.mmu.load_section(stack_sec, fixed_position=False):
print(FMT_ERROR + "[CPU] Could not insert stack section!" + FMT_NONE)
return False 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)
return True return True
@classmethod @classmethod

@ -129,7 +129,7 @@ class MMU:
if sec is None: 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 return
sec.dump(addr, *args, **kwargs) sec.dump(addr - sec.base, *args, **kwargs)
def label(self, symb: str): def label(self, symb: str):
""" """
@ -163,7 +163,7 @@ class MMU:
name, val = x name, val = x
return address - val 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')) best = ('', float('inf'))
for name, val in best_fit: for name, val in best_fit:
@ -178,7 +178,10 @@ class MMU:
name, val = best name, val = best
if not name: 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( return str('{}:{} at {} (0x{:0x}) + 0x{:0x}'.format(
sec.owner, sec.name, name, val, address - val 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>" __author__ = "Anton Lydike <Anton@Lydike.com>"
__copyright__ = "Copyright 2022 Anton Lydike" __copyright__ = "Copyright 2022 Anton Lydike"
__version__ = '2.0.2' __version__ = '2.0.3'

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

@ -7,8 +7,8 @@ SPDX-License-Identifier: MIT
from math import log10, ceil from math import log10, ceil
from typing import Iterable, Iterator, TypeVar, Generic, List, Optional from typing import Iterable, Iterator, TypeVar, Generic, List, Optional
from .types.exceptions import *
from .types import Int32, UInt32 from .types import Int32, UInt32
from .types.exceptions import *
def align_addr(addr: int, to_bytes: int = 8) -> int: 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 parse hex or int strings
""" """
try: try:
if '0x' in arg or '0X' in arg: if arg.lower().startswith('0x'):
return int(arg, 16) return int(arg, 16)
return int(arg) return int(arg)
except ValueError as ex: except ValueError as ex:
@ -44,8 +44,8 @@ def apply_highlight(item, ind, hi_ind):
return item return item
def highlight_in_list(items, hi_ind): def highlight_in_list(items, hi_ind, joiner=" "):
return " ".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): 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)))) spc = str(ceil(log10(2 ** (group * 8))))
return highlight_in_list([('{:0' + spc + 'd}').format(UInt32(ch)) for ch in chunks], return highlight_in_list([('{:0' + spc + 'd}').format(UInt32(ch)) for ch in chunks],
highlight) highlight)
if fmt == 'ascii': if fmt == 'char':
return "".join(repr(chr(b))[1:-1] for b in byte_arr) return highlight_in_list((repr(chr(b))[1:-1] for b in byte_arr), highlight, "")
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
T = TypeVar('T') T = TypeVar('T')

@ -295,9 +295,9 @@ class RV32I(InstructionSet):
def instruction_sbreak(self, ins: 'Instruction'): def instruction_sbreak(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 0) 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() raise LaunchDebuggerException()
def instruction_nop(self, ins: 'Instruction'): def instruction_nop(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 0) 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) context.new_section('.text', MemorySectionType.Instructions)
if context.section.type != MemorySectionType.Instructions: if context.section.type != MemorySectionType.Instructions:
raise ParseException("{} {} encountered in invalid context: {}".format(token, args, context)) 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) context.section.data.append(ins)
@ -27,11 +27,11 @@ def parse_label(token: Token, args: Tuple[str], context: ParseContext):
name = token.value[:-1] name = token.value[:-1]
if re.match(r'^\d+$', name): if re.match(r'^\d+$', name):
# relative label: # relative label:
context.context.numbered_labels[name].append(context.section.current_address()) context.context.numbered_labels[name].append(context.current_address())
else: else:
if name in context.context.labels: 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.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]] = { PARSERS: Dict[TokenType, Callable[[Token, Tuple[str], ParseContext], None]] = {

@ -8,10 +8,9 @@ import sys
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, IO from typing import Dict, IO
from .helpers import * from .colors import FMT_SYSCALL, FMT_NONE
from .types import Int32, CPU
if typing.TYPE_CHECKING: from .types.exceptions import InvalidSyscallException
from riscemu.CPU import UserModeCPU
SYSCALLS = { SYSCALLS = {
63: 'read', 63: 'read',
@ -20,7 +19,13 @@ SYSCALLS = {
1024: 'open', 1024: 'open',
1025: 'close', 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 = { OPEN_MODES = {
0: 'rb', 0: 'rb',
@ -39,7 +44,7 @@ class Syscall:
""" """
id: int id: int
"""The syscall number (e.g. 64 - write)""" """The syscall number (e.g. 64 - write)"""
cpu: 'UserModeCPU' cpu: CPU
"""The CPU object that created the syscall""" """The CPU object that created the syscall"""
@property @property
@ -95,7 +100,7 @@ class SyscallInterface:
addr = scall.cpu.regs.get('a1').unsigned_value addr = scall.cpu.regs.get('a1').unsigned_value
size = scall.cpu.regs.get('a2').unsigned_value size = scall.cpu.regs.get('a2').unsigned_value
if fileno not in self.open_files: if fileno not in self.open_files:
scall.cpu.regs.set('a0', -1) scall.ret(-1)
return return
chars = self.open_files[fileno].readline(size) chars = self.open_files[fileno].readline(size)
@ -142,7 +147,7 @@ class SyscallInterface:
Requires running with flag scall-fs 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: 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) return scall.ret(-1)

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

@ -2,9 +2,9 @@ from abc import ABC, abstractmethod
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional 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 ..helpers import format_bytes
from . import MemoryFlags, T_AbsoluteAddress, InstructionContext, T_RelativeAddress, Instruction from . import MemoryFlags, T_AbsoluteAddress, InstructionContext, T_RelativeAddress, Instruction, Int32
@dataclass @dataclass
@ -32,17 +32,55 @@ class MemorySection(ABC):
def read_ins(self, offset: T_RelativeAddress) -> Instruction: def read_ins(self, offset: T_RelativeAddress) -> Instruction:
pass pass
def dump(self, start: T_RelativeAddress, end: Optional[T_RelativeAddress] = None, fmt: str = 'hex', def dump(self, start: T_RelativeAddress, end: Optional[T_RelativeAddress] = None, fmt: str = None,
bytes_per_row: int = 16, rows: int = 10, group: int = 4): bytes_per_row: int = None, rows: int = 10, group: int = None, highlight: int = None):
if self.flags.executable: """
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 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: 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 highlight = start
start = max(0, start - (bytes_per_row * (rows // 2))) start = max(0, start - (bytes_per_row * (rows // 2)))
if self.flags.executable: if fmt == 'asm':
print(FMT_MEM + "{}, viewing {} instructions:".format( print(FMT_MEM + "{}, viewing {} instructions:".format(
self, (end - start) // 4 self, (end - start) // 4
) + FMT_NONE) ) + FMT_NONE)

@ -4,16 +4,4 @@ from riscemu.helpers import *
class TestHelpers(TestCase): class TestHelpers(TestCase):
pass
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")
Loading…
Cancel
Save