715 lines
23 KiB
Markdown
715 lines
23 KiB
Markdown
# TransPyC 调试器(vdb)设计文档
|
||
|
||
> **状态**:设计阶段,暂不实现
|
||
> **创建时间**:2026-06-26
|
||
> **目标**:为 TransPyC 编译的程序提供源码级调试能力,支持断点、变量查看、单步、调用栈、异常栈跟踪
|
||
|
||
---
|
||
|
||
## 1. 背景与动机
|
||
|
||
TransPyC 将 Python 子集编译为 LLVM IR 再编译为原生机器码。当前编译产物**完全不含调试信息**(无 DWARF/PDB),运行时崩溃只能看到错误码和消息,无法定位源码行、查看变量、回溯调用栈。
|
||
|
||
本设计文档规划一个 TransPyC 专用调试器 **vdb**,目标是:
|
||
|
||
- 源码级断点(`break main.py:30`)
|
||
- 变量查看(`print x` / `info locals`)
|
||
- 单步执行(`step` / `next`)
|
||
- 调用栈回溯(`backtrace`)
|
||
- 异常栈跟踪(`raise` 时记录栈帧)
|
||
- 硬错误捕获(segfault / 溢出)
|
||
|
||
---
|
||
|
||
## 2. 现状分析
|
||
|
||
### 2.1 已有可复用资产
|
||
|
||
| 资产 | 位置 | 复用方式 |
|
||
|------|------|---------|
|
||
| 变量映射三件套 `variables`/`_direct_values`/`_reg_values` | `lib/core/LLVMCG/BaseGen.py:79-81` | 序列化到 .tpd 供变量查看 |
|
||
| 变量类型信息 `var_type_info`/`var_signedness`/`var_struct_class` | `lib/core/LLVMCG/BaseGen.py:82-112,124` | 序列化为 VarBinding.TypeDesc |
|
||
| 源码行号跟踪 `_current_lineno`/`_set_node_info`/`_get_source_line` | `lib/core/LLVMCG/BaseGen.py:126-127,138,297-440` | 扩展为 SourceMap |
|
||
| 类元信息表 `class_members`/`class_member_defaults`/`class_parent`/`Vtables` | `lib/core/LLVMCG/BaseGen.py:85-100` | 序列化为 TypeMeta |
|
||
| 符号表序列化 `SymbolTable.ToDict` | `lib/core/SymbolTable.py:265-319` | vdb 直接加载,无需重建 |
|
||
| 异常机制 `__eh_msg_out__`/`__eh_code_out__` | `lib/core/Handles/HandlesRaise.py` / `HandlesTry.py` / `HandlesExprCall.py` | 扩展携带栈信息 |
|
||
| VLogger `compile_error(file, line, code)` 三元组 | `lib/core/VLogger.py:286` | 复用错误位置格式 |
|
||
| 本地堆指针记录 `_local_heap_ptrs`/`_var_to_heap_ptr` | `lib/core/LLVMCG/MemoryOps.py:7-15` | 堆对象追踪起点 |
|
||
|
||
### 2.2 关键缺口
|
||
|
||
| 能力 | 现状 | 缺口说明 |
|
||
|------|------|---------|
|
||
| DWARF 调试信息 | ❌ 完全未生成 | `lib/` 下无任何 `DIBuilder`/`!dbg`/`DICompileUnit` 调用 |
|
||
| PDB 调试信息 | ❌ 完全未生成 | Windows 下 WinDbg/VS 无法源码级调试 |
|
||
| 源码行 → IR 指令映射 | ❌ 无数据结构 | 最核心缺口,需从零构建 SourceMap |
|
||
| 变量名 → 运行时内存 | ⚠️ 仅编译期 ephemeral | `Gen.variables` 随 Gen 实例销毁,需持久化 |
|
||
| 类型元信息运行时查询 | ❌ 无 RTTI/反射 | `class_members` 等均编译期状态,未发射到二进制 |
|
||
| 运行时调用栈 | ❌ 完全无 | 异常仅传 `(code, msg)`,无栈帧链 |
|
||
| 硬错误处理 | ❌ 无 | `__builtin_trap` 仅声明未实现,segfault/SIGFPE 无捕获 |
|
||
| 断点/单步/变量查看 | ❌ 完全无 | `lib/` 下搜索 `breakpoint`/`step`/`watch` 零命中 |
|
||
|
||
---
|
||
|
||
## 3. 路线选择
|
||
|
||
### 3.1 方案 A:纯 DWARF + GDB/LLDB
|
||
|
||
**优点**:生态成熟,无需自研前端
|
||
**缺点**:
|
||
- llvmlite 的 DIBuilder 支持度需验证
|
||
- Python 语义与 C 不完全对应(`self` 是指针但 Python 视为对象)
|
||
- SHA1 命名导致符号名难读
|
||
- 泛型特化类型(`Stack[int]`)在 DWARF 中表达困难
|
||
- 跨模块符号(`6d71a13705da41ad.main`)可读性差
|
||
|
||
### 3.2 方案 B:纯自研 vdb
|
||
|
||
**优点**:完全匹配 Python 语义,可显示原始源码,理解泛型/SHA1/跨模块
|
||
**缺点**:开发量大,需自研调试协议与前端
|
||
|
||
### 3.3 方案 C:混合(推荐)
|
||
|
||
- 生成轻量 DWARF(仅行号 + 变量名)让 GDB 可用作后备
|
||
- 同时生成 TransPyC 专用调试元数据 `.tpd` 文件
|
||
- vdb 前端优先使用 `.tpd`,可降级到 GDB 后端
|
||
|
||
**本设计采用方案 C**,但优先实现 `.tpd` + vdb,DWARF 作为可选增强。
|
||
|
||
---
|
||
|
||
## 4. 核心数据结构
|
||
|
||
### 4.1 .tpd 文件格式(TransPyC Debug)
|
||
|
||
每个 `.ll` 文件配套生成一个 `.tpd` 文件(JSON 格式),包含以下结构:
|
||
|
||
```python
|
||
from dataclasses import dataclass, field
|
||
from typing import Any
|
||
|
||
@dataclass
|
||
class IrLocation:
|
||
"""单条 IR 指令的源码位置映射"""
|
||
FuncName: str # mangled 函数名(含 SHA1 头,如 '6d71a13705da41ad.main')
|
||
BlockName: str # 基本块名(如 'entry'、'if.then')
|
||
InstrIdx: int # 指令在块中的索引
|
||
SourceFile: str # 源码相对路径
|
||
LineNo: int # 源码行号
|
||
ColNo: int # 列号
|
||
|
||
@dataclass
|
||
class VarBinding:
|
||
"""变量绑定信息(作用域内)"""
|
||
VarName: str # Python 变量名(如 'x'、'self')
|
||
LlvmName: str # alloca 名或 SSA(如 '%x'、'%".1"')
|
||
TypeDesc: str # 类型描述('i32' / 'Stack[int]*' / 'i8*')
|
||
IsSigned: bool # 是否有符号整数
|
||
StructClass: str | None # 所属结构体类名(如 'Stack[int]')
|
||
StartLine: int # 作用域起始行
|
||
EndLine: int # 作用域结束行
|
||
Storage: str # 'stack' | 'register' | 'direct' | 'global'
|
||
StackOffset: int | None # 栈偏移(仅 'stack')
|
||
|
||
@dataclass
|
||
class TypeMeta:
|
||
"""类型元数据"""
|
||
TypeName: str # 'Stack[int]' / 'IntPair' / 'memory_block'
|
||
Members: list[tuple[str, str, int]] # (成员名, 类型, 字节偏移)
|
||
Size: int # 总大小(字节)
|
||
Align: int # 对齐
|
||
Parent: str | None # 父类名
|
||
IsException: bool # 是否异常类
|
||
ExceptionCode: int # 异常码(仅 IsException=True)
|
||
HasVTable: bool # 是否有虚表
|
||
IsPacked: bool # 是否紧凑布局
|
||
|
||
@dataclass
|
||
class FuncSig:
|
||
"""函数签名"""
|
||
FuncName: str # mangled 名
|
||
RawName: str # 原始 Python 名(去 SHA1)
|
||
ReturnType: str # 返回类型描述
|
||
ParamNames: list[str]
|
||
ParamTypes: list[str]
|
||
IsExport: bool # 是否导出(@t.CExport)
|
||
IsMethod: bool # 是否实例方法
|
||
ClassName: str | None # 所属类名(方法)
|
||
SourceFile: str
|
||
StartLine: int
|
||
EndLine: int
|
||
HasException: bool # 是否可能抛异常(有 __eh_msg_out__)
|
||
|
||
@dataclass
|
||
class TpdFile:
|
||
"""完整的 .tpd 调试信息文件"""
|
||
SourceMap: list[IrLocation]
|
||
VarScopes: dict[str, list[VarBinding]] # FuncName -> bindings
|
||
TypeMeta: dict[str, TypeMeta]
|
||
FuncSignatures: dict[str, FuncSig]
|
||
ExceptionCodes: dict[int, str] # code -> exception name
|
||
SourceFiles: dict[str, list[str]] # file -> 源码行列表(供 vdb 显示上下文)
|
||
ModuleSha1: str | None
|
||
Version: str = "1.0"
|
||
```
|
||
|
||
### 4.2 序列化格式
|
||
|
||
`.tpd` 文件使用 JSON,字段名采用 PascalCase(与代码一致)。示例:
|
||
|
||
```json
|
||
{
|
||
"Version": "1.0",
|
||
"ModuleSha1": "6d71a13705da41ad",
|
||
"SourceFiles": {
|
||
"App/main.py": ["import t", "import stdio", ...]
|
||
},
|
||
"SourceMap": [
|
||
{"FuncName": "main", "BlockName": "entry", "InstrIdx": 0, "SourceFile": "App/main.py", "LineNo": 255, "ColNo": 0},
|
||
...
|
||
],
|
||
"VarScopes": {
|
||
"6d71a13705da41ad.test_generic_stack": [
|
||
{"VarName": "s", "LlvmName": "%s", "TypeDesc": "Stack[int]*", "IsSigned": false, "StructClass": "Stack[int]", "StartLine": 66, "EndLine": 73, "Storage": "stack", "StackOffset": 0}
|
||
]
|
||
},
|
||
"TypeMeta": {
|
||
"Stack[int]": {
|
||
"TypeName": "Stack[int]",
|
||
"Members": [["data", "[16 x i32]", 0], ["top", "i32", 64]],
|
||
"Size": 68,
|
||
"Align": 4,
|
||
"Parent": null,
|
||
"IsException": false,
|
||
"ExceptionCode": 0,
|
||
"HasVTable": false,
|
||
"IsPacked": false
|
||
}
|
||
},
|
||
"ExceptionCodes": {
|
||
"1": "ValueError",
|
||
"2": "TypeError",
|
||
"99": "Exception",
|
||
"100": "MyCustomError"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 实现阶段
|
||
|
||
### 5.1 Phase 1:源码级断点(最小可用)
|
||
|
||
**目标**:`break main.py:30` + `continue` + `run`
|
||
|
||
#### 5.1.1 编译器改动
|
||
|
||
**位置**:`lib/core/LLVMCG/BaseGen.py`
|
||
|
||
1. **扩展 `_set_node_info`**:记录当前指令的源码位置到 `SourceMap`
|
||
|
||
```python
|
||
def _set_node_info(self, Node: ast.AST) -> None:
|
||
self._current_lineno = getattr(Node, 'lineno', 0)
|
||
self._current_col = getattr(Node, 'col_offset', 0)
|
||
self._current_node_info = f" [line {self._current_lineno}, col {self._current_col}]"
|
||
# 新增:记录到 SourceMap
|
||
if self.func and self.builder and self.builder.block:
|
||
loc: IrLocation = IrLocation(
|
||
FuncName=self.func.name,
|
||
BlockName=self.builder.block.name,
|
||
InstrIdx=len(self.builder.block.instructions),
|
||
SourceFile=self._current_source_file or '',
|
||
LineNo=self._current_lineno,
|
||
ColNo=self._current_col
|
||
)
|
||
self._source_map.append(loc)
|
||
```
|
||
|
||
2. **插入断点桩**:在每行源码对应的首条 IR 指令前插入
|
||
|
||
```llvm
|
||
; 运行时断点桩声明
|
||
declare void @__tpc_breakpoint(i32 %line, i8* %file)
|
||
|
||
; 在 main.py:255 的首条指令前插入
|
||
%src_file_ptr = getelementptr [12 x i8], [12 x i8]* @.src.main_py, i32 0, i32 0
|
||
call void @__tpc_breakpoint(i32 255, i8* %src_file_ptr)
|
||
```
|
||
|
||
3. **条件性插入**:仅当 `--debug` 编译选项启用时插入,release 构建完全剥离
|
||
|
||
4. **生成 .tpd 文件**:函数编译结束时,序列化 `SourceMap` 到 `.tpd`
|
||
|
||
#### 5.1.2 运行时库
|
||
|
||
新建 `includes/vdb_runtime.py`:
|
||
|
||
```python
|
||
import t
|
||
import c
|
||
|
||
# 全局断点表(行号 -> 是否启用)
|
||
_Breakpoints: t.CArray[t.CInt, 65536] | t.CPtr = [0]
|
||
# 当前调试器状态(0=运行, 1=暂停)
|
||
_DebugState: t.CInt = 0
|
||
# 当前源码文件名(用于断点匹配)
|
||
_CurrentFile: t.CArray[t.CInt8T, 256] | t.CPtr = [0]
|
||
|
||
@t.CExport
|
||
def __tpc_breakpoint(line: t.CInt, file: t.CPtr) -> None:
|
||
"""断点桩入口,由编译器在每个源码行前插入调用"""
|
||
# 检查是否启用断点
|
||
if _Breakpoints[line] == 0:
|
||
return
|
||
# 命中断点,陷入调试器循环
|
||
_DebugState = 1
|
||
_tpc_debug_loop(line, file)
|
||
|
||
def _tpc_debug_loop(line: t.CInt, file: t.CPtr) -> None:
|
||
"""调试器主循环,等待用户命令"""
|
||
# 通过 IPC 或 stdio 与 vdb 前端通信
|
||
# 解析命令:continue / step / next / print / info / backtrace
|
||
while _DebugState == 1:
|
||
cmd: t.CArray[t.CInt8T, 256] | t.CPtr = [0]
|
||
stdio.scanf("%s", cmd)
|
||
# ... 命令分派
|
||
```
|
||
|
||
#### 5.1.3 vdb 前端
|
||
|
||
新建 `lib/vdb/` 目录:
|
||
|
||
```
|
||
lib/vdb/
|
||
├── __init__.py
|
||
├── TpdLoader.py # .tpd 文件加载器
|
||
├── BreakpointManager.py # 断点管理
|
||
├── DebugSession.py # 调试会话主循环
|
||
├── CommandParser.py # 命令解析
|
||
└── SourceView.py # 源码显示
|
||
```
|
||
|
||
**核心命令**:
|
||
- `break <file>:<line>` / `b <file>:<line>` — 设置断点
|
||
- `delete <n>` / `d <n>` — 删除断点
|
||
- `continue` / `c` — 继续执行
|
||
- `run` / `r` — 启动程序
|
||
- `list` / `l` — 显示当前源码上下文
|
||
- `info breakpoints` — 列出断点
|
||
|
||
### 5.2 Phase 2:变量查看
|
||
|
||
**目标**:`print x` / `info locals` / `info args`
|
||
|
||
#### 5.2.1 编译器改动
|
||
|
||
**位置**:`lib/core/LLVMCG/FuncGen.py` 或 `lib/core/Handles/HandlesFunctions.py`
|
||
|
||
函数编译结束时,序列化变量绑定:
|
||
|
||
```python
|
||
def _serialize_var_scope(self, FuncName: str) -> list[VarBinding]:
|
||
bindings: list[VarBinding] = []
|
||
for var_name, llvm_val in self.variables.items():
|
||
if llvm_val is None:
|
||
continue
|
||
binding: VarBinding = VarBinding(
|
||
VarName=var_name,
|
||
LlvmName=llvm_val.name if hasattr(llvm_val, 'name') else '',
|
||
TypeDesc=self._describe_llvm_type(llvm_val.type),
|
||
IsSigned=self.var_signedness.get(var_name, True),
|
||
StructClass=self.var_struct_class.get(var_name),
|
||
StartLine=self._func_start_line,
|
||
EndLine=self._func_end_line,
|
||
Storage='stack',
|
||
StackOffset=self._get_alloca_offset(llvm_val) # 新增:记录栈偏移
|
||
)
|
||
bindings.append(binding)
|
||
# 同样处理 _direct_values 和全局变量
|
||
return bindings
|
||
```
|
||
|
||
#### 5.2.2 运行时变量读取
|
||
|
||
调试器通过以下方式读取变量值:
|
||
|
||
1. **栈变量**:通过 `StackOffset` + 当前栈帧基址读取
|
||
2. **全局变量**:通过符号表查找全局符号地址
|
||
3. **寄存器变量**:需调试器理解 SSA 值(较复杂,Phase 2 可跳过,仅支持 stack)
|
||
|
||
#### 5.2.3 类型渲染
|
||
|
||
```python
|
||
def RenderValue(llvm_val: bytes, type_meta: TypeMeta, type_desc: str) -> str:
|
||
"""根据类型渲染变量值"""
|
||
if type_desc == 'i32':
|
||
return str(int.from_bytes(llvm_val, 'little', signed=True))
|
||
elif type_desc == 'i8*':
|
||
# 尝试作为 C 字符串读取
|
||
return f'"{read_cstring(llvm_val)}"'
|
||
elif type_desc.endswith('*') and type_meta:
|
||
# 结构体指针:展开成员
|
||
return RenderStruct(llvm_val, type_meta)
|
||
...
|
||
```
|
||
|
||
### 5.3 Phase 3:单步执行
|
||
|
||
**目标**:`step` / `next` / `finish`
|
||
|
||
基于 SourceMap 实现:
|
||
|
||
- `step`(步入):在下一行源码的首条 IR 指令前设临时断点,包括被调用函数内部
|
||
- `next`(步过):在当前函数的下一行设临时断点,跳过函数调用
|
||
- `finish`(步出):在当前函数的返回点设临时断点
|
||
|
||
**实现**:
|
||
1. 查 SourceMap 找到当前行的下一个源码行
|
||
2. 设置临时断点(一次性)
|
||
3. `continue`
|
||
4. 命中后自动删除临时断点
|
||
|
||
### 5.4 Phase 4:调用栈跟踪
|
||
|
||
**目标**:`backtrace` / `bt` / `frame <n>`
|
||
|
||
#### 5.4.1 编译器改动
|
||
|
||
每个函数入口/出口插入栈帧维护调用:
|
||
|
||
```llvm
|
||
; 函数入口
|
||
call void @__tpc_push_frame(
|
||
i8* getelementptr([N x i8], [N x i8]* @.func.name, i32 0, i32 0), ; 函数名
|
||
i8* getelementptr([M x i8], [M x i8]* @.src.file, i32 0, i32 0), ; 源文件
|
||
i32 255 ; 起始行
|
||
)
|
||
|
||
; 函数出口(每个 ret 前)
|
||
call void @__tpc_pop_frame()
|
||
```
|
||
|
||
#### 5.4.2 运行时栈结构
|
||
|
||
```python
|
||
import t
|
||
|
||
@t.CStruct
|
||
class FrameInfo:
|
||
FuncName: t.CPtr # i8* 函数名
|
||
SrcFile: t.CPtr # i8* 源文件
|
||
LineNo: t.CInt # 当前行号(动态更新)
|
||
PrevFrame: 'FrameInfo' | t.CPtr # 上一帧(链表)
|
||
|
||
# 全局栈顶
|
||
_FrameTop: FrameInfo | t.CPtr = t.CPtr(0)
|
||
|
||
@t.CExport
|
||
def __tpc_push_frame(func_name: t.CPtr, src_file: t.CPtr, line: t.CInt) -> None:
|
||
frame: FrameInfo | t.CPtr = c.cast(FrameInfo, mb.alloc(FrameInfo.__sizeof__()))
|
||
frame.FuncName = func_name
|
||
frame.SrcFile = src_file
|
||
frame.LineNo = line
|
||
frame.PrevFrame = _FrameTop
|
||
_FrameTop = frame
|
||
|
||
@t.CExport
|
||
def __tpc_pop_frame() -> None:
|
||
if _FrameTop != t.CPtr(0):
|
||
old: FrameInfo | t.CPtr = _FrameTop
|
||
_FrameTop = _FrameTop.PrevFrame
|
||
mb.free(old)
|
||
```
|
||
|
||
#### 5.4.3 断点桩更新当前行
|
||
|
||
`__tpc_breakpoint` 命中时,更新 `_FrameTop.LineNo`:
|
||
|
||
```python
|
||
@t.CExport
|
||
def __tpc_breakpoint(line: t.CInt, file: t.CPtr) -> None:
|
||
if _Breakpoints[line] == 0:
|
||
return
|
||
if _FrameTop != t.CPtr(0):
|
||
_FrameTop.LineNo = line
|
||
_DebugState = 1
|
||
_tpc_debug_loop(line, file)
|
||
```
|
||
|
||
### 5.5 Phase 5:异常栈跟踪
|
||
|
||
**目标**:`raise ValueError("msg")` 时输出完整调用栈
|
||
|
||
#### 5.5.1 编译器改动
|
||
|
||
**位置**:`lib/core/Handles/HandlesRaise.py`
|
||
|
||
在 `_HandleRaiseLlvm` 中,抛异常前捕获栈:
|
||
|
||
```python
|
||
def _HandleRaiseLlvm(self, Node: ast.Raise) -> None:
|
||
# ... 原有错误码计算 ...
|
||
# 新增:捕获调用栈
|
||
capture_call = self.Gen.builder.call(
|
||
self._get_or_declare_function('__tpc_capture_trace', ir.FunctionType(ir.VoidType(), [])),
|
||
[]
|
||
)
|
||
# ... 原有异常传播逻辑 ...
|
||
```
|
||
|
||
#### 5.5.2 异常结构扩展
|
||
|
||
将 `__eh_msg_out__`(i8**)扩展为指向包含栈的结构:
|
||
|
||
```python
|
||
import t
|
||
|
||
@t.CStruct
|
||
class ExceptionInfo:
|
||
Code: t.CInt
|
||
Message: t.CArray[t.CInt8T, 256] | t.CPtr
|
||
TraceDepth: t.CInt
|
||
TraceFuncs: t.CArray[t.CPtr, 64] # 函数名指针数组
|
||
TraceLines: t.CArray[t.CInt, 64] # 对应行号
|
||
TraceFiles: t.CArray[t.CPtr, 64] # 对应源文件
|
||
```
|
||
|
||
#### 5.5.3 未捕获异常处理
|
||
|
||
在 `main` 函数返回前检查异常,打印栈:
|
||
|
||
```python
|
||
def main() -> t.CInt:
|
||
# ... 用户代码 ...
|
||
# 编译器自动插入:检查未捕获异常
|
||
if _UnhandledException != t.CPtr(0):
|
||
_tpc_print_trace(_UnhandledException)
|
||
return 1
|
||
return testcheck.end()
|
||
```
|
||
|
||
### 5.6 Phase 6:硬错误捕获
|
||
|
||
**目标**:segfault / 除零 / 栈溢出时进入调试器
|
||
|
||
#### 5.6.1 信号处理器注册
|
||
|
||
```python
|
||
import t
|
||
import c
|
||
|
||
@t.CExport
|
||
def __tpc_signal_handler(sig: t.CInt) -> None:
|
||
"""OS 信号处理入口"""
|
||
__tpc_capture_trace()
|
||
# 打印信号类型
|
||
if sig == 11: # SIGSEGV
|
||
stdio.printf("Segmentation fault\n")
|
||
elif sig == 8: # SIGFPE
|
||
stdio.printf("Arithmetic exception\n")
|
||
# 陷入调试器
|
||
_tpc_debug_loop(0, t.CPtr(0))
|
||
|
||
def __tpc_install_signal_handlers() -> None:
|
||
"""在 main 入口前自动调用"""
|
||
c.signal(11, __tpc_signal_handler) # SIGSEGV
|
||
c.signal(8, __tpc_signal_handler) # SIGFPE
|
||
```
|
||
|
||
#### 5.6.2 编译器改动
|
||
|
||
在 `main` 函数入口自动插入 `__tpc_install_signal_handlers()` 调用(仅 `--debug` 模式)。
|
||
|
||
---
|
||
|
||
## 6. vdb 命令参考
|
||
|
||
### 6.1 启动与控制
|
||
|
||
| 命令 | 缩写 | 说明 |
|
||
|------|------|------|
|
||
| `run [args]` | `r` | 启动被调试程序 |
|
||
| `continue` | `c` | 继续执行至下一断点 |
|
||
| `step` | `s` | 单步进入函数 |
|
||
| `next` | `n` | 单步跳过函数 |
|
||
| `finish` | `f` | 执行至当前函数返回 |
|
||
| `quit` | `q` | 退出调试器 |
|
||
|
||
### 6.2 断点管理
|
||
|
||
| 命令 | 缩写 | 说明 |
|
||
|------|------|------|
|
||
| `break <file>:<line>` | `b` | 设置源码行断点 |
|
||
| `break <func>` | `b` | 设置函数入口断点 |
|
||
| `tbreak <file>:<line>` | `tb` | 临时断点(命中后删除) |
|
||
| `delete <n>` | `d` | 删除断点 n |
|
||
| `info breakpoints` | `i b` | 列出所有断点 |
|
||
|
||
### 6.3 变量查看
|
||
|
||
| 命令 | 缩写 | 说明 |
|
||
|------|------|------|
|
||
| `print <expr>` | `p` | 打印表达式值 |
|
||
| `info locals` | `i lo` | 列出当前局部变量 |
|
||
| `info args` | `i ar` | 列出函数参数 |
|
||
| `display <expr>` | - | 每次暂停自动显示 |
|
||
| `set <var>=<val>` | - |修改变量值 |
|
||
|
||
### 6.4 栈操作
|
||
|
||
| 命令 | 缩写 | 说明 |
|
||
|------|------|------|
|
||
| `backtrace` | `bt` | 显示调用栈 |
|
||
| `frame <n>` | `fr` | 切换到第 n 帧 |
|
||
| `up` | - | 向上切换一帧 |
|
||
| `down` | `do` | 向下切换一帧 |
|
||
|
||
### 6.5 源码浏览
|
||
|
||
| 命令 | 缩写 | 说明 |
|
||
|------|------|------|
|
||
| `list` | `l` | 显示当前行上下文 |
|
||
| `list <file>:<line>` | `l` | 显示指定位置 |
|
||
| `info source` | `i so` | 显示当前源文件信息 |
|
||
|
||
### 6.6 类型与结构
|
||
|
||
| 命令 | 缩写 | 说明 |
|
||
|------|------|------|
|
||
| `ptype <typename>` | `pt` | 显示类型定义 |
|
||
| `info types` | `i ty` | 列出所有类型 |
|
||
| `info exceptions` | `i ex` | 列出异常码表 |
|
||
|
||
---
|
||
|
||
## 7. 实现优先级
|
||
|
||
| 优先级 | 阶段 | 价值 | 改动量 |
|
||
|--------|------|------|--------|
|
||
| P0 | Phase 1(源码断点)+ Phase 2(变量查看) | 80% 调试价值 | 小 |
|
||
| P1 | Phase 4(调用栈) | 崩溃定位关键 | 中 |
|
||
| P2 | Phase 5(异常栈) | Python 语义匹配 | 中 |
|
||
| P3 | Phase 3(单步) | 体验提升 | 中(依赖 Phase 1) |
|
||
| P4 | Phase 6(硬错误) | 兜底保护 | 中 |
|
||
| P5 | DWARF 生成 | GDB 互操作 | 大(可选) |
|
||
|
||
---
|
||
|
||
## 8. 风险与考量
|
||
|
||
### 8.1 性能开销
|
||
|
||
- **断点桩**:仅 `--debug` 构建启用,release 完全剥离
|
||
- **IR 膨胀**:每行插入断点桩约增加 10-20% IR 体积,可接受
|
||
- **运行时开销**:断点桩检查为单次数组访问 + 分支预测,开销极低
|
||
- **栈帧维护**:每函数调用增加 2 次 `push/pop_frame`,约 5-10% 调用开销
|
||
|
||
### 8.2 兼容性
|
||
|
||
- **SHA1 命名**:vdb 需理解 SHA1 前缀,通过 `ModuleSha1` 字段还原为 `模块名.函数名` 显示
|
||
- **泛型特化**:`Stack[int]` 需作为完整类型名处理,TypeMeta 中保留特化名
|
||
- **跨模块**:`.tpd` 文件按模块生成,vdb 启动时合并所有相关 `.tpd`
|
||
- **`main` 特化**:`main` 函数无 SHA1 头,vdb 需特殊处理
|
||
|
||
### 8.3 安全性
|
||
|
||
- **栈帧分配**:使用 `mb.alloc` 而非 `c.malloc`,确保与 TransPyC 内存管理一致
|
||
- **信号处理器**:异步信号中避免调用非异步信号安全函数,仅设置标志位
|
||
|
||
### 8.4 限制
|
||
|
||
- **优化构建**:高优化级别(`-O2`+)下变量可能被优化掉,`.tpd` 中的 VarBinding 可能失效
|
||
- **内联函数**:内联后栈帧信息丢失,需在 `.tpd` 中标记 `IsInline`
|
||
- **尾调用优化**:破坏调用栈链,需禁用或特殊处理
|
||
|
||
---
|
||
|
||
## 9. 文件结构规划
|
||
|
||
```
|
||
lib/vdb/ # vdb 调试器前端
|
||
├── __init__.py
|
||
├── TpdLoader.py # .tpd 文件加载与合并
|
||
├── BreakpointManager.py # 断点管理
|
||
├── DebugSession.py # 调试会话主循环
|
||
├── CommandParser.py # 命令解析
|
||
├── SourceView.py # 源码显示
|
||
├── ValueRenderer.py # 变量值渲染
|
||
├── StackWalker.py # 调用栈遍历
|
||
└── IpcChannel.py # 与被调试进程的 IPC 通信
|
||
|
||
lib/core/LLVMCG/
|
||
└── DebugInfoGen.py # 新增:.tpd 生成器(编译器侧)
|
||
|
||
includes/
|
||
└── vdb_runtime.py # 运行时断点/栈帧/信号处理库
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 与现有系统的集成点
|
||
|
||
### 10.1 编译器集成
|
||
|
||
- **`Phase2Translator.py`**:在生成 `.ll` 后,调用 `DebugInfoGen.GenerateTpd(module, gen, output_path)`
|
||
- **`BaseGen.py`**:`_set_node_info` 扩展记录 SourceMap;函数编译结束序列化 VarScope
|
||
- **`HandlesFunctions.py`**:函数入口/出口插入 `__tpc_push_frame`/`__tpc_pop_frame`(仅 debug 模式)
|
||
- **`HandlesRaise.py`**:`_HandleRaiseLlvm` 插入 `__tpc_capture_trace`
|
||
|
||
### 10.2 project.json 配置
|
||
|
||
新增 `debug` 选项:
|
||
|
||
```json
|
||
{
|
||
"options": {
|
||
"debug": true,
|
||
"debug_info": "tpd"
|
||
}
|
||
}
|
||
```
|
||
|
||
- `debug: true` — 启用调试构建(插入断点桩、栈帧维护、信号处理)
|
||
- `debug_info: "tpd"` — 生成 .tpd 文件(默认)
|
||
- `debug_info: "dwarf"` — 生成 DWARF(未来支持)
|
||
- `debug_info: "both"` — 同时生成(未来支持)
|
||
|
||
### 10.3 命令行集成
|
||
|
||
```bash
|
||
# 编译带调试信息
|
||
transpyc build --debug
|
||
|
||
# 直接启动调试器
|
||
transpyc debug ./output/MyApp.exe
|
||
# 等价于
|
||
vdb ./output/MyApp.exe
|
||
```
|
||
|
||
---
|
||
|
||
## 11. 开放问题
|
||
|
||
1. **llvmlite DIBuilder 支持度**:需验证 llvmlite 是否暴露 DIBuilder 接口,决定 DWARF 生成可行性
|
||
2. **IPC 协议**:vdb 前端与被调试进程的通信方式(管道/Socket/共享内存)待定
|
||
3. **远程调试**:是否支持远程调试(如 ViperOS 裸机目标)需进一步设计
|
||
4. **表达式求值**:`print x.y.z` 等复杂表达式是否需要完整 Python 解释器,还是简化为路径访问
|
||
5. **多线程**:TransPyC 当前是否支持多线程,调试器如何处理线程切换
|
||
|
||
---
|
||
|
||
## 12. 参考文档
|
||
|
||
- 现状分析:基于 2026-06-26 对 `lib/core/` 和 `lib/Projectrans/` 的完整搜索
|
||
- 异常机制:`wiki/09-exceptions.md`
|
||
- 导入机制:`wiki/10-imports.md`
|
||
- 符号表:`lib/core/SymbolTable.py`
|
||
- 类型系统:`lib/core/TypeSpec.py` / `lib/core/Handles/HandlesBase.py`
|
||
- LLVM 生成:`lib/core/LLVMCG/BaseGen.py` / `FuncGen.py` / `MemoryOps.py`
|
||
|
||
---
|
||
|
||
> **本文档为设计阶段产物,暂不实现。待社区需求或开发优先级提升后,按 Phase 1 → 2 → 4 → 5 → 3 → 6 顺序推进。**
|