Compare commits

...

2 Commits

7 changed files with 180 additions and 2 deletions

0
src/debugger/__init__.py Normal file
View File

170
src/debugger/debugger.py Normal file
View File

@ -0,0 +1,170 @@
"""
Отладчик принимает на вход довольно много всего и пытается вам
хоть как-то помочь
"""
from emulator.vm import VM, VMException, Breakpoint, Condition, WORD_SIZE
from dataclasses import dataclass
from typing import TypedDict, Callable, cast
import re
import struct
class DbgInstrDesc(TypedDict):
"""
Описание отдельно взятой инструкции по определенному оффсету
"""
length: int
srcline: int
class DbgDict(TypedDict):
"""
Урезанная версия полной отладочной информации, предоставляемой
в файле dbg.json. Содержит ту информацию, которую может
предоставить ассемблер
"""
src: str
labels: dict[str, int]
instructions: dict[int, DbgInstrDesc]
@dataclass
class Debugger:
_vm: VM
_dbg_dict: DbgDict
_callbacks_table: dict[str, Callable[[list[str]], str]]
_breakpoints: set[int]
_source_lines: list[str]
# will be nessesary in _inspect
UNPACK_SYMBOL_TABLE = {
"b": "b",
"B": "B",
"h": "h",
"H": "H",
"w": "i",
"W": "I"
}
SIZE_SYMBOL_TABLE = {
"b": 1,
"B": 1,
"h": 2,
"H": 2,
"w": 4,
"W": 4
}
def __init__(self, mem: bytearray, dbg_dict: DbgDict):
with open(dbg_dict["src"], 'r') as f:
self._source_lines = f.readlines()
self._vm = VM(mem)
self._dbg_dict = dbg_dict
self._breakpoints = set()
def __init_callbacks__(self):
self._callbacks_table = {
"step": self._step,
"continue": self._continue,
"breakpoint": self._breakpoint,
"print": self._print,
"run": self._run,
"inspect": self._inspect
}
def do_command(self, command: list[str]) -> str:
callback_name = command[0]
if not callback_name in self._callbacks_table:
return "Unknown command"
return self._callbacks_table[callback_name](command[1:])
def _step(self, args: list[str]) -> str:
try:
self._vm.step()
except VMException as e:
# TODO: тут необходимо выкорчевать из
# исключения текст ошибки
return f"Virtual machine exception: {e.message}"
return ""
def _continue(self, args: list[str]) -> str:
try:
self._vm.continue_()
except Breakpoint:
return ""
return "Program finished"
def _breakpoint(self, args: list[str]) -> str:
if len(args) == 0:
addr = self._vm.pc.value
self._breakpoints.add(addr)
src_line = self._dbg_dict["instructions"][addr]["srcline"]
return f"Breakpoint succesfully set on line {src_line}"
desired_addr: int = int(args[0])
addr = -1
# находим за линейное время. Плохая практика, но так как
# сходные коды вряд ли будут длиннее 1000 строк - приемлемо
for addr, desc in self._dbg_dict.items():
desc = cast(DbgInstrDesc, desc)
if not desc["srcline"] == desired_addr:
continue
self._breakpoints.add(int(addr))
return f"Breakpoint succesfully set on line {desired_addr}"
return f"Couldn't place breakpoint on src line {desired_addr}"
def _print(self, args: list[str]) -> str:
to_print = args[0]
if to_print == "pc":
return "pc: " + str(self._vm.pc.value)
elif to_print == "cc":
flags = Condition(self._vm.cc.value)
return (
f"v: {flags.v}\n"
f"n: {flags.n}\n"
f"z: {flags.z}\n"
)
elif re.fullmatch(r'r\d+', to_print):
index = int(to_print[1:])
return f"{to_print}: {self._vm.registers[index].value}"
elif to_print in self._dbg_dict["labels"]:
return f'{to_print}: {self._dbg_dict["labels"][to_print]}'
else:
return "Can't print required value"
def _run(self, args: list[str]) -> str:
try:
self._vm.run()
except Breakpoint:
return ""
return "Program finished"
def _inspect(self, args: list[str]) -> str:
size_arg = args.pop(0)
amount = int(args.pop(0))
mem_location = args.pop(0)
offset = -1
if mem_location in self._dbg_dict["labels"]:
offset = self._dbg_dict["labels"][mem_location]
elif mem_location.isdigit():
offset = int(mem_location) * WORD_SIZE
elif mem_location.startswith("0x"):
offset = int(mem_location, 16)
else:
return ("You passed wrong offset parameter. It should be either "
"name of some label or explicit decimal or hexdecimal "
"number, in the second case it should start with 0x")
try:
sym = self.UNPACK_SYMBOL_TABLE[size_arg]
size = self.SIZE_SYMBOL_TABLE[size_arg]
except KeyError:
return ("Size Error. Please select one of the following "
"options as size: b/B - byte, h/H - 2 bytes, "
"w/W - 4 bytes. Big - unsigned, small - signed")
contents = struct.unpack(
sym*amount,
self._vm.mem[offset:offset+size*amount]
)
return f"{mem_location}:" + " ".join(map(str, contents))

5
src/debugger/main.py Normal file
View File

@ -0,0 +1,5 @@
def main():
print("Hello world!")
if __name__ == "__main__":
main()

0
src/emulator/__init__.py Normal file
View File

View File

@ -37,6 +37,7 @@ class VMExceptionType(Enum):
class VMException(Exception):
cause: VMExceptionType
pc: int
message: str = ""
@dataclass
class VM:
@ -125,7 +126,8 @@ class VM:
if self._to_raw_bytes_offset(self.pc) > len(self.mem) - WORD_SIZE:
raise VMException(
VMExceptionType.END_OF_MEM,
self.pc.value
self.pc.value,
"couldn't perform step because end of memory occured"
)
opcode, *_ = instr = self._fetch_instr()
opdesc = self._get_opcode_desc(opcode)
@ -175,7 +177,8 @@ class VM:
if not opcode in OPCODES:
raise VMException(
VMExceptionType.INVALID_OPCODE,
self.pc.value
self.pc.value,
f"Couldn't resolve an opcode {hex(opcode)}"
)
return OPCODES[opcode]