started with base type overhaul
parent
0488a9d6bc
commit
5538034f8b
@ -0,0 +1,81 @@
|
||||
"""
|
||||
This file contains a base implementation of Instruction, and MemorySection.
|
||||
|
||||
This aims to be a simple base, usable for everyone who needs the basic functionality, but doesn't
|
||||
want to set up their own subtypes of Instruction and MemorySection
|
||||
"""
|
||||
|
||||
from typing import List, Tuple
|
||||
from .exceptions import MemoryAccessException
|
||||
from .helpers import parse_numeric_argument
|
||||
from .types import Instruction, MemorySection, MemoryFlags, InstructionContext, T_RelativeAddress, \
|
||||
T_AbsoluteAddress, Program
|
||||
|
||||
|
||||
class SimpleInstruction(Instruction):
|
||||
def __init__(self, name: str, args: Tuple[str], context: InstructionContext, addr: T_RelativeAddress):
|
||||
self.context = context
|
||||
self.name = name
|
||||
self.args = args
|
||||
self.addr = addr
|
||||
|
||||
def get_imm(self, num: int) -> int:
|
||||
resolved_label = self.context.resolve_label(self.args[num], self.addr)
|
||||
if resolved_label is None:
|
||||
return parse_numeric_argument(self.args[num])
|
||||
return resolved_label
|
||||
|
||||
def get_imm_reg(self, num: int) -> Tuple[int, str]:
|
||||
return self.get_imm(num + 1), self.get_reg(num)
|
||||
|
||||
def get_reg(self, num: int) -> str:
|
||||
return self.args[num]
|
||||
|
||||
|
||||
class InstructionMemorySection(MemorySection):
|
||||
def __init__(self, instructions: List[Instruction], name: str, context: InstructionContext, owner: str, base: int = 0):
|
||||
self.name = name
|
||||
self.base = base
|
||||
self.context = context
|
||||
self.size = len(instructions) * 4
|
||||
self.flags = MemoryFlags(True, True)
|
||||
self.instructions = instructions
|
||||
self.owner = owner
|
||||
|
||||
def read(self, offset: T_RelativeAddress, size: int) -> bytearray:
|
||||
raise MemoryAccessException("Cannot read raw bytes from instruction section", self.base + offset, size, 'read')
|
||||
|
||||
def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
|
||||
raise MemoryAccessException("Cannot write raw bytes to instruction section", self.base + offset, size, 'write')
|
||||
|
||||
def read_ins(self, offset: T_RelativeAddress) -> Instruction:
|
||||
if offset % 4 != 0:
|
||||
raise MemoryAccessException("Unaligned instruction fetch!", self.base + offset, 4, 'instruction fetch')
|
||||
return self.instructions[offset // 4]
|
||||
|
||||
|
||||
class BinaryDataMemorySection(MemorySection):
|
||||
def __init__(self, data: bytearray, name: str, context: InstructionContext, owner: str, base: int = 0, flags: MemoryFlags = None):
|
||||
self.name = name
|
||||
self.base = base
|
||||
self.context = context
|
||||
self.size = len(data)
|
||||
self.flags = flags if flags is not None else MemoryFlags(False, False)
|
||||
self.data = data
|
||||
self.owner = owner
|
||||
|
||||
def read(self, offset: T_RelativeAddress, size: int) -> bytearray:
|
||||
if offset + size > self.size:
|
||||
raise MemoryAccessException("Out of bounds access in {}".format(self), offset, size, 'read')
|
||||
return self.data[offset:offset + size]
|
||||
|
||||
def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
|
||||
if offset + size > self.size:
|
||||
raise MemoryAccessException("Out of bounds access in {}".format(self), offset, size, 'write')
|
||||
if len(data[0:size]) != size:
|
||||
raise MemoryAccessException("Invalid write parameter sizing", offset, size, 'write')
|
||||
self.data[offset:offset + size] = data[0:size]
|
||||
|
||||
def read_ins(self, offset: T_RelativeAddress) -> Instruction:
|
||||
raise MemoryAccessException("Tried reading instruction on non-executable section {}".format(self),
|
||||
offset, 4, 'instruction fetch')
|
@ -1,188 +0,0 @@
|
||||
"""
|
||||
RiscEmu (c) 2021 Anton Lydike
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
This file contains base classes which represent loaded programs
|
||||
"""
|
||||
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Optional, Tuple, Set
|
||||
|
||||
from .colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE
|
||||
from .exceptions import ParseException
|
||||
from .helpers import format_bytes
|
||||
|
||||
T_RelativeAddress = int
|
||||
T_AbsoluteAddress = int
|
||||
|
||||
NUMBER_SYMBOL_PATTERN = re.compile(r'^\d+[fb]$')
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MemoryFlags:
|
||||
read_only: bool
|
||||
executable: bool
|
||||
|
||||
def __repr__(self):
|
||||
return "{}({},{})".format(
|
||||
self.__class__.__name__,
|
||||
'ro' if self.read_only else 'rw',
|
||||
'x' if self.executable else '-'
|
||||
)
|
||||
|
||||
|
||||
class InstructionContext:
|
||||
base_address: T_AbsoluteAddress
|
||||
"""
|
||||
The address where the instruction block is placed
|
||||
"""
|
||||
|
||||
labels: Dict[str, T_RelativeAddress]
|
||||
"""
|
||||
This dictionary maps all labels to their relative position of the instruction block
|
||||
"""
|
||||
numbered_labels: Dict[str, List[T_RelativeAddress]]
|
||||
"""
|
||||
This dictionary maps numbered labels (which can occur multiple times) to a list of (block-relative) addresses where
|
||||
the label was placed
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.labels = dict()
|
||||
self.numbered_labels = defaultdict(list)
|
||||
self.base_address = 0
|
||||
|
||||
def resolve_label(self, symbol: str, address_at: Optional[T_RelativeAddress] = None) -> Optional[T_RelativeAddress]:
|
||||
if NUMBER_SYMBOL_PATTERN.match(symbol):
|
||||
if address_at is None:
|
||||
raise ParseException("Cannot resolve relative symbol {} without an address!".format(symbol))
|
||||
|
||||
direction = symbol[-1]
|
||||
if direction == 'b':
|
||||
return max([addr for addr in self.numbered_labels.get(symbol[:-1], []) 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)
|
||||
else:
|
||||
return self.labels.get(symbol, None)
|
||||
|
||||
|
||||
class Instruction(ABC):
|
||||
name: str
|
||||
args: tuple
|
||||
|
||||
@abstractmethod
|
||||
def get_imm(self, num: int) -> int:
|
||||
"""
|
||||
parse and get immediate argument
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_imm_reg(self, num: int) -> Tuple[int, str]:
|
||||
"""
|
||||
parse and get an argument imm(reg)
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_reg(self, num: int) -> str:
|
||||
"""
|
||||
parse and get an register argument
|
||||
"""
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return "{} {}".format(self.name, ", ".join(self.args))
|
||||
|
||||
|
||||
@dataclass
|
||||
class MemorySection(ABC):
|
||||
name: str
|
||||
flags: MemoryFlags
|
||||
size: int
|
||||
base: T_AbsoluteAddress
|
||||
owner: str
|
||||
context: InstructionContext
|
||||
|
||||
@abstractmethod
|
||||
def read(self, offset: T_RelativeAddress, size: int) -> bytearray:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def read_ins(self, offset: T_RelativeAddress) -> Instruction:
|
||||
pass
|
||||
|
||||
def dump(self, start: T_RelativeAddress, end: Optional[T_RelativeAddress], fmt: str = 'hex',
|
||||
bytes_per_row: int = 16, rows: int = 10, group: int = 4):
|
||||
if self.flags.executable:
|
||||
bytes_per_row = 4
|
||||
highlight = None
|
||||
if end is None:
|
||||
end = start + (bytes_per_row * (rows // 2))
|
||||
highlight = start
|
||||
start = start - (bytes_per_row * (rows // 2))
|
||||
if self.flags.executable:
|
||||
print(FMT_MEM + "{}, viewing {} instructions:".format(
|
||||
self, (end - start) // 4
|
||||
) + FMT_NONE)
|
||||
|
||||
for addr in range(start, end, 4):
|
||||
if addr == highlight:
|
||||
print(FMT_UNDERLINE + FMT_ORANGE, end='')
|
||||
print("0x{:x}: {}{}".format(
|
||||
self.base + addr, self.read_ins(addr), FMT_NONE
|
||||
))
|
||||
else:
|
||||
print(FMT_MEM + "{}, viewing {} bytes:".format(
|
||||
self, (end - start)
|
||||
) + FMT_NONE)
|
||||
|
||||
for addr in range(start, end, bytes_per_row):
|
||||
hi_ind = (highlight - addr) // group
|
||||
print("0x{:x}: {}{}".format(
|
||||
self.base + addr, format_bytes(self.read(addr, bytes_per_row), fmt, group, hi_ind), FMT_NONE
|
||||
))
|
||||
|
||||
def __repr__(self):
|
||||
return "{}[{}] at 0x{:08X} (size={}bytes, flags={}, owner={})".format(
|
||||
self.__class__.__name__,
|
||||
self.name,
|
||||
self.base,
|
||||
self.size,
|
||||
self.flags,
|
||||
self.owner
|
||||
)
|
||||
|
||||
|
||||
class Program:
|
||||
name: str
|
||||
context: InstructionContext
|
||||
global_labels: Set[str]
|
||||
sections: List[MemorySection]
|
||||
base: T_AbsoluteAddress = 0
|
||||
|
||||
def __init__(self, name: str, base: int = 0):
|
||||
self.name = name
|
||||
self.context = InstructionContext()
|
||||
self.sections = []
|
||||
self.base = base
|
||||
self.global_labels = set()
|
||||
|
||||
def add_section(self, sec: MemorySection):
|
||||
self.sections.append(sec)
|
||||
|
||||
def __repr__(self):
|
||||
return "{}(name={},context={},globals={},sections={},base={})".format(
|
||||
self.__class__.__name__, self.name, self.context, self.global_labels,
|
||||
[s.name for s in self.sections], self.base
|
||||
)
|
@ -0,0 +1,140 @@
|
||||
import json
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from functools import lru_cache
|
||||
from typing import Tuple, Dict, Set
|
||||
|
||||
from riscemu import MemoryAccessException
|
||||
from riscemu.priv.Exceptions import InstructionAccessFault, InstructionAddressMisalignedTrap, LoadAccessFault
|
||||
from riscemu.types import Instruction, InstructionContext, T_RelativeAddress, MemoryFlags, T_AbsoluteAddress
|
||||
from riscemu.base import BinaryDataMemorySection
|
||||
from riscemu.colors import FMT_NONE, FMT_PARSE
|
||||
from riscemu.decoder import format_ins, RISCV_REGS, decode
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ElfInstruction(Instruction):
|
||||
name: str
|
||||
args: Tuple[int]
|
||||
encoded: int
|
||||
|
||||
def get_imm(self, num: int) -> int:
|
||||
return self.args[num]
|
||||
|
||||
def get_imm_reg(self, num: int) -> Tuple[int, int]:
|
||||
return self.args[-1], self.args[-2]
|
||||
|
||||
def get_reg(self, num: int) -> str:
|
||||
return RISCV_REGS[self.args[num]]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if self.name == 'jal' and self.args[0] == 0:
|
||||
return "j {}".format(self.args[1])
|
||||
if self.name == 'addi' and self.args[2] == 0:
|
||||
return "mv {}, {}".format(self.get_reg(0), self.get_reg(1))
|
||||
if self.name == 'addi' and self.args[1] == 0:
|
||||
return "li {}, {}".format(self.get_reg(0), self.args[2])
|
||||
if self.name == 'ret' and len(self.args) == 0:
|
||||
return "ret"
|
||||
return format_ins(self.encoded, self.name)
|
||||
|
||||
|
||||
class ElfMemorySection(BinaryDataMemorySection):
|
||||
def __init__(self, data: bytearray, name: str, context: InstructionContext, owner: str, base: int,
|
||||
flags: MemoryFlags):
|
||||
super().__init__(data, name, context, owner, base=base, flags=flags)
|
||||
|
||||
@lru_cache
|
||||
def read_ins(self, offset):
|
||||
if not self.flags.executable:
|
||||
print(FMT_PARSE + "Reading instruction from non-executable memory!" + FMT_NONE)
|
||||
raise InstructionAccessFault(offset + self.base)
|
||||
if offset % 4 != 0:
|
||||
raise InstructionAddressMisalignedTrap(offset + self.base)
|
||||
return ElfInstruction(*decode(self.data[offset:offset + 4]))
|
||||
|
||||
def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
|
||||
if self.flags.read_only:
|
||||
raise LoadAccessFault('read-only section', offset + self.base, size, 'write')
|
||||
self.read_ins.cache_clear()
|
||||
return super(ElfMemorySection, self).write(offset, size, data)
|
||||
|
||||
@property
|
||||
def end(self):
|
||||
return self.size + self.base
|
||||
|
||||
|
||||
class MemoryImageDebugInfos:
|
||||
VERSION = '1'
|
||||
"""
|
||||
Schema version
|
||||
"""
|
||||
|
||||
base: T_AbsoluteAddress = 0
|
||||
"""
|
||||
The base address where the image starts. Defaults to zero.
|
||||
"""
|
||||
|
||||
sections: Dict[str, Dict[str, Tuple[int, int]]]
|
||||
"""
|
||||
This dictionary maps a program and section to (start address, section length)
|
||||
"""
|
||||
|
||||
symbols: Dict[str, Dict[str, int]]
|
||||
"""
|
||||
This dictionary maps a program and a symbol to a value
|
||||
"""
|
||||
|
||||
globals: Dict[str, Set[str]]
|
||||
"""
|
||||
This dictionary contains the list of all global symbols of a given program
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
sections: Dict[str, Dict[str, Tuple[int, int]]],
|
||||
symbols: Dict[str, Dict[str, int]],
|
||||
globals: Dict[str, Set[str]],
|
||||
base: int = 0
|
||||
):
|
||||
self.sections = sections
|
||||
self.symbols = symbols
|
||||
self.globals = globals
|
||||
self.base = base
|
||||
|
||||
def serialize(self) -> str:
|
||||
def serialize(obj: any) -> str:
|
||||
if isinstance(obj, defaultdict):
|
||||
return json.dumps(dict(obj), default=serialize)
|
||||
if isinstance(obj, (set, tuple)):
|
||||
return json.dumps(list(obj), default=serialize)
|
||||
return "<<unserializable {}>>".format(getattr(obj, '__qualname__', '{unknown}'))
|
||||
|
||||
return json.dumps(
|
||||
dict(sections=self.sections, symbols=self.symbols, globals=self.globals, base=self.base),
|
||||
default=serialize
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def load(cls, serialized_str: str) -> 'MemoryImageDebugInfos':
|
||||
json_obj: dict = json.loads(serialized_str)
|
||||
|
||||
if 'VERSION' not in json_obj:
|
||||
raise RuntimeError("Unknown MemoryImageDebugInfo version!")
|
||||
|
||||
version: str = json_obj.pop('VERSION')
|
||||
|
||||
# compare major version
|
||||
if version != cls.VERSION or version.split('.')[0] != cls.VERSION.split('.')[0]:
|
||||
raise RuntimeError(
|
||||
"Unknown MemoryImageDebugInfo version! This emulator expects version {}, debug info version {}".format(
|
||||
cls.VERSION, version
|
||||
)
|
||||
)
|
||||
|
||||
return MemoryImageDebugInfos(**json_obj)
|
||||
|
||||
@classmethod
|
||||
def builder(cls) -> 'MemoryImageDebugInfos':
|
||||
return MemoryImageDebugInfos(
|
||||
defaultdict(dict), defaultdict(dict), defaultdict(set)
|
||||
)
|
@ -1,74 +1,412 @@
|
||||
from typing import List, Tuple
|
||||
from .exceptions import MemoryAccessException
|
||||
from .helpers import parse_numeric_argument
|
||||
from .base_types import Instruction, MemorySection, MemoryFlags, InstructionContext, T_RelativeAddress, \
|
||||
T_AbsoluteAddress, Program
|
||||
"""
|
||||
RiscEmu (c) 2021 Anton Lydike
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
This file contains abstract base classes and types, bundling only the absolute basic functionality
|
||||
|
||||
See base.py for some basic implementations of these classes
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Optional, Tuple, Set, Union, Generator, Iterator, Callable, Type
|
||||
|
||||
from . import MMU, InstructionSet
|
||||
from .assembler import get_section_base_name
|
||||
from .colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_PARSE, FMT_RED, FMT_BOLD
|
||||
from .exceptions import ParseException
|
||||
from .helpers import format_bytes
|
||||
|
||||
# define some base type aliases so we can keep track of absolute and relative addresses
|
||||
T_RelativeAddress = int
|
||||
T_AbsoluteAddress = int
|
||||
|
||||
# parser options are just dictionaries with arbitrary values
|
||||
T_ParserOpts = Dict[str, any]
|
||||
|
||||
NUMBER_SYMBOL_PATTERN = re.compile(r'^\d+[fb]$')
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MemoryFlags:
|
||||
read_only: bool
|
||||
executable: bool
|
||||
|
||||
def __repr__(self):
|
||||
return "r{}{}".format(
|
||||
'-' if self.read_only else 'w',
|
||||
'x' if self.executable else '-'
|
||||
)
|
||||
|
||||
|
||||
class InstructionContext:
|
||||
base_address: T_AbsoluteAddress
|
||||
"""
|
||||
The address where the instruction block is placed
|
||||
"""
|
||||
|
||||
labels: Dict[str, T_RelativeAddress]
|
||||
"""
|
||||
This dictionary maps all labels to their relative position of the instruction block
|
||||
"""
|
||||
|
||||
numbered_labels: Dict[str, List[T_RelativeAddress]]
|
||||
"""
|
||||
This dictionary maps numbered labels (which can occur multiple times) to a list of (block-relative) addresses where
|
||||
the label was placed
|
||||
"""
|
||||
|
||||
global_symbol_dict: Dict[str, T_AbsoluteAddress]
|
||||
"""
|
||||
A reference to the MMU for access to global symbols
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.labels = dict()
|
||||
self.numbered_labels = defaultdict(list)
|
||||
self.base_address = 0
|
||||
self.global_symbol_dict = dict()
|
||||
|
||||
def resolve_label(self, symbol: str, address_at: Optional[T_RelativeAddress] = None) -> Optional[T_AbsoluteAddress]:
|
||||
if NUMBER_SYMBOL_PATTERN.match(symbol):
|
||||
if address_at is None:
|
||||
raise ParseException("Cannot resolve relative symbol {} without an address!".format(symbol))
|
||||
|
||||
direction = symbol[-1]
|
||||
if direction == 'b':
|
||||
return max([addr for addr in self.numbered_labels.get(symbol[:-1], []) 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)
|
||||
else:
|
||||
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
|
||||
|
||||
class SimpleInstruction(Instruction):
|
||||
def __init__(self, name: str, args: Tuple[str], context: InstructionContext, addr: T_RelativeAddress):
|
||||
self.context = context
|
||||
self.name = name
|
||||
self.args = args
|
||||
self.addr = addr
|
||||
|
||||
class Instruction(ABC):
|
||||
name: str
|
||||
args: tuple
|
||||
|
||||
@abstractmethod
|
||||
def get_imm(self, num: int) -> int:
|
||||
resolved_label = self.context.resolve_label(self.args[num], self.addr)
|
||||
if resolved_label is None:
|
||||
return parse_numeric_argument(self.args[num])
|
||||
return resolved_label
|
||||
"""
|
||||
parse and get immediate argument
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_imm_reg(self, num: int) -> Tuple[int, str]:
|
||||
return self.get_imm(num + 1), self.get_reg(num)
|
||||
"""
|
||||
parse and get an argument imm(reg)
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_reg(self, num: int) -> str:
|
||||
return self.args[num]
|
||||
"""
|
||||
parse and get an register argument
|
||||
"""
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return "{} {}".format(self.name, ", ".join(self.args))
|
||||
|
||||
class InstructionMemorySection(MemorySection):
|
||||
def __init__(self, instructions: List[Instruction], name: str, context: InstructionContext, owner: Program, base: int = 0):
|
||||
self.name = name
|
||||
self.base = base
|
||||
self.context = context
|
||||
self.size = len(instructions) * 4
|
||||
self.flags = MemoryFlags(True, True)
|
||||
self.instructions = instructions
|
||||
self.owner = owner.name
|
||||
|
||||
@dataclass
|
||||
class MemorySection(ABC):
|
||||
name: str
|
||||
flags: MemoryFlags
|
||||
size: int
|
||||
base: T_AbsoluteAddress
|
||||
owner: str
|
||||
context: InstructionContext
|
||||
|
||||
@property
|
||||
def end(self):
|
||||
return self.base + self.size
|
||||
|
||||
@abstractmethod
|
||||
def read(self, offset: T_RelativeAddress, size: int) -> bytearray:
|
||||
raise MemoryAccessException("Cannot read raw bytes from instruction section", self.base + offset, size, 'read')
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
|
||||
raise MemoryAccessException("Cannot write raw bytes to instruction section", self.base + offset, size, 'write')
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def read_ins(self, offset: T_RelativeAddress) -> Instruction:
|
||||
if offset % 4 != 0:
|
||||
raise MemoryAccessException("Unaligned instruction fetch!", self.base + offset, 4, 'instruction fetch')
|
||||
return self.instructions[offset // 4]
|
||||
pass
|
||||
|
||||
def dump(self, start: T_RelativeAddress, end: Optional[T_RelativeAddress] = None, fmt: str = 'hex',
|
||||
bytes_per_row: int = 16, rows: int = 10, group: int = 4):
|
||||
if self.flags.executable:
|
||||
bytes_per_row = 4
|
||||
highlight = None
|
||||
if end is None:
|
||||
end = min(start + (bytes_per_row * (rows // 2)), self.size - 1)
|
||||
highlight = start
|
||||
start = max(0, start - (bytes_per_row * (rows // 2)))
|
||||
|
||||
if self.flags.executable:
|
||||
print(FMT_MEM + "{}, viewing {} instructions:".format(
|
||||
self, (end - start) // 4
|
||||
) + FMT_NONE)
|
||||
|
||||
for addr in range(start, end, 4):
|
||||
if addr == highlight:
|
||||
print(FMT_UNDERLINE + FMT_ORANGE, end='')
|
||||
print("0x{:04x}: {}{}".format(
|
||||
self.base + addr, self.read_ins(addr), FMT_NONE
|
||||
))
|
||||
else:
|
||||
print(FMT_MEM + "{}, viewing {} bytes:".format(
|
||||
self, (end - start)
|
||||
) + FMT_NONE)
|
||||
|
||||
aligned_end = end - (end % bytes_per_row) if end % bytes_per_row != 0 else end
|
||||
|
||||
for addr in range(start, aligned_end, bytes_per_row):
|
||||
hi_ind = (highlight - addr) // group if highlight is not None else -1
|
||||
print("0x{:04x}: {}{}".format(
|
||||
self.base + addr, format_bytes(self.read(addr, bytes_per_row), fmt, group, hi_ind), FMT_NONE
|
||||
))
|
||||
|
||||
if aligned_end != end:
|
||||
hi_ind = (highlight - aligned_end) // group if highlight is not None else -1
|
||||
print("0x{:04x}: {}{}".format(
|
||||
self.base + aligned_end, format_bytes(
|
||||
self.read(aligned_end, end % bytes_per_row), fmt, group, hi_ind
|
||||
), FMT_NONE
|
||||
))
|
||||
|
||||
def dump_all(self, *args, **kwargs):
|
||||
self.dump(0, self.size, *args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return "{}[{}] at 0x{:08X} (size={}bytes, flags={}, owner={})".format(
|
||||
self.__class__.__name__,
|
||||
self.name,
|
||||
self.base,
|
||||
self.size,
|
||||
self.flags,
|
||||
self.owner
|
||||
)
|
||||
|
||||
|
||||
class Program:
|
||||
"""
|
||||
This represents a collection of sections which together form an executable program
|
||||
|
||||
When you want to create a program which can be located anywhere in memory, set base to None,
|
||||
this signals the other components, that this is relocatable. Set the base of each section to
|
||||
the offset in the program, and everything will be taken care of for you.
|
||||
|
||||
"""
|
||||
name: str
|
||||
context: InstructionContext
|
||||
global_labels: Set[str]
|
||||
sections: List[MemorySection]
|
||||
base: Optional[T_AbsoluteAddress]
|
||||
is_loaded: bool
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
if len(self.sections) == 0:
|
||||
return 0
|
||||
if self.base is None:
|
||||
return self.sections[-1].base + self.sections[-1].size
|
||||
return (self.sections[-1].base - self.base) + self.sections[-1].size
|
||||
|
||||
class BinaryDataMemorySection(MemorySection):
|
||||
def __init__(self, data: bytearray, name: str, context: InstructionContext, owner: Program, base: int = 0):
|
||||
def __init__(self, name: str, base: Optional[int] = None):
|
||||
self.name = name
|
||||
self.context = InstructionContext()
|
||||
self.sections = []
|
||||
self.global_labels = set()
|
||||
self.base = base
|
||||
self.context = context
|
||||
self.size = len(data)
|
||||
self.flags = MemoryFlags(False, False)
|
||||
self.data = data
|
||||
self.owner = owner.name
|
||||
self.loaded = False
|
||||
|
||||
def read(self, offset: T_RelativeAddress, size: int) -> bytearray:
|
||||
if offset + size > self.size:
|
||||
raise MemoryAccessException("Out of bounds access in {}".format(self), offset, size, 'read')
|
||||
return self.data[offset:offset + size]
|
||||
def add_section(self, sec: MemorySection):
|
||||
# print a warning when a section is located before the programs base
|
||||
if self.base is not None:
|
||||
if sec.base < self.base:
|
||||
print(FMT_RED + FMT_BOLD + "WARNING: memory section {} in {} is placed before program base (0x{:x})".format(
|
||||
sec, self.name, self.base
|
||||
) + FMT_NONE)
|
||||
|
||||
def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
|
||||
if offset + size > self.size:
|
||||
raise MemoryAccessException("Out of bounds access in {}".format(self), offset, size, 'write')
|
||||
if len(data[0:size]) != size:
|
||||
raise MemoryAccessException("Invalid write parameter sizing", offset, size, 'write')
|
||||
self.data[offset:offset + size] = data[0:size]
|
||||
self.sections.append(sec)
|
||||
# keep section list ordered
|
||||
self.sections.sort(key=lambda section: section.base)
|
||||
|
||||
def read_ins(self, offset: T_RelativeAddress) -> Instruction:
|
||||
raise MemoryAccessException("Tried reading instruction on non-executable section {}".format(self),
|
||||
offset, 4, 'instruction fetch')
|
||||
def __repr__(self):
|
||||
return "{}(name={},globals={},sections={},base={})".format(
|
||||
self.__class__.__name__, self.name, self.global_labels,
|
||||
[s.name for s in self.sections], self.base
|
||||
)
|
||||
|
||||
@property
|
||||
def entrypoint(self):
|
||||
base = 0 if self.base is None else self.base
|
||||
if '_start' in self.context.labels:
|
||||
return base + self.context.labels.get('_start')
|
||||
if 'main' in self.context.labels:
|
||||
return base + self.context.labels.get('main')
|
||||
for sec in self.sections:
|
||||
if get_section_base_name(sec.name) == '.text' and sec.flags.executable:
|
||||
return base + sec.base
|
||||
|
||||
def loaded_trigger(self, at_addr: T_AbsoluteAddress):
|
||||
"""
|
||||
This trigger is called when the binary is loaded and its final address in memory is determined
|
||||
|
||||
This will do a small sanity check to prevent programs loading twice, or at addresses they don't
|
||||
expect to be loaded.
|
||||
|
||||
:param at_addr: the address where the program will be located
|
||||
"""
|
||||
if self.is_loaded:
|
||||
if at_addr != self.base:
|
||||
raise RuntimeError("Program loaded twice at different addresses! This will probably break things!")
|
||||
return
|
||||
|
||||
if self.base is not None and self.base != at_addr:
|
||||
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
|
||||
|
||||
if self.base != at_addr:
|
||||
# move sections so they are located where they want to be located
|
||||
offset = at_addr - self.base
|
||||
for sec in self.sections:
|
||||
sec.base += offset
|
||||
|
||||
self.base = at_addr
|
||||
self.context.base_address = at_addr
|
||||
|
||||
|
||||
class ProgramLoader(ABC):
|
||||
"""
|
||||
A program loader is always specific to a given source file. It is a place to store all state
|
||||
concerning the parsing and loading of that specific source file, including options.
|
||||
"""
|
||||
|
||||
def __init__(self, source_path: str, options: T_ParserOpts):
|
||||
self.source_path = source_path
|
||||
self.options = options
|
||||
self.filename = os.path.split(self.source_path)[-1]
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def can_parse(cls, source_path: str) -> float:
|
||||
"""
|
||||
Return confidence that the file located at source_path
|
||||
should be parsed and loaded by this loader
|
||||
:param source_path: the path of the source file
|
||||
:return: the confidence that this file belongs to this parser
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def get_options(cls, argv: list[str]) -> [List[str], T_ParserOpts]:
|
||||
"""
|
||||
parse command line args into an options dictionary
|
||||
|
||||
:param argv: the command line args list
|
||||
:return: all remaining command line args and the parser options object
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def instantiate(cls, source_path: str, options: T_ParserOpts) -> 'ProgramLoader':
|
||||
"""
|
||||
Instantiate a loader for the given source file with the required arguments
|
||||
|
||||
:param source_path: the path to the source file
|
||||
:param options: the parsed options (guaranteed to come from this classes get_options method.
|
||||
:return: An instance of a ProgramLoader for the spcified source
|
||||
"""
|
||||
return cls(source_path, options)
|
||||
|
||||
@abstractmethod
|
||||
def parse(self) -> Union[Program, Iterator[Program]]:
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class CPU(ABC):
|
||||
# static cpu configuration
|
||||
INS_XLEN: int = 4
|
||||
|
||||
# housekeeping variables
|
||||
mmu: MMU
|
||||
pc: T_AbsoluteAddress
|
||||
cycle: int
|
||||
halted: bool
|
||||
|
||||
# debugging context
|
||||
debugger_active: bool
|
||||
|
||||
# instruction information
|
||||
instructions: Dict[str, Callable[[Instruction], None]]
|
||||
instruction_sets: Set[InstructionSet]
|
||||
|
||||
def __init__(self, mmu: MMU, instruction_sets: List[Type[InstructionSet]]):
|
||||
self.mmu = mmu
|
||||
|
||||
self.instruction_sets = set()
|
||||
self.instructions = dict()
|
||||
|
||||
for set_class in instruction_sets:
|
||||
ins_set = set_class(self)
|
||||
self.instructions.update(ins_set.load())
|
||||
self.instruction_sets.add(ins_set)
|
||||
|
||||
self.cycle = 0
|
||||
self.pc = 0
|
||||
self.debugger_active = False
|
||||
|
||||
self.sections = list()
|
||||
self.programs = list()
|
||||
|
||||
def run_instruction(self, ins: Instruction):
|
||||
"""
|
||||
Execute a single instruction
|
||||
|
||||
:param ins: The instruction to execute
|
||||
"""
|
||||
if ins.name in self.instructions:
|
||||
self.instructions[ins.name](ins)
|
||||
else:
|
||||
# this should never be reached, as unknown instructions are imparseable
|
||||
raise RuntimeError("Unknown instruction: {}".format(ins))
|
||||
|
||||
def load_program(self, program: Program):
|
||||
self.mmu.load_program(program)
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Returns a representation of the CPU and some of its state.
|
||||
"""
|
||||
return "{}(pc=0x{:08X}, cycle={}, halted={} instructions={})".format(
|
||||
self.__class__.__name__,
|
||||
self.pc,
|
||||
self.cycle,
|
||||
self.halted,
|
||||
" ".join(s.name for s in self.instruction_sets)
|
||||
)
|
||||
|
Loading…
Reference in New Issue