codecin 5.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codecin/__init__.py +27 -0
- codecin/aot.py +229 -0
- codecin/assembler.py +548 -0
- codecin/cache.py +71 -0
- codecin/cin.py +2558 -0
- codecin/cli.py +282 -0
- codecin/config.py +58 -0
- codecin/console.py +183 -0
- codecin/cpu.py +1654 -0
- codecin/crom.py +222 -0
- codecin/debugger.py +659 -0
- codecin/disasm.py +86 -0
- codecin/errors.py +58 -0
- codecin/isa.py +457 -0
- codecin/jit.py +270 -0
- codecin/libcodecin_native.so +0 -0
- codecin/logger.py +145 -0
- codecin/memory.py +348 -0
- codecin/native/aot/aot.go +70 -0
- codecin/native/aot/build.go +219 -0
- codecin/native/aot/stub_main.go.txt +22 -0
- codecin/native/build.ps1 +33 -0
- codecin/native/build.sh +48 -0
- codecin/native/cmd/codecin/main.go +287 -0
- codecin/native/compiler/codegen.go +1938 -0
- codecin/native/compiler/compile_test.go +164 -0
- codecin/native/compiler/parser.go +1071 -0
- codecin/native/compiler/syscalls.go +86 -0
- codecin/native/compiler/tokenizer.go +336 -0
- codecin/native/compiler/types.go +213 -0
- codecin/native/engine/audio.go +152 -0
- codecin/native/engine/audio_other.go +46 -0
- codecin/native/engine/audio_windows.go +43 -0
- codecin/native/engine/canvas.go +271 -0
- codecin/native/engine/crom.go +86 -0
- codecin/native/engine/encode.go +93 -0
- codecin/native/engine/engine_test.go +198 -0
- codecin/native/engine/isa_gen.go +445 -0
- codecin/native/engine/system.go +190 -0
- codecin/native/engine/termux.go +113 -0
- codecin/native/engine/version_gen.go +7 -0
- codecin/native/engine/vm.go +1157 -0
- codecin/native/go.mod +3 -0
- codecin/native/ir/ir.go +48 -0
- codecin/native/main.go +225 -0
- codecin/native.py +336 -0
- codecin/registers.py +130 -0
- codecin/stats.py +235 -0
- codecin-5.4.2.dist-info/METADATA +985 -0
- codecin-5.4.2.dist-info/RECORD +54 -0
- codecin-5.4.2.dist-info/WHEEL +5 -0
- codecin-5.4.2.dist-info/entry_points.txt +2 -0
- codecin-5.4.2.dist-info/licenses/LICENSE +7 -0
- codecin-5.4.2.dist-info/top_level.txt +1 -0
codecin/debugger.py
ADDED
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
"""调试器: 条件断点、执行历史(前进/后退)、TCP 远程调试服务与交互式调试会话。
|
|
2
|
+
|
|
3
|
+
职责 (自 codecin/cpu.py 拆分):
|
|
4
|
+
- DebugSession 交互式调试会话 (断点命中 / --step 统一命令集)
|
|
5
|
+
- display_state 终端状态渲染 (寄存器/内存/缓存)
|
|
6
|
+
- DebugServer 条件断点 + 执行历史 + TCP 远程调试服务
|
|
7
|
+
|
|
8
|
+
设计约束: 本模块不 import codecin.cpu (仅 TYPE_CHECKING), 通过属性访问 CPU。
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import contextlib
|
|
12
|
+
import socket
|
|
13
|
+
import time
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import TYPE_CHECKING, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
from .console import Colors, Panel
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from .cpu import CPU
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ==================== 状态渲染 ====================
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def fmt_operand(op: tuple) -> str:
|
|
27
|
+
"""格式化操作数为汇编文本 (自 cpu.CPU._fmt_operand 迁移)。"""
|
|
28
|
+
kind = op[0]
|
|
29
|
+
if kind == 'reg':
|
|
30
|
+
return f"X{op[1]}"
|
|
31
|
+
if kind == 'vec':
|
|
32
|
+
return f"V{op[1]}"
|
|
33
|
+
if kind == 'veclane':
|
|
34
|
+
return f"V{op[1]}.{op[2]}"
|
|
35
|
+
if kind == 'imm':
|
|
36
|
+
return f"#{op[1]}"
|
|
37
|
+
if kind == 'mem':
|
|
38
|
+
base, off = op[1], op[2]
|
|
39
|
+
if base >= 0:
|
|
40
|
+
return f"[X{base}, #{off}]" if off else f"[X{base}]"
|
|
41
|
+
return f"[#{off}]"
|
|
42
|
+
if kind == 'cond':
|
|
43
|
+
return op[1]
|
|
44
|
+
if kind == 'str':
|
|
45
|
+
return f"\"@{op[1]}\""
|
|
46
|
+
return str(op)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def display_state(cpu: 'CPU', title: str = "CPU State",
|
|
50
|
+
opcode: Optional[str] = None,
|
|
51
|
+
args: Optional[List[tuple]] = None) -> None:
|
|
52
|
+
"""渲染 CPU 完整状态 (寄存器/标志/内存/缓存)。"""
|
|
53
|
+
console = cpu.console
|
|
54
|
+
console.clear()
|
|
55
|
+
console.rule(f"{Colors.colorize(title, Colors.MAGENTA, True)}")
|
|
56
|
+
|
|
57
|
+
if opcode:
|
|
58
|
+
instr_text = f"{opcode} " + ", ".join(fmt_operand(a) for a in (args or []))
|
|
59
|
+
console.print(str(Panel(instr_text.strip(), title="Current Instruction")))
|
|
60
|
+
|
|
61
|
+
reg_info = {
|
|
62
|
+
'PSTATE': ' '.join(f"{k}={int(v)}" for k, v in cpu.pstate.items()),
|
|
63
|
+
'SP': cpu.sp,
|
|
64
|
+
'PC': cpu.pc,
|
|
65
|
+
}
|
|
66
|
+
if cpu.breakpoints:
|
|
67
|
+
reg_info['BREAKPOINTS'] = ', '.join(f"{b:#x}" for b in cpu.breakpoints)
|
|
68
|
+
cpu.regs.display_registers("General Registers (X0-X31)", reg_info, console)
|
|
69
|
+
|
|
70
|
+
if cpu.config.show_vector_regs:
|
|
71
|
+
cpu.vec_regs.display_vector_registers("Vector Registers (V0-V31)", console)
|
|
72
|
+
|
|
73
|
+
cpu.memory.display_memory("Memory (first 64 bytes)", 0, 64, console)
|
|
74
|
+
|
|
75
|
+
cache_stats = cpu.cache.get_stats()
|
|
76
|
+
console.print(
|
|
77
|
+
f"Cache: {cache_stats['hits']} hits, {cache_stats['misses']} misses "
|
|
78
|
+
f"({cache_stats['hit_rate'] * 100:.1f}% hit rate)")
|
|
79
|
+
console.rule()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
DEBUG_HELP = f"""
|
|
83
|
+
{Colors.colorize('Debug Commands:', Colors.CYAN, True)}
|
|
84
|
+
{Colors.colorize('continue / c', Colors.GREEN)} - Continue execution
|
|
85
|
+
{Colors.colorize('step / s (空行)', Colors.GREEN)} - Single step
|
|
86
|
+
{Colors.colorize('run / r', Colors.GREEN)} - Run freely (退出单步模式)
|
|
87
|
+
{Colors.colorize('reverse', Colors.GREEN)} - Reverse one step
|
|
88
|
+
{Colors.colorize('forward', Colors.GREEN)} - Forward one step
|
|
89
|
+
{Colors.colorize('break <addr> [cond]', Colors.GREEN)} - Set breakpoint
|
|
90
|
+
{Colors.colorize('delete <addr>', Colors.GREEN)} - Remove breakpoint
|
|
91
|
+
{Colors.colorize('watch <addr> [r|w|rw]', Colors.GREEN)} - Set watchpoint
|
|
92
|
+
{Colors.colorize('list / info', Colors.GREEN)} - List breakpoints
|
|
93
|
+
{Colors.colorize('print / p <target>', Colors.GREEN)} - X0-X31, regs, mem [addr], cache, pc, sp
|
|
94
|
+
{Colors.colorize('quit / q', Colors.GREEN)} - Exit simulator
|
|
95
|
+
{Colors.colorize('help / h', Colors.GREEN)} - Show this help
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class ConditionalBreakpoint:
|
|
101
|
+
address: int
|
|
102
|
+
condition: str
|
|
103
|
+
count: int = 0
|
|
104
|
+
hit_count: int = 0
|
|
105
|
+
enabled: bool = True
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ==================== 远程调试服务 ====================
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class DebugServer:
|
|
112
|
+
"""TCP 远程调试服务 + 条件断点求值 + 执行历史 (前进/后退)。
|
|
113
|
+
|
|
114
|
+
注意: 交互式会话在本地以 DebugSession 驱动; 本服务为未来 IDE/远程
|
|
115
|
+
集成预留 (start() 为阻塞式服务循环)。
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def __init__(self, cpu: 'CPU', port: int = 1234):
|
|
119
|
+
self.cpu = cpu
|
|
120
|
+
self.port = port
|
|
121
|
+
self.socket = None
|
|
122
|
+
self.connected = False
|
|
123
|
+
self.running = False
|
|
124
|
+
self.execution_history: List[Dict] = []
|
|
125
|
+
self.history_limit = 1000
|
|
126
|
+
self.history_index = 0
|
|
127
|
+
self.conditional_breakpoints: List[ConditionalBreakpoint] = []
|
|
128
|
+
|
|
129
|
+
# ---------------- 历史记录 ----------------
|
|
130
|
+
|
|
131
|
+
def record_state(self) -> None:
|
|
132
|
+
snapshot = {
|
|
133
|
+
'pc': self.cpu.pc,
|
|
134
|
+
'regs': self.cpu.regs.get_all(),
|
|
135
|
+
'sp': self.cpu.sp,
|
|
136
|
+
'pstate': self.cpu.pstate.copy(),
|
|
137
|
+
'timestamp': time.time(),
|
|
138
|
+
}
|
|
139
|
+
self.execution_history.append(snapshot)
|
|
140
|
+
if len(self.execution_history) > self.history_limit:
|
|
141
|
+
self.execution_history.pop(0)
|
|
142
|
+
self.history_index = len(self.execution_history) - 1
|
|
143
|
+
|
|
144
|
+
def _restore(self, snapshot: Dict) -> None:
|
|
145
|
+
self.cpu.pc = snapshot['pc']
|
|
146
|
+
self.cpu.regs.set_all(snapshot['regs'])
|
|
147
|
+
self.cpu.sp = snapshot['sp']
|
|
148
|
+
self.cpu.pstate = snapshot['pstate'].copy()
|
|
149
|
+
|
|
150
|
+
def reverse_step(self) -> bool:
|
|
151
|
+
if self.history_index > 0:
|
|
152
|
+
self.history_index -= 1
|
|
153
|
+
self._restore(self.execution_history[self.history_index])
|
|
154
|
+
return True
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
def forward_step(self) -> bool:
|
|
158
|
+
if self.history_index < len(self.execution_history) - 1:
|
|
159
|
+
self.history_index += 1
|
|
160
|
+
self._restore(self.execution_history[self.history_index])
|
|
161
|
+
return True
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
# ---------------- 条件断点 ----------------
|
|
165
|
+
|
|
166
|
+
def add_conditional_breakpoint(self, address: int, condition: str) -> None:
|
|
167
|
+
bp = ConditionalBreakpoint(address=address, condition=condition)
|
|
168
|
+
self.conditional_breakpoints.append(bp)
|
|
169
|
+
self.cpu.breakpoints.add(address)
|
|
170
|
+
self.cpu.logger.info(f"Conditional breakpoint set at PC={address:#x}: {condition}")
|
|
171
|
+
|
|
172
|
+
def check_conditional_breakpoints(self) -> bool:
|
|
173
|
+
for bp in self.conditional_breakpoints:
|
|
174
|
+
if bp.address == self.cpu.pc and bp.enabled:
|
|
175
|
+
try:
|
|
176
|
+
namespace = {
|
|
177
|
+
'regs': self.cpu.regs.get_all(),
|
|
178
|
+
'pc': self.cpu.pc,
|
|
179
|
+
'sp': self.cpu.sp,
|
|
180
|
+
'pstate': self.cpu.pstate.copy(),
|
|
181
|
+
}
|
|
182
|
+
if eval(bp.condition, {"__builtins__": {}}, namespace):
|
|
183
|
+
bp.hit_count += 1
|
|
184
|
+
self.cpu.console.print(
|
|
185
|
+
f"{Colors.colorize('Conditional breakpoint hit', Colors.YELLOW)} "
|
|
186
|
+
f"at PC={self.cpu.pc:#x}: {bp.condition}")
|
|
187
|
+
return True
|
|
188
|
+
except Exception as e:
|
|
189
|
+
self.cpu.logger.warning(f"Condition evaluation failed: {e}")
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
# ---------------- TCP 服务 ----------------
|
|
193
|
+
|
|
194
|
+
def start(self) -> None:
|
|
195
|
+
"""阻塞式远程调试服务 (仅绑定 localhost; 供未来 IDE/调试客户端接入)。"""
|
|
196
|
+
try:
|
|
197
|
+
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
198
|
+
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
199
|
+
self.socket.bind(('localhost', self.port))
|
|
200
|
+
self.socket.listen(1)
|
|
201
|
+
self.cpu.logger.info(f"Debug server listening on port {self.port}")
|
|
202
|
+
self.running = True
|
|
203
|
+
while self.running:
|
|
204
|
+
conn, addr = self.socket.accept()
|
|
205
|
+
self.connected = True
|
|
206
|
+
self.cpu.logger.info(f"Debug client connected from {addr}")
|
|
207
|
+
self._handle_connection(conn)
|
|
208
|
+
except Exception as e:
|
|
209
|
+
self.cpu.logger.error(f"Debug server error: {e}")
|
|
210
|
+
|
|
211
|
+
def _handle_connection(self, conn) -> None:
|
|
212
|
+
try:
|
|
213
|
+
while self.running:
|
|
214
|
+
data = conn.recv(1024)
|
|
215
|
+
if not data:
|
|
216
|
+
break
|
|
217
|
+
response = self._process_command(data)
|
|
218
|
+
if response:
|
|
219
|
+
conn.send(response.encode())
|
|
220
|
+
except Exception as e:
|
|
221
|
+
self.cpu.logger.debug(f"Debug connection error: {e}")
|
|
222
|
+
finally:
|
|
223
|
+
conn.close()
|
|
224
|
+
self.connected = False
|
|
225
|
+
|
|
226
|
+
def _process_command(self, data: bytes) -> str:
|
|
227
|
+
cmd = data.decode().strip()
|
|
228
|
+
parts = cmd.split()
|
|
229
|
+
if not parts:
|
|
230
|
+
return "ERROR: Empty command"
|
|
231
|
+
command = parts[0].lower()
|
|
232
|
+
|
|
233
|
+
if command == 'step':
|
|
234
|
+
self.record_state()
|
|
235
|
+
self.cpu.step()
|
|
236
|
+
return "OK: Stepped"
|
|
237
|
+
elif command == 'continue':
|
|
238
|
+
self.cpu.running = True
|
|
239
|
+
return "OK: Continuing"
|
|
240
|
+
elif command == 'reverse':
|
|
241
|
+
return "OK: Reversed" if self.reverse_step() else "ERROR: No history"
|
|
242
|
+
elif command == 'forward':
|
|
243
|
+
return "OK: Forwarded" if self.forward_step() else "ERROR: No history"
|
|
244
|
+
elif command == 'break':
|
|
245
|
+
if len(parts) > 1:
|
|
246
|
+
try:
|
|
247
|
+
addr = int(parts[1], 0)
|
|
248
|
+
if len(parts) > 2:
|
|
249
|
+
cond = ' '.join(parts[2:])
|
|
250
|
+
self.add_conditional_breakpoint(addr, cond)
|
|
251
|
+
return f"OK: Conditional breakpoint at {addr:#x}: {cond}"
|
|
252
|
+
self.cpu.add_breakpoint(addr)
|
|
253
|
+
return f"OK: Breakpoint at {addr:#x}"
|
|
254
|
+
except ValueError:
|
|
255
|
+
return "ERROR: Invalid address"
|
|
256
|
+
return "ERROR: Missing address"
|
|
257
|
+
elif command == 'delete':
|
|
258
|
+
if len(parts) > 1:
|
|
259
|
+
try:
|
|
260
|
+
addr = int(parts[1], 0)
|
|
261
|
+
self.cpu.remove_breakpoint(addr)
|
|
262
|
+
self.conditional_breakpoints = [
|
|
263
|
+
bp for bp in self.conditional_breakpoints if bp.address != addr]
|
|
264
|
+
return f"OK: Removed breakpoint at {addr:#x}"
|
|
265
|
+
except ValueError:
|
|
266
|
+
return "ERROR: Invalid address"
|
|
267
|
+
return "ERROR: Missing address"
|
|
268
|
+
elif command == 'watch':
|
|
269
|
+
if len(parts) > 1:
|
|
270
|
+
try:
|
|
271
|
+
addr = int(parts[1], 0)
|
|
272
|
+
access = parts[2] if len(parts) > 2 else 'rw'
|
|
273
|
+
self.cpu.memory.set_protection(addr, access)
|
|
274
|
+
return f"OK: Watchpoint at {addr:#x} for {access}"
|
|
275
|
+
except ValueError:
|
|
276
|
+
return "ERROR: Invalid address"
|
|
277
|
+
return "ERROR: Missing address"
|
|
278
|
+
elif command == 'regs':
|
|
279
|
+
return str(self.cpu.regs.get_all())
|
|
280
|
+
elif command == 'pc':
|
|
281
|
+
return f"PC: {self.cpu.pc:#x}"
|
|
282
|
+
elif command == 'mem':
|
|
283
|
+
if len(parts) > 1:
|
|
284
|
+
try:
|
|
285
|
+
addr = int(parts[1], 0)
|
|
286
|
+
if len(parts) > 2:
|
|
287
|
+
value = int(parts[2], 0)
|
|
288
|
+
self.cpu.memory.write_byte(addr, value)
|
|
289
|
+
return f"OK: mem[{addr:#x}] = {value:#x}"
|
|
290
|
+
value = self.cpu.memory.read_byte(addr)
|
|
291
|
+
return f"mem[{addr:#x}] = {value:#x}"
|
|
292
|
+
except ValueError:
|
|
293
|
+
return "ERROR: Invalid address or value"
|
|
294
|
+
return "ERROR: Missing address"
|
|
295
|
+
elif command == 'history':
|
|
296
|
+
if len(parts) > 1 and parts[1] == 'clear':
|
|
297
|
+
self.execution_history.clear()
|
|
298
|
+
self.history_index = 0
|
|
299
|
+
return "OK: History cleared"
|
|
300
|
+
return f"OK: History size: {len(self.execution_history)}, index: {self.history_index}"
|
|
301
|
+
elif command == 'info':
|
|
302
|
+
if len(parts) > 1:
|
|
303
|
+
if parts[1] == 'break':
|
|
304
|
+
lines = ["Breakpoints:"]
|
|
305
|
+
for bp in self.cpu.breakpoints:
|
|
306
|
+
lines.append(f" {bp:#x}")
|
|
307
|
+
for bp in self.conditional_breakpoints:
|
|
308
|
+
lines.append(f" {bp.address:#x} (cond: {bp.condition})")
|
|
309
|
+
return "\n".join(lines)
|
|
310
|
+
elif parts[1] == 'regs':
|
|
311
|
+
return str(self.cpu.regs.get_all())
|
|
312
|
+
elif parts[1] == 'pc':
|
|
313
|
+
return f"PC: {self.cpu.pc:#x}"
|
|
314
|
+
return "ERROR: Missing info target"
|
|
315
|
+
elif command == 'quit':
|
|
316
|
+
self.running = False
|
|
317
|
+
return "OK: Quitting"
|
|
318
|
+
return f"ERROR: Unknown command: {command}"
|
|
319
|
+
|
|
320
|
+
# ---------------- A4: 驱动式远程调试 ----------------
|
|
321
|
+
|
|
322
|
+
@staticmethod
|
|
323
|
+
def _readline(conn) -> str:
|
|
324
|
+
buf = []
|
|
325
|
+
while True:
|
|
326
|
+
ch = conn.recv(1)
|
|
327
|
+
if not ch:
|
|
328
|
+
return ''
|
|
329
|
+
if ch == b'\n':
|
|
330
|
+
return ''.join(buf)
|
|
331
|
+
if ch != b'\r':
|
|
332
|
+
buf.append(ch.decode('utf-8', 'replace'))
|
|
333
|
+
return ''.join(buf)
|
|
334
|
+
|
|
335
|
+
@staticmethod
|
|
336
|
+
def _send(conn, line: str) -> None:
|
|
337
|
+
conn.sendall((line + '\n').encode('utf-8'))
|
|
338
|
+
|
|
339
|
+
def _burst(self) -> str:
|
|
340
|
+
"""continue: 执行到断点/停机/指令上限。返回 'halt' | 'limit' | 'paused'。"""
|
|
341
|
+
cpu = self.cpu
|
|
342
|
+
cpu.running = True
|
|
343
|
+
while cpu.running:
|
|
344
|
+
if cpu.pc < 0 or cpu.pc >= len(cpu.instructions):
|
|
345
|
+
return 'halt'
|
|
346
|
+
if cpu.stats.instruction_count >= cpu.config.max_instructions:
|
|
347
|
+
return 'limit'
|
|
348
|
+
# continue 后的首次断点豁免 (与本地会话一致)
|
|
349
|
+
resume_bp = cpu._resume_bp_pc is not None
|
|
350
|
+
if resume_bp:
|
|
351
|
+
cpu._resume_bp_pc = None
|
|
352
|
+
if not resume_bp:
|
|
353
|
+
if self.check_conditional_breakpoints():
|
|
354
|
+
return 'paused'
|
|
355
|
+
if cpu.pc in cpu.breakpoints:
|
|
356
|
+
cpu.console.print(
|
|
357
|
+
f"{Colors.colorize(f'Breakpoint hit at PC={cpu.pc:#x}', Colors.YELLOW)}")
|
|
358
|
+
return 'paused'
|
|
359
|
+
opcode, args = cpu.instructions[cpu.pc]
|
|
360
|
+
try:
|
|
361
|
+
if not cpu.execute(opcode, args):
|
|
362
|
+
return 'halt'
|
|
363
|
+
except Exception as e:
|
|
364
|
+
cpu.running = False
|
|
365
|
+
cpu.logger.error(f"Execution error: {e}")
|
|
366
|
+
raise
|
|
367
|
+
return 'halt'
|
|
368
|
+
|
|
369
|
+
def drive(self) -> None:
|
|
370
|
+
"""单客户端驱动式调试会话 (阻塞; 由 cpu.run 在 --debug-server 时调用)。"""
|
|
371
|
+
cpu = self.cpu
|
|
372
|
+
try:
|
|
373
|
+
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
374
|
+
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
375
|
+
self.socket.bind(('localhost', self.port))
|
|
376
|
+
self.socket.listen(1)
|
|
377
|
+
cpu.logger.info(f"Debug server listening on port {self.port}")
|
|
378
|
+
conn, addr = self.socket.accept()
|
|
379
|
+
except Exception as e:
|
|
380
|
+
cpu.logger.error(f"Debug server error: {e}")
|
|
381
|
+
return
|
|
382
|
+
self.connected = True
|
|
383
|
+
cpu.logger.info(f"Debug client connected from {addr}")
|
|
384
|
+
try:
|
|
385
|
+
self._send(conn, 'Code CIN remote debug ready '
|
|
386
|
+
'(step/continue/break/delete/watch/regs/mem/pc/'
|
|
387
|
+
'history/info/quit)')
|
|
388
|
+
halted = False
|
|
389
|
+
while True:
|
|
390
|
+
line = self._readline(conn)
|
|
391
|
+
if line == '':
|
|
392
|
+
break
|
|
393
|
+
cmd = line.strip()
|
|
394
|
+
if not cmd:
|
|
395
|
+
continue
|
|
396
|
+
parts = cmd.split()
|
|
397
|
+
verb = parts[0].lower()
|
|
398
|
+
if verb in ('quit', 'q', 'exit'):
|
|
399
|
+
self._send(conn, 'BYE')
|
|
400
|
+
break
|
|
401
|
+
if halted and verb in ('step', 's', 'continue', 'c', 'run', 'r'):
|
|
402
|
+
self._send(conn, 'ERROR: program halted')
|
|
403
|
+
continue
|
|
404
|
+
if verb in ('step', 's'):
|
|
405
|
+
self.record_state()
|
|
406
|
+
if cpu.step():
|
|
407
|
+
self._send(conn, f"OK pc=0x{cpu.pc:x}")
|
|
408
|
+
else:
|
|
409
|
+
self._send(conn, 'HALTED')
|
|
410
|
+
halted = True
|
|
411
|
+
continue
|
|
412
|
+
if verb in ('continue', 'c', 'run', 'r'):
|
|
413
|
+
self._send(conn, 'OK continuing')
|
|
414
|
+
try:
|
|
415
|
+
res = self._burst()
|
|
416
|
+
except Exception as e:
|
|
417
|
+
self._send(conn, f'ERROR: {e}')
|
|
418
|
+
halted = True
|
|
419
|
+
continue
|
|
420
|
+
if res == 'halt':
|
|
421
|
+
self._send(conn, 'HALTED')
|
|
422
|
+
halted = True
|
|
423
|
+
elif res == 'limit':
|
|
424
|
+
self._send(conn, 'LIMIT')
|
|
425
|
+
halted = True
|
|
426
|
+
else:
|
|
427
|
+
self._send(conn, f"PAUSED pc=0x{cpu.pc:x}")
|
|
428
|
+
continue
|
|
429
|
+
# 其余命令: 断点/查询等 (驱动空闲时执行)
|
|
430
|
+
try:
|
|
431
|
+
resp = self._process_command(cmd.encode('utf-8'))
|
|
432
|
+
except Exception as e:
|
|
433
|
+
resp = f'ERROR: {e}'
|
|
434
|
+
self._send(conn, resp or 'OK')
|
|
435
|
+
finally:
|
|
436
|
+
with contextlib.suppress(Exception):
|
|
437
|
+
conn.close()
|
|
438
|
+
self.connected = False
|
|
439
|
+
with contextlib.suppress(Exception):
|
|
440
|
+
self.socket.close()
|
|
441
|
+
self.running = False
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
# ==================== 交互式调试会话 ====================
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
class DebugSession:
|
|
448
|
+
"""交互式调试会话 (断点命中 / --step 单步共用同一命令集)。
|
|
449
|
+
|
|
450
|
+
input_fn 可注入 (默认 input), 便于测试与 GUI 接入。
|
|
451
|
+
"""
|
|
452
|
+
|
|
453
|
+
def __init__(self, cpu: 'CPU', input_fn=None):
|
|
454
|
+
self.cpu = cpu
|
|
455
|
+
self.input_fn = input_fn or input
|
|
456
|
+
self.console = cpu.console
|
|
457
|
+
self.server: Optional[DebugServer] = None
|
|
458
|
+
|
|
459
|
+
def _ensure_server(self) -> DebugServer:
|
|
460
|
+
"""会话共用 CPU 上持久化的 DebugServer (保留条件断点/历史)。"""
|
|
461
|
+
if self.cpu.debug_server is None:
|
|
462
|
+
self.cpu.debug_server = DebugServer(self.cpu)
|
|
463
|
+
self.server = self.cpu.debug_server
|
|
464
|
+
return self.server
|
|
465
|
+
|
|
466
|
+
# ---------------- 打印目标 ----------------
|
|
467
|
+
|
|
468
|
+
def _print_value(self, parts: List[str]) -> None:
|
|
469
|
+
if len(parts) < 2:
|
|
470
|
+
self.console.print(f"{Colors.colorize('Missing argument', Colors.RED)}")
|
|
471
|
+
return
|
|
472
|
+
cpu = self.cpu
|
|
473
|
+
target = parts[1]
|
|
474
|
+
if target.lower().startswith('x') and target[1:].isdigit():
|
|
475
|
+
idx = int(target[1:])
|
|
476
|
+
v = cpu._reg(idx)
|
|
477
|
+
self.console.print(f"{target.upper()} = {v} (0x{v:x})")
|
|
478
|
+
elif target == 'regs':
|
|
479
|
+
cpu.regs.display_registers(console=self.console)
|
|
480
|
+
elif target == 'mem':
|
|
481
|
+
if len(parts) > 2:
|
|
482
|
+
try:
|
|
483
|
+
addr = int(parts[2], 0)
|
|
484
|
+
value = cpu.memory.read_qword(addr)
|
|
485
|
+
self.console.print(f"mem[{addr:#x}] = {value} (0x{value:x})")
|
|
486
|
+
except ValueError:
|
|
487
|
+
self.console.print(f"{Colors.colorize('Invalid address', Colors.RED)}")
|
|
488
|
+
else:
|
|
489
|
+
cpu.memory.display_memory(console=self.console)
|
|
490
|
+
elif target == 'cache':
|
|
491
|
+
self.console.print(f"Cache stats: {cpu.cache.get_stats()}")
|
|
492
|
+
elif target == 'pc':
|
|
493
|
+
self.console.print(f"PC: {cpu.pc:#x}")
|
|
494
|
+
elif target == 'sp':
|
|
495
|
+
self.console.print(f"SP: {cpu.sp:#x}")
|
|
496
|
+
else:
|
|
497
|
+
self.console.print(f"{Colors.colorize('Unknown target', Colors.RED)}")
|
|
498
|
+
|
|
499
|
+
def _set_breakpoint(self, parts: List[str]) -> None:
|
|
500
|
+
cpu = self.cpu
|
|
501
|
+
if len(parts) < 2:
|
|
502
|
+
self.console.print(f"{Colors.colorize('Missing address', Colors.RED)}")
|
|
503
|
+
return
|
|
504
|
+
try:
|
|
505
|
+
addr = int(parts[1], 0)
|
|
506
|
+
except ValueError:
|
|
507
|
+
self.console.print(f"{Colors.colorize('Invalid address', Colors.RED)}")
|
|
508
|
+
return
|
|
509
|
+
if len(parts) > 2:
|
|
510
|
+
condition = ' '.join(parts[2:])
|
|
511
|
+
self._ensure_server().add_conditional_breakpoint(addr, condition)
|
|
512
|
+
self.console.print(
|
|
513
|
+
f"{Colors.colorize(f'Conditional breakpoint set at {addr:#x}: {condition}', Colors.GREEN)}")
|
|
514
|
+
else:
|
|
515
|
+
cpu.add_breakpoint(addr)
|
|
516
|
+
self.console.print(
|
|
517
|
+
f"{Colors.colorize(f'Breakpoint set at {addr:#x}', Colors.GREEN)}")
|
|
518
|
+
|
|
519
|
+
def _set_watchpoint(self, parts: List[str]) -> None:
|
|
520
|
+
cpu = self.cpu
|
|
521
|
+
if len(parts) < 2:
|
|
522
|
+
self.console.print(f"{Colors.colorize('Missing address', Colors.RED)}")
|
|
523
|
+
return
|
|
524
|
+
try:
|
|
525
|
+
addr = int(parts[1], 0)
|
|
526
|
+
access = parts[2] if len(parts) > 2 else 'rw'
|
|
527
|
+
cpu.memory.set_protection(addr, access)
|
|
528
|
+
self.console.print(
|
|
529
|
+
f"{Colors.colorize(f'Watchpoint set at {addr:#x} for {access}', Colors.GREEN)}")
|
|
530
|
+
except ValueError:
|
|
531
|
+
self.console.print(f"{Colors.colorize('Invalid address', Colors.RED)}")
|
|
532
|
+
|
|
533
|
+
def _list_breakpoints(self) -> None:
|
|
534
|
+
cpu = self.cpu
|
|
535
|
+
self.console.print(f"{Colors.colorize('Breakpoints:', Colors.BOLD)}")
|
|
536
|
+
for addr in sorted(cpu.breakpoints):
|
|
537
|
+
self.console.print(f" {addr:#x}")
|
|
538
|
+
for bp in self._ensure_server().conditional_breakpoints:
|
|
539
|
+
self.console.print(f" {bp.address:#x} (cond: {bp.condition}, "
|
|
540
|
+
f"hits: {bp.hit_count})")
|
|
541
|
+
|
|
542
|
+
# ---------------- 命令分发 ----------------
|
|
543
|
+
|
|
544
|
+
def run(self) -> str:
|
|
545
|
+
"""断点命中时进入的会话主循环。
|
|
546
|
+
|
|
547
|
+
返回 'continue' | 'quit' | 'halted'。
|
|
548
|
+
"""
|
|
549
|
+
cpu = self.cpu
|
|
550
|
+
cpu.is_debugging = True
|
|
551
|
+
server = self._ensure_server()
|
|
552
|
+
self.console.print(
|
|
553
|
+
f"{Colors.colorize('Debug mode (type help for commands)', Colors.BLUE, True)}")
|
|
554
|
+
|
|
555
|
+
while cpu.is_debugging:
|
|
556
|
+
try:
|
|
557
|
+
raw = self.input_fn(f"{Colors.colorize('dbg>', Colors.YELLOW)} ").strip()
|
|
558
|
+
except (EOFError, KeyboardInterrupt):
|
|
559
|
+
cpu.is_debugging = False
|
|
560
|
+
return 'continue'
|
|
561
|
+
if not raw:
|
|
562
|
+
continue
|
|
563
|
+
parts = raw.split()
|
|
564
|
+
command = parts[0].lower()
|
|
565
|
+
|
|
566
|
+
if command in ('help', 'h'):
|
|
567
|
+
self.console.print(DEBUG_HELP)
|
|
568
|
+
elif command in ('continue', 'c'):
|
|
569
|
+
cpu.is_debugging = False
|
|
570
|
+
# 建议 5: 继续时豁免当前 PC 一次, 避免立即再次命中同一断点
|
|
571
|
+
cpu._resume_bp_pc = cpu.pc
|
|
572
|
+
return 'continue'
|
|
573
|
+
elif command in ('step', 's'):
|
|
574
|
+
server.record_state()
|
|
575
|
+
if not cpu.step():
|
|
576
|
+
cpu.is_debugging = False
|
|
577
|
+
return 'halted'
|
|
578
|
+
display_state(cpu, "Step Execution")
|
|
579
|
+
elif command in ('reverse',):
|
|
580
|
+
if server.reverse_step():
|
|
581
|
+
self.console.print(f"{Colors.colorize('Reversed one step', Colors.GREEN)}")
|
|
582
|
+
display_state(cpu, "Reverse Step")
|
|
583
|
+
else:
|
|
584
|
+
self.console.print(f"{Colors.colorize('No history available', Colors.RED)}")
|
|
585
|
+
elif command == 'forward':
|
|
586
|
+
if server.forward_step():
|
|
587
|
+
self.console.print(f"{Colors.colorize('Forward one step', Colors.GREEN)}")
|
|
588
|
+
display_state(cpu, "Forward Step")
|
|
589
|
+
else:
|
|
590
|
+
self.console.print(f"{Colors.colorize('No forward history', Colors.RED)}")
|
|
591
|
+
elif command in ('print', 'p'):
|
|
592
|
+
self._print_value(parts)
|
|
593
|
+
elif command == 'break':
|
|
594
|
+
self._set_breakpoint(parts)
|
|
595
|
+
elif command == 'delete':
|
|
596
|
+
if len(parts) > 1:
|
|
597
|
+
try:
|
|
598
|
+
cpu.remove_breakpoint(int(parts[1]))
|
|
599
|
+
except ValueError:
|
|
600
|
+
self.console.print(f"{Colors.colorize('Invalid address', Colors.RED)}")
|
|
601
|
+
elif command == 'watch':
|
|
602
|
+
self._set_watchpoint(parts)
|
|
603
|
+
elif command in ('list', 'info'):
|
|
604
|
+
self._list_breakpoints()
|
|
605
|
+
elif command in ('quit', 'q'):
|
|
606
|
+
cpu.is_debugging = False
|
|
607
|
+
cpu.running = False # 优雅退出运行 (原 sys.exit(0) 的替代)
|
|
608
|
+
return 'quit'
|
|
609
|
+
else:
|
|
610
|
+
self.console.print(f"{Colors.colorize('Unknown command', Colors.RED)}")
|
|
611
|
+
return 'continue'
|
|
612
|
+
|
|
613
|
+
def run_step_mode(self) -> str:
|
|
614
|
+
"""--step 单步模式: 每次执行前渲染状态并等待命令。
|
|
615
|
+
|
|
616
|
+
返回 'next' | 'run' | 'quit' | 'halted'。
|
|
617
|
+
- 'next': 已执行一条指令, 由外层继续 (通常不再使用, 见下)
|
|
618
|
+
- 'run' : 用户选择 continue, 退出单步、自由运行
|
|
619
|
+
"""
|
|
620
|
+
cpu = self.cpu
|
|
621
|
+
server = self._ensure_server()
|
|
622
|
+
while cpu.running:
|
|
623
|
+
if cpu.pc < 0 or cpu.pc >= len(cpu.instructions):
|
|
624
|
+
return 'halted'
|
|
625
|
+
opcode, args = cpu.instructions[cpu.pc]
|
|
626
|
+
display_state(cpu, "Step Execution", opcode, args)
|
|
627
|
+
try:
|
|
628
|
+
raw = self.input_fn(f"{Colors.colorize('step>', Colors.YELLOW)} ").strip()
|
|
629
|
+
except (EOFError, KeyboardInterrupt):
|
|
630
|
+
return 'quit'
|
|
631
|
+
if not raw or raw.lower() in ('s', 'step'):
|
|
632
|
+
server.record_state()
|
|
633
|
+
if not cpu.step():
|
|
634
|
+
return 'halted'
|
|
635
|
+
continue
|
|
636
|
+
parts = raw.split()
|
|
637
|
+
command = parts[0].lower()
|
|
638
|
+
if command in ('c', 'continue', 'r', 'run'):
|
|
639
|
+
return 'run'
|
|
640
|
+
if command in ('q', 'quit'):
|
|
641
|
+
cpu.running = False
|
|
642
|
+
return 'quit'
|
|
643
|
+
if command in ('p', 'print'):
|
|
644
|
+
self._print_value(parts)
|
|
645
|
+
elif command == 'b':
|
|
646
|
+
self._set_breakpoint(parts)
|
|
647
|
+
elif command == 'd':
|
|
648
|
+
if len(parts) > 1:
|
|
649
|
+
try:
|
|
650
|
+
cpu.remove_breakpoint(int(parts[1]))
|
|
651
|
+
except ValueError:
|
|
652
|
+
self.console.print(f"{Colors.colorize('Invalid address', Colors.RED)}")
|
|
653
|
+
elif command in ('list', 'info', 'l'):
|
|
654
|
+
self._list_breakpoints()
|
|
655
|
+
elif command in ('help', 'h', '?'):
|
|
656
|
+
self.console.print(DEBUG_HELP)
|
|
657
|
+
else:
|
|
658
|
+
self.console.print(f"{Colors.colorize('Unknown command', Colors.RED)}")
|
|
659
|
+
return 'quit'
|