added interactive mode, fixed some bugs
This commit is contained in:
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)]
|
||||
|
25
riscemu/interactive.py
Normal file
25
riscemu/interactive.py
Normal file
@ -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 get_sec_containing(self, addr: T_AbsoluteAddress) -> MemorySection:
|
||||
# try to get an existing section
|
||||
existing_sec = super().get_sec_containing(addr)
|
||||
|
||||
def set_cpu(self, cpu: 'PrivCPU'):
|
||||
self.cpu = cpu
|
||||
if existing_sec is not None:
|
||||
return existing_sec
|
||||
|
||||
def translate_address(self, addr: int):
|
||||
return ""
|
||||
# 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)
|
||||
|
||||
# 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
|
||||
|
||||
class LoadedElfMMU(PrivMMU):
|
||||
def __init__(self, elf: ElfExecutable):
|
||||
super().__init__(conf=RunConfig())
|
||||
self.entrypoint = elf.symbols['_start']
|
||||
# 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))
|
||||
|
||||
self.binaries.append(elf)
|
||||
for sec in elf.sections:
|
||||
self.sections.append(sec)
|
||||
sec = ElfMemorySection(bytearray(end - start), '.empty', self.global_instruction_context(), '', start, MemoryFlags(False, True))
|
||||
self.sections.append(sec)
|
||||
self._update_state()
|
||||
|
||||
def load_bin(self, exe: Executable) -> LoadedExecutable:
|
||||
raise NotImplementedError("This is a privMMU, it's initialized with a single ElfExecutable!")
|
||||
return sec
|
||||
|
||||
def allocate_section(self, name: str, req_size: int, flag: MemoryFlags):
|
||||
raise NotImplementedError("Not supported!")
|
||||
|
||||
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…
Reference in New Issue
Block a user