Compare commits
2 Commits
6b2d5fa31e
...
d3f06a6795
| Author | SHA1 | Date | |
|---|---|---|---|
| d3f06a6795 | |||
| 44f3e622c8 |
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()
|
||||||
0
src/emulator/__init__.py
Normal file
0
src/emulator/__init__.py
Normal file
@ -37,6 +37,7 @@ class VMExceptionType(Enum):
|
|||||||
class VMException(Exception):
|
class VMException(Exception):
|
||||||
cause: VMExceptionType
|
cause: VMExceptionType
|
||||||
pc: int
|
pc: int
|
||||||
|
message: str = ""
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class VM:
|
class VM:
|
||||||
@ -125,7 +126,8 @@ class VM:
|
|||||||
if self._to_raw_bytes_offset(self.pc) > len(self.mem) - WORD_SIZE:
|
if self._to_raw_bytes_offset(self.pc) > len(self.mem) - WORD_SIZE:
|
||||||
raise VMException(
|
raise VMException(
|
||||||
VMExceptionType.END_OF_MEM,
|
VMExceptionType.END_OF_MEM,
|
||||||
self.pc.value
|
self.pc.value,
|
||||||
|
"couldn't perform step because end of memory occured"
|
||||||
)
|
)
|
||||||
opcode, *_ = instr = self._fetch_instr()
|
opcode, *_ = instr = self._fetch_instr()
|
||||||
opdesc = self._get_opcode_desc(opcode)
|
opdesc = self._get_opcode_desc(opcode)
|
||||||
@ -175,7 +177,8 @@ class VM:
|
|||||||
if not opcode in OPCODES:
|
if not opcode in OPCODES:
|
||||||
raise VMException(
|
raise VMException(
|
||||||
VMExceptionType.INVALID_OPCODE,
|
VMExceptionType.INVALID_OPCODE,
|
||||||
self.pc.value
|
self.pc.value,
|
||||||
|
f"Couldn't resolve an opcode {hex(opcode)}"
|
||||||
)
|
)
|
||||||
return OPCODES[opcode]
|
return OPCODES[opcode]
|
||||||
|
|
||||||
Reference in New Issue
Block a user