You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
riscemu/riscemu/Syscall.py

202 lines
5.5 KiB
Python

"""
RiscEmu (c) 2021 Anton Lydike
SPDX-License-Identifier: MIT
"""
from dataclasses import dataclass
from typing import Dict, IO
import sys
from .helpers import *
import riscemu
import typing
if typing.TYPE_CHECKING:
from . import CPU
SYSCALLS = {
63: 'read',
64: 'write',
93: 'exit',
1024: 'open',
1025: 'close',
}
"""All available syscalls (mapped id->name)"""
OPEN_MODES = {
0: 'rb',
1: 'wb',
2: 'r+b',
3: 'x',
4: 'ab',
}
"""All available file open modes"""
@dataclass(frozen=True)
class Syscall:
"""
Represents a syscall
"""
id: int
"""The syscall number (e.g. 64 - write)"""
cpu: 'riscemu.CPU'
"""The CPU object that created the syscall"""
@property
def name(self):
return SYSCALLS.get(self.id, "unknown")
def __repr__(self):
return "Syscall(id={}, name={})".format(
self.id, self.name
)
def ret(self, code):
self.cpu.regs.set('a0', code)
def get_syscall_symbols():
"""
Generate global syscall symbols (such as SCALL_READ, SCALL_EXIT etc)
:return: dictionary of all syscall symbols (SCALL_<name> -> id)
"""
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
}
if getattr(self, scall.name):
getattr(self, scall.name)(scall)
else:
raise InvalidSyscallException(scall)
def read(self, scall: Syscall):
"""
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')
addr = scall.cpu.regs.get('a1')
size = scall.cpu.regs.get('a2')
if fileno not in self.open_files:
scall.cpu.regs.set('a0', -1)
return
chars = self.open_files[fileno].readline(size)
try:
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)
return scall.ret(-1)
def write(self, scall: Syscall):
"""
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')
addr = scall.cpu.regs.get('a1')
size = scall.cpu.regs.get('a2')
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)
return scall.ret(-1)
self.open_files[fileno].write(data.decode('ascii'))
return scall.ret(size)
def open(self, scall: Syscall):
"""
open syscall (1024): read path of a2 bytes from addr a1, in mode a0
returns the file no in a0
modes:
- 0: read
- 1: write (truncate)
- 2: read/write (no truncate)
- 3: only create
- 4: append
Requires running with flag scall-fs
"""
if not scall.cpu.conf.scall_fs:
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')
addr = scall.cpu.regs.get('a1')
size = scall.cpu.regs.get('a2')
mode_st = OPEN_MODES.get(mode, )
if mode_st == -1:
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')
fileno = self.next_open_handle
self.next_open_handle += 1
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)
return scall.ret(-1)
print(FMT_SYSCALL + '[Syscall] open: opened fd {} to {}!'.format(fileno, path) + FMT_NONE)
return scall.ret(fileno)
def close(self, scall: Syscall):
"""
close syscall (1025): closes file no a0
return -1 if an error was encountered, otherwise returns 0
"""
fileno = scall.cpu.regs.get('a0')
if fileno not in self.open_files:
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)
del self.open_files[fileno]
return scall.ret(0)
def exit(self, scall: Syscall):
"""
Exit syscall. Exits the system with status code a0
"""
scall.cpu.exit = True
scall.cpu.exit_code = scall.cpu.regs.get('a0')
def __repr__(self):
return "{}(\n\tfiles={}\n)".format(
self.__class__.__name__,
self.open_files
)