diff --git a/docs/syscalls.md b/docs/syscalls.md new file mode 100644 index 0000000..1c76b2d --- /dev/null +++ b/docs/syscalls.md @@ -0,0 +1,46 @@ +# Syscalls: + +Performing a syscall is quite simple: +```risc-v asm + ; set syscall code: + addi a7, zero, 93 ; or SCALL_EXIT if syscall symbols are mapped + ; set syscall args: + addi a0, zero, 1 ; exit with code 1 + ; invode syscall handler + scall +``` + +In order to use the global syscall symbols, run with the `--syscall-symbols` flag (not implemented yet) + +## Read (63) `SCALL_READ` +* `a0`: source file descriptor +* `a1`: addr in which to read +* `a2`: number of bytes to read (at most) +* `return: a0` number of bytes read or -1 + +## Write (64) `SCALL_WRITE` +* `a0`: target file descriptor +* `a1`: addr from which to read +* `a2`: number of bytes to read +* `return: a0`: number of bytes written or -1 + +## Exit (93) `SCALL_EXIT` +* `a0`: exit code + +## Open (1024) `SCALL_OPEN` +* `a0`: open mode: + - `0`: read + - `1`: write (truncate) + - `2`: read/write (no truncate) + - `3`: only create + - `4`: append +* `a1`: addr where path is stored +* `a2`: length of path +* `return: a0`: file descriptor of opened file or -1 + +Requires flag `--scall-fs` to be set to True + +## Close (1025) `SCALL_OPEN` +* `a0`: file descriptor to close +* `return: a0`: 0 if closed correctly or -1 + diff --git a/riscemu/Syscall.py b/riscemu/Syscall.py index fe0909a..eb7460b 100644 --- a/riscemu/Syscall.py +++ b/riscemu/Syscall.py @@ -1,6 +1,10 @@ from dataclasses import dataclass +from typing import Dict, IO +import sys + from .Registers import Registers from .Exceptions import InvalidSyscallException +from .helpers import * import typing @@ -8,9 +12,19 @@ if typing.TYPE_CHECKING: from . import CPU SYSCALLS = { - 63: 'read', - 64: 'write', - 93: 'exit' + 63: 'read', + 64: 'write', + 93: 'exit', + 1024: 'open', + 1025: 'close', +} + +OPEN_MODES = { + 0: 'rb', + 1: 'wb', + 2: 'r+b', + 3: 'x', + 4: 'ab', } @@ -29,19 +43,124 @@ class Syscall: self.id, self.name ) + def ret(self, code): + self.registers.set('a0', code) class SyscallInterface: + 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): - pass + """ + 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.registers.get('a0') + addr = scall.registers.get('a1') + len = scall.registers.get('a2') + if fileno not in self.open_files: + scall.registers.set('a0', -1) + return + + chars = self.open_files[fileno].read(len) + try: + data = bytearray(chars, 'ascii') + scall.cpu.mmu.write(addr, len(data), data) + return scall.ret(len(data)) + + except UnicodeEncodeError: + print(FMT_ERROR + '[Syscall] read: UnicodeError - invalid input "{}"'.format(chars) + FMT_NONE) + return scall.ret(-1) def write(self, scall: Syscall): - pass + """ + 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.registers.get('a0') + addr = scall.registers.get('a1') + size = scall.registers.get('a2') + if fileno not in self.open_files: + return scall.ret(-1) + + data = scall.cpu.mmu.read(addr, size) + + if not isinstance(str, bytearray): + print(FMT_ERROR + '[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_ERROR + '[Syscall] open: opening files not supported without scall-fs flag!' + FMT_NONE) + return scall.ret(-1) + + mode = scall.registers.get('a0') + addr = scall.registers.get('a1') + size = scall.registers.get('a2') + + mode_st = OPEN_MODES.get(mode, ) + if mode_st == -1: + print(FMT_ERROR + '[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_ERROR + '[Syscall] open: encountered error during {}!'.format(err.strerror) + FMT_NONE) + return scall.ret(-1) + + print(FMT_CYAN + '[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.registers.get('a0') + if fileno not in self.open_files: + print(FMT_ERROR + '[Syscall] close: unknown fileno {}!'.format(fileno) + FMT_NONE) + return scall.ret(-1) + + self.open_files[fileno].close() + print(FMT_CYAN + '[Syscall] close: closed fd {}!'.format(fileno) + FMT_NONE) + del self.open_files[fileno] + return scall.ret(0) def exit(self, scall: Syscall): scall.cpu.exit = True diff --git a/riscemu/helpers.py b/riscemu/helpers.py index 4ae0dce..0441bfc 100644 --- a/riscemu/helpers.py +++ b/riscemu/helpers.py @@ -43,6 +43,7 @@ def int_from_bytes(bytes, unsigned=False): # Colors +FMT_RED = '\033[31m' FMT_ORANGE = '\033[33m' FMT_GRAY = '\033[37m' FMT_CYAN = '\033[36m' @@ -50,4 +51,6 @@ FMT_GREEN = '\033[32m' FMT_BOLD = '\033[1m' FMT_MAGENTA = '\033[35m' FMT_NONE = '\033[0m' -FMT_UNDERLINE = '\033[4m' \ No newline at end of file +FMT_UNDERLINE = '\033[4m' + +FMT_ERROR = FMT_RED + FMT_BOLD