added interactive mode, fixed some bugs

assembly-parser-rework
Anton Lydike 3 years ago
parent 3d2619c258
commit 6fa3558f6c

@ -10,7 +10,7 @@ import typing
from typing import List, Type from typing import List, Type
import riscemu import riscemu
from . import AssemblyFileLoader, RunConfig from .config import RunConfig
from .MMU import MMU from .MMU import MMU
from .base import BinaryDataMemorySection from .base import BinaryDataMemorySection
from .colors import FMT_CPU, FMT_NONE from .colors import FMT_CPU, FMT_NONE
@ -18,6 +18,7 @@ from .debug import launch_debug_session
from .exceptions import RiscemuBaseException, LaunchDebuggerException from .exceptions import RiscemuBaseException, LaunchDebuggerException
from .syscall import SyscallInterface, get_syscall_symbols from .syscall import SyscallInterface, get_syscall_symbols
from .types import CPU, ProgramLoader from .types import CPU, ProgramLoader
from .parser import AssemblyFileLoader
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from .instructions.instruction_set import InstructionSet from .instructions.instruction_set import InstructionSet
@ -105,6 +106,7 @@ class UserModeCPU(CPU):
return False return False
self.regs.set('sp', stack_sec.base + stack_sec.size) self.regs.set('sp', stack_sec.base + stack_sec.size)
return True
@classmethod @classmethod
def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]: def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]:

@ -10,7 +10,7 @@ from .colors import *
from .exceptions import InvalidAllocationException, MemoryAccessException from .exceptions import InvalidAllocationException, MemoryAccessException
from .helpers import align_addr, int_from_bytes from .helpers import align_addr, int_from_bytes
from .types import Instruction, MemorySection, MemoryFlags, T_AbsoluteAddress, \ from .types import Instruction, MemorySection, MemoryFlags, T_AbsoluteAddress, \
Program Program, InstructionContext
class MMU: class MMU:
@ -53,7 +53,6 @@ class MMU:
self.sections = list() self.sections = list()
self.global_symbols = dict() self.global_symbols = dict()
def get_sec_containing(self, addr: T_AbsoluteAddress) -> Optional[MemorySection]: def get_sec_containing(self, addr: T_AbsoluteAddress) -> Optional[MemorySection]:
""" """
Returns the section that contains the address addr Returns the section that contains the address addr
@ -82,7 +81,7 @@ class MMU:
sec = self.get_sec_containing(addr) sec = self.get_sec_containing(addr)
if sec is None: if sec is None:
print(FMT_MEM + "[MMU] Trying to read instruction form invalid region! (read at {}) ".format(addr) 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!") raise RuntimeError("No next instruction available!")
return sec.read_ins(addr - sec.base) return sec.read_ins(addr - sec.base)
@ -234,3 +233,11 @@ class MMU:
return "MMU(\n\t{}\n)".format( return "MMU(\n\t{}\n)".format(
"\n\t".join(repr(x) for x in self.programs) "\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

@ -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, \ from .exceptions import RiscemuBaseException, LaunchDebuggerException, InvalidSyscallException, LinkerException, \
ParseException, NumberFormatException, InvalidRegisterException, MemoryAccessException, OutOfMemoryException ParseException, NumberFormatException, InvalidRegisterException, MemoryAccessException, OutOfMemoryException
#from .base_types import Executable, LoadedExecutable, LoadedMemorySection
from .instructions import * from .instructions import *
from .MMU import MMU from .MMU import MMU
from .registers import Registers from .registers import Registers
from .syscall import SyscallInterface, Syscall from .syscall import SyscallInterface, Syscall
from .CPU import CPU from .CPU import CPU, UserModeCPU
from .debug import launch_debug_session
from .config import RunConfig from .config import RunConfig

@ -9,9 +9,9 @@ from riscemu.CPU import UserModeCPU
if __name__ == '__main__': if __name__ == '__main__':
from .config import RunConfig from .config import RunConfig
from .helpers import *
from .instructions import InstructionSetDict from .instructions import InstructionSetDict
from riscemu.parser import AssemblyFileLoader from .colors import FMT_BOLD, FMT_MAGENTA
from .parser import AssemblyFileLoader
import argparse import argparse
import sys import sys
@ -69,7 +69,8 @@ if __name__ == '__main__':
parser.add_argument('-v', '--verbose', help="Verbosity level (can be used multiple times)", action='count', parser.add_argument('-v', '--verbose', help="Verbosity level (can be used multiple times)", action='count',
default=0) 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 # create a RunConfig from the cli args
cfg_dict = dict( cfg_dict = dict(

@ -89,6 +89,14 @@ class ParseContext:
self._finalize_section() self._finalize_section()
self.section = CurrentSection(name, type, base) 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): 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

@ -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 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 .exceptions import MemoryAccessException
from .helpers import parse_numeric_argument from .helpers import parse_numeric_argument
from .types import Instruction, MemorySection, MemoryFlags, InstructionContext, T_RelativeAddress, \ from .types import Instruction, MemorySection, MemoryFlags, InstructionContext, T_RelativeAddress, \
@ -13,7 +13,8 @@ from .types import Instruction, MemorySection, MemoryFlags, InstructionContext,
class SimpleInstruction(Instruction): 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.context = context
self.name = name self.name = name
self.args = args self.args = args

@ -3,6 +3,7 @@ RiscEmu (c) 2021 Anton Lydike
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT
""" """
import os.path
from .base import SimpleInstruction from .base import SimpleInstruction
from .helpers import * from .helpers import *
@ -11,6 +12,7 @@ if typing.TYPE_CHECKING:
from riscemu import CPU, Registers from riscemu import CPU, Registers
def launch_debug_session(cpu: 'CPU', prompt=""): def launch_debug_session(cpu: 'CPU', prompt=""):
if cpu.debugger_active: if cpu.debugger_active:
return return
@ -46,12 +48,12 @@ def launch_debug_session(cpu: 'CPU', prompt=""):
if len(args) > 3: if len(args) > 3:
print("Invalid arg count!") print("Invalid arg count!")
return return
bin = mmu.get_bin_containing(cpu.pc) context = mmu.context_for(cpu.pc)
ins = SimpleInstruction( ins = SimpleInstruction(
name, name,
tuple(args), tuple(args),
bin.context, context,
cpu.pc) cpu.pc)
print(FMT_DEBUG + "Running instruction {}".format(ins) + FMT_NONE) print(FMT_DEBUG + "Running instruction {}".format(ins) + FMT_NONE)
cpu.run_instruction(ins) cpu.run_instruction(ins)
@ -76,11 +78,17 @@ def launch_debug_session(cpu: 'CPU', prompt=""):
# add tab completion # add tab completion
readline.set_completer(rlcompleter.Completer(sess_vars).complete) readline.set_completer(rlcompleter.Completer(sess_vars).complete)
readline.parse_and_bind("tab: complete") readline.parse_and_bind("tab: complete")
if os.path.exists('~/.riscemu_history'):
readline.read_history_file('~/.riscemu_history')
relaunch_debugger = False relaunch_debugger = False
try: 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: finally:
cpu.debugger_active = False cpu.debugger_active = False
readline.write_history_file('~/.riscemu_history')

@ -61,6 +61,7 @@ def to_signed(num: int, bytes=4) -> int:
return num return num
def create_chunks(my_list, chunk_size): 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]]""" """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)] return [my_list[i:i + chunk_size] for i in range(0, len(my_list), chunk_size)]

@ -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)

@ -1,42 +1,43 @@
from .types import ElfMemorySection
from ..MMU import * from ..MMU import *
from abc import abstractmethod from abc import abstractmethod
import typing import typing
from .ElfLoader import ElfExecutable
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from .PrivCPU import PrivCPU from .PrivCPU import PrivCPU
class PrivMMU(MMU): class PrivMMU(MMU):
cpu: 'PrivCPU'
@abstractmethod
def get_entrypoint(self) -> int:
raise
def set_cpu(self, cpu: 'PrivCPU'): def get_sec_containing(self, addr: T_AbsoluteAddress) -> MemorySection:
self.cpu = cpu # try to get an existing section
existing_sec = super().get_sec_containing(addr)
def translate_address(self, addr: int): if existing_sec is not None:
return "" 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): # calc start end end of "free" space
def __init__(self, elf: ElfExecutable): prev_sec_end = 0 if sec_before is None else sec_before.end
super().__init__(conf=RunConfig()) next_sec_start = 0x7FFFFFFF if sec_after is None else sec_before.base
self.entrypoint = elf.symbols['_start']
self.binaries.append(elf) # start at the end of the prev section, or current address - 0xFFFF (aligned to 16 byte boundary)
for sec in elf.sections: start = max(prev_sec_end, align_addr(addr - 0xFFFF, 16))
self.sections.append(sec) # 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: sec = ElfMemorySection(bytearray(end - start), '.empty', self.global_instruction_context(), '', start, MemoryFlags(False, True))
raise NotImplementedError("This is a privMMU, it's initialized with a single ElfExecutable!") self.sections.append(sec)
self._update_state()
def allocate_section(self, name: str, req_size: int, flag: MemoryFlags): return sec
raise NotImplementedError("Not supported!")
def get_entrypoint(self): def global_instruction_context(self) -> InstructionContext:
return self.entrypoint context = InstructionContext()
context.global_symbol_dict = self.global_symbols
return context

@ -15,7 +15,7 @@ from collections import defaultdict
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple, Set, Union, Iterator, Callable, Type 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 .colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_RED, FMT_BOLD
from .exceptions import ParseException from .exceptions import ParseException
from .helpers import format_bytes, get_section_base_name 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)) raise ParseException("Cannot resolve relative symbol {} without an address!".format(symbol))
direction = symbol[-1] direction = symbol[-1]
values = self.numbered_labels.get(symbol[:-1], [])
if direction == 'b': if direction == 'b':
return max([addr for addr in self.numbered_labels.get(symbol[:-1], []) if addr < address_at], return max((addr + self.base_address for addr in values if addr < address_at), default=None)
default=None)
else: else:
return min([addr for addr in self.numbered_labels.get(symbol[:-1], []) if addr > address_at], return min((addr + self.base_address for addr in values if addr > address_at), default=None)
default=None)
else: else:
# if it's not a local symbol, try the globals
if symbol not in self.labels: if symbol not in self.labels:
return self.global_symbol_dict.get(symbol, None) return self.global_symbol_dict.get(symbol, None)
value = self.labels.get(symbol, None) # otherwise return the local symbol
if value is None: return self.labels.get(symbol, None)
return value
return value + self.base_address
class Instruction(ABC): class Instruction(ABC):
@ -218,6 +216,7 @@ class Program:
name: str name: str
context: InstructionContext context: InstructionContext
global_labels: Set[str] global_labels: Set[str]
relative_labels: Set[str]
sections: List[MemorySection] sections: List[MemorySection]
base: Optional[T_AbsoluteAddress] base: Optional[T_AbsoluteAddress]
is_loaded: bool is_loaded: bool
@ -235,6 +234,7 @@ class Program:
self.context = InstructionContext() self.context = InstructionContext()
self.sections = [] self.sections = []
self.global_labels = set() self.global_labels = set()
self.relative_labels = set()
self.base = base self.base = base
self.is_loaded = False self.is_loaded = False
@ -285,18 +285,18 @@ class Program:
print(FMT_MEM + 'WARNING: Program loaded at different address then expected! (loaded at {}, ' 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) '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 # check if we are relocating
# to the defined section bases if self.base != at_addr:
if self.base is None: offset = at_addr if self.base is None else at_addr - self.base
for sec in self.sections:
sec.base += at_addr
if self.base is not None and self.base != at_addr: # move all sections by the offset
# move sections so they are located where they want to be located
offset = at_addr - self.base
for sec in self.sections: for sec in self.sections:
sec.base += offset 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.base = at_addr
self.context.base_address = at_addr self.context.base_address = at_addr
@ -393,9 +393,6 @@ class CPU(ABC):
self.pc = 0 self.pc = 0
self.debugger_active = False self.debugger_active = False
self.sections = list()
self.programs = list()
def run_instruction(self, ins: Instruction): def run_instruction(self, ins: Instruction):
""" """
Execute a single instruction Execute a single instruction
@ -446,3 +443,11 @@ class CPU(ABC):
def get_best_loader_for(self, file_name: str) -> Type[ProgramLoader]: def get_best_loader_for(self, file_name: str) -> Type[ProgramLoader]:
return max(self.get_loaders(), key=lambda ld: ld.can_parse(file_name)) 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
Loading…
Cancel
Save