From 6fa3558f6c39dcd76fe7741af028ff04dba6181f Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Sun, 13 Feb 2022 14:55:03 +0100 Subject: [PATCH] added interactive mode, fixed some bugs --- riscemu/CPU.py | 4 +++- riscemu/MMU.py | 13 ++++++++--- riscemu/__init__.py | 5 ++--- riscemu/__main__.py | 7 +++--- riscemu/assembler.py | 8 +++++++ riscemu/base.py | 5 +++-- riscemu/debug.py | 14 +++++++++--- riscemu/helpers.py | 1 + riscemu/interactive.py | 25 +++++++++++++++++++++ riscemu/priv/PrivMMU.py | 49 +++++++++++++++++++++-------------------- riscemu/types.py | 45 ++++++++++++++++++++----------------- 11 files changed, 117 insertions(+), 59 deletions(-) create mode 100644 riscemu/interactive.py diff --git a/riscemu/CPU.py b/riscemu/CPU.py index 6340495..8e3c395 100644 --- a/riscemu/CPU.py +++ b/riscemu/CPU.py @@ -10,7 +10,7 @@ import typing from typing import List, Type import riscemu -from . import AssemblyFileLoader, RunConfig +from .config import RunConfig from .MMU import MMU from .base import BinaryDataMemorySection from .colors import FMT_CPU, FMT_NONE @@ -18,6 +18,7 @@ from .debug import launch_debug_session from .exceptions import RiscemuBaseException, LaunchDebuggerException from .syscall import SyscallInterface, get_syscall_symbols from .types import CPU, ProgramLoader +from .parser import AssemblyFileLoader if typing.TYPE_CHECKING: from .instructions.instruction_set import InstructionSet @@ -105,6 +106,7 @@ class UserModeCPU(CPU): return False self.regs.set('sp', stack_sec.base + stack_sec.size) + return True @classmethod def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]: diff --git a/riscemu/MMU.py b/riscemu/MMU.py index 0854747..a91ca9b 100644 --- a/riscemu/MMU.py +++ b/riscemu/MMU.py @@ -10,7 +10,7 @@ from .colors import * from .exceptions import InvalidAllocationException, MemoryAccessException from .helpers import align_addr, int_from_bytes from .types import Instruction, MemorySection, MemoryFlags, T_AbsoluteAddress, \ - Program + Program, InstructionContext class MMU: @@ -53,7 +53,6 @@ class MMU: self.sections = list() self.global_symbols = dict() - def get_sec_containing(self, addr: T_AbsoluteAddress) -> Optional[MemorySection]: """ Returns the section that contains the address addr @@ -82,7 +81,7 @@ class MMU: sec = self.get_sec_containing(addr) if sec is None: print(FMT_MEM + "[MMU] Trying to read instruction form invalid region! (read at {}) ".format(addr) - + "Have you forgotten an exit syscall or ret statement?" + FMT_NONE) + + "Have you forgotten an exit syscall or ret statement?" + FMT_NONE) raise RuntimeError("No next instruction available!") return sec.read_ins(addr - sec.base) @@ -234,3 +233,11 @@ class MMU: return "MMU(\n\t{}\n)".format( "\n\t".join(repr(x) for x in self.programs) ) + + def context_for(self, addr: T_AbsoluteAddress) -> Optional[InstructionContext]: + sec = self.get_sec_containing(addr) + + if sec is not None: + return sec.context + + return None diff --git a/riscemu/__init__.py b/riscemu/__init__.py index 90ba867..22d1f8b 100644 --- a/riscemu/__init__.py +++ b/riscemu/__init__.py @@ -11,14 +11,13 @@ It contains everything needed to run assembly files, so you don't need any custo from .exceptions import RiscemuBaseException, LaunchDebuggerException, InvalidSyscallException, LinkerException, \ ParseException, NumberFormatException, InvalidRegisterException, MemoryAccessException, OutOfMemoryException -#from .base_types import Executable, LoadedExecutable, LoadedMemorySection - from .instructions import * from .MMU import MMU from .registers import Registers from .syscall import SyscallInterface, Syscall -from .CPU import CPU +from .CPU import CPU, UserModeCPU +from .debug import launch_debug_session from .config import RunConfig diff --git a/riscemu/__main__.py b/riscemu/__main__.py index 1a88200..21d676e 100644 --- a/riscemu/__main__.py +++ b/riscemu/__main__.py @@ -9,9 +9,9 @@ from riscemu.CPU import UserModeCPU if __name__ == '__main__': from .config import RunConfig - from .helpers import * from .instructions import InstructionSetDict - from riscemu.parser import AssemblyFileLoader + from .colors import FMT_BOLD, FMT_MAGENTA + from .parser import AssemblyFileLoader import argparse import sys @@ -69,7 +69,8 @@ if __name__ == '__main__': parser.add_argument('-v', '--verbose', help="Verbosity level (can be used multiple times)", action='count', default=0) - args = parser.parse_args() + parser.add_argument('--interactive', help="Launch the interactive debugger instantly instead of loading any " + "programs", action='store_true') # create a RunConfig from the cli args cfg_dict = dict( diff --git a/riscemu/assembler.py b/riscemu/assembler.py index de29044..869b9e4 100644 --- a/riscemu/assembler.py +++ b/riscemu/assembler.py @@ -89,6 +89,14 @@ class ParseContext: self._finalize_section() self.section = CurrentSection(name, type, base) + def add_label(self, name: str, value: int, is_global: bool = False, is_relative: bool = False): + self.context.labels[name] = value + if is_global: + self.program.global_labels.add(name) + if is_relative: + self.program.relative_labels.add(name) + + def __repr__(self): return "{}(\n\tsetion={},\n\tprogram={}\n)".format( self.__class__.__name__, self.section, self.program diff --git a/riscemu/base.py b/riscemu/base.py index 3989266..474e4ed 100644 --- a/riscemu/base.py +++ b/riscemu/base.py @@ -5,7 +5,7 @@ This aims to be a simple base, usable for everyone who needs the basic functiona want to set up their own subtypes of Instruction and MemorySection """ -from typing import List, Tuple +from typing import List, Tuple, Union from .exceptions import MemoryAccessException from .helpers import parse_numeric_argument from .types import Instruction, MemorySection, MemoryFlags, InstructionContext, T_RelativeAddress, \ @@ -13,7 +13,8 @@ from .types import Instruction, MemorySection, MemoryFlags, InstructionContext, class SimpleInstruction(Instruction): - def __init__(self, name: str, args: Tuple[str], context: InstructionContext, addr: T_RelativeAddress): + def __init__(self, name: str, args: Union[Tuple[()], Tuple[str], Tuple[str, str], Tuple[str, str, str]], + context: InstructionContext, addr: T_RelativeAddress): self.context = context self.name = name self.args = args diff --git a/riscemu/debug.py b/riscemu/debug.py index a186097..0885843 100644 --- a/riscemu/debug.py +++ b/riscemu/debug.py @@ -3,6 +3,7 @@ RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ +import os.path from .base import SimpleInstruction from .helpers import * @@ -11,6 +12,7 @@ if typing.TYPE_CHECKING: from riscemu import CPU, Registers + def launch_debug_session(cpu: 'CPU', prompt=""): if cpu.debugger_active: return @@ -46,12 +48,12 @@ def launch_debug_session(cpu: 'CPU', prompt=""): if len(args) > 3: print("Invalid arg count!") return - bin = mmu.get_bin_containing(cpu.pc) + context = mmu.context_for(cpu.pc) ins = SimpleInstruction( name, tuple(args), - bin.context, + context, cpu.pc) print(FMT_DEBUG + "Running instruction {}".format(ins) + FMT_NONE) cpu.run_instruction(ins) @@ -76,11 +78,17 @@ def launch_debug_session(cpu: 'CPU', prompt=""): # add tab completion readline.set_completer(rlcompleter.Completer(sess_vars).complete) readline.parse_and_bind("tab: complete") + if os.path.exists('~/.riscemu_history'): + readline.read_history_file('~/.riscemu_history') relaunch_debugger = False try: - code.InteractiveConsole(sess_vars).interact(banner=FMT_DEBUG + prompt + FMT_NONE, exitmsg="Exiting debugger") + code.InteractiveConsole(sess_vars).interact( + banner=FMT_DEBUG + prompt + FMT_NONE, + exitmsg="Exiting debugger", + ) finally: cpu.debugger_active = False + readline.write_history_file('~/.riscemu_history') diff --git a/riscemu/helpers.py b/riscemu/helpers.py index bbec01f..9c94635 100644 --- a/riscemu/helpers.py +++ b/riscemu/helpers.py @@ -61,6 +61,7 @@ def to_signed(num: int, bytes=4) -> int: return num + def create_chunks(my_list, chunk_size): """Split a list like [a,b,c,d,e,f,g,h,i,j,k,l,m] into e.g. [[a,b,c,d],[e,f,g,h],[i,j,k,l],[m]]""" return [my_list[i:i + chunk_size] for i in range(0, len(my_list), chunk_size)] diff --git a/riscemu/interactive.py b/riscemu/interactive.py new file mode 100644 index 0000000..71526f3 --- /dev/null +++ b/riscemu/interactive.py @@ -0,0 +1,25 @@ +from riscemu import RunConfig +from riscemu.base import InstructionMemorySection, SimpleInstruction +from riscemu.types import InstructionContext, Program + +if __name__ == '__main__': + from .CPU import UserModeCPU + from .instructions import InstructionSetDict + from .debug import launch_debug_session + + cpu = UserModeCPU(list(InstructionSetDict.values()), RunConfig(verbosity=4)) + + program = Program('interactive session', base=0x100) + context = program.context + program.add_section(InstructionMemorySection([ + SimpleInstruction('ebreak', (), context, 0x100), + SimpleInstruction('addi', ('a0', 'zero', '0'), context, 0x104), + SimpleInstruction('addi', ('a7', 'zero', '93'), context, 0x108), + SimpleInstruction('scall', (), context, 0x10C), + ], '.text', context, program.name, 0x100)) + + cpu.load_program(program) + + cpu.setup_stack() + + cpu.launch(program) diff --git a/riscemu/priv/PrivMMU.py b/riscemu/priv/PrivMMU.py index 798caa9..f6a86d6 100644 --- a/riscemu/priv/PrivMMU.py +++ b/riscemu/priv/PrivMMU.py @@ -1,42 +1,43 @@ +from .types import ElfMemorySection from ..MMU import * from abc import abstractmethod import typing -from .ElfLoader import ElfExecutable - if typing.TYPE_CHECKING: from .PrivCPU import PrivCPU class PrivMMU(MMU): - cpu: 'PrivCPU' - - @abstractmethod - def get_entrypoint(self) -> int: - raise - def set_cpu(self, cpu: 'PrivCPU'): - self.cpu = cpu + def get_sec_containing(self, addr: T_AbsoluteAddress) -> MemorySection: + # try to get an existing section + existing_sec = super().get_sec_containing(addr) - def translate_address(self, addr: int): - return "" + if existing_sec is not None: + return existing_sec + # get section preceding empty space at addr + sec_before = next((sec for sec in reversed(self.sections) if sec.end < addr), None) + # get sec succeeding empty space at addr + sec_after = next((sec for sec in self.sections if sec.base > addr), None) -class LoadedElfMMU(PrivMMU): - def __init__(self, elf: ElfExecutable): - super().__init__(conf=RunConfig()) - self.entrypoint = elf.symbols['_start'] + # calc start end end of "free" space + prev_sec_end = 0 if sec_before is None else sec_before.end + next_sec_start = 0x7FFFFFFF if sec_after is None else sec_before.base - self.binaries.append(elf) - for sec in elf.sections: - self.sections.append(sec) + # start at the end of the prev section, or current address - 0xFFFF (aligned to 16 byte boundary) + start = max(prev_sec_end, align_addr(addr - 0xFFFF, 16)) + # end at the start of the next section, or address + 0xFFFF (aligned to 16 byte boundary) + end = min(next_sec_start, align_addr(addr + 0xFFFF, 16)) - def load_bin(self, exe: Executable) -> LoadedExecutable: - raise NotImplementedError("This is a privMMU, it's initialized with a single ElfExecutable!") + sec = ElfMemorySection(bytearray(end - start), '.empty', self.global_instruction_context(), '', start, MemoryFlags(False, True)) + self.sections.append(sec) + self._update_state() - def allocate_section(self, name: str, req_size: int, flag: MemoryFlags): - raise NotImplementedError("Not supported!") + return sec - def get_entrypoint(self): - return self.entrypoint + def global_instruction_context(self) -> InstructionContext: + context = InstructionContext() + context.global_symbol_dict = self.global_symbols + return context \ No newline at end of file diff --git a/riscemu/types.py b/riscemu/types.py index e2f7065..f5c9989 100644 --- a/riscemu/types.py +++ b/riscemu/types.py @@ -15,7 +15,7 @@ from collections import defaultdict from dataclasses import dataclass from typing import Dict, List, Optional, Tuple, Set, Union, Iterator, Callable, Type -from . import RunConfig +from .config import RunConfig from .colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_RED, FMT_BOLD from .exceptions import ParseException from .helpers import format_bytes, get_section_base_name @@ -81,19 +81,17 @@ class InstructionContext: raise ParseException("Cannot resolve relative symbol {} without an address!".format(symbol)) direction = symbol[-1] + values = self.numbered_labels.get(symbol[:-1], []) if direction == 'b': - return max([addr for addr in self.numbered_labels.get(symbol[:-1], []) if addr < address_at], - default=None) + return max((addr + self.base_address for addr in values if addr < address_at), default=None) else: - return min([addr for addr in self.numbered_labels.get(symbol[:-1], []) if addr > address_at], - default=None) + return min((addr + self.base_address for addr in values if addr > address_at), default=None) else: + # if it's not a local symbol, try the globals if symbol not in self.labels: return self.global_symbol_dict.get(symbol, None) - value = self.labels.get(symbol, None) - if value is None: - return value - return value + self.base_address + # otherwise return the local symbol + return self.labels.get(symbol, None) class Instruction(ABC): @@ -218,6 +216,7 @@ class Program: name: str context: InstructionContext global_labels: Set[str] + relative_labels: Set[str] sections: List[MemorySection] base: Optional[T_AbsoluteAddress] is_loaded: bool @@ -235,6 +234,7 @@ class Program: self.context = InstructionContext() self.sections = [] self.global_labels = set() + self.relative_labels = set() self.base = base self.is_loaded = False @@ -285,18 +285,18 @@ class Program: print(FMT_MEM + 'WARNING: Program loaded at different address then expected! (loaded at {}, ' 'but expects to be loaded at {})'.format(at_addr, self.base) + FMT_NONE) - # if the program is not located anywhere explicitly in memory, add the program address - # to the defined section bases - if self.base is None: - for sec in self.sections: - sec.base += at_addr + # check if we are relocating + if self.base != at_addr: + offset = at_addr if self.base is None else at_addr - self.base - if self.base is not None and self.base != at_addr: - # move sections so they are located where they want to be located - offset = at_addr - self.base + # move all sections by the offset for sec in self.sections: sec.base += offset + # move all relative symbols by the offset + for name in self.relative_labels: + self.context.labels[name] += offset + self.base = at_addr self.context.base_address = at_addr @@ -393,9 +393,6 @@ class CPU(ABC): self.pc = 0 self.debugger_active = False - self.sections = list() - self.programs = list() - def run_instruction(self, ins: Instruction): """ Execute a single instruction @@ -446,3 +443,11 @@ class CPU(ABC): def get_best_loader_for(self, file_name: str) -> Type[ProgramLoader]: return max(self.get_loaders(), key=lambda ld: ld.can_parse(file_name)) + + @property + def sections(self): + return self.mmu.sections + + @property + def programs(self): + return self.mmu.programs \ No newline at end of file