feat: написано первое приближение дебаггера
Сейчас он не запускаестя из-за проблем с модулями
This commit is contained in:
0
src/debugger/__init__.py
Normal file
0
src/debugger/__init__.py
Normal file
170
src/debugger/debugger.py
Normal file
170
src/debugger/debugger.py
Normal 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
5
src/debugger/main.py
Normal file
@ -0,0 +1,5 @@
|
||||
def main():
|
||||
print("Hello world!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user