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
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]]:

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

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

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

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

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

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

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

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

@ -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
Loading…
Cancel
Save