Skip to content

Commit 3bc7cc0

Browse files
committed
docs: add kvcpu agent debugger design
1 parent 21ab64e commit 3bc7cc0

1 file changed

Lines changed: 236 additions & 0 deletions

File tree

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# kvcpu Agent 调试机制设计
2+
3+
> 草稿 · 2026-07-15
4+
5+
---
6+
7+
## 0. 定位与边界
8+
9+
**`kvspace` 管空间,调试机制管时间。**
10+
11+
| 能力 | 工具 | 本质 |
12+
|------|------|------|
13+
| 读/写/浏览 KV | `kvlang kvspace get/dump/tree` | 静态快照 |
14+
| 等待/通知 KV 变化 | `kvlang kvspace watch/notify` | 被动监听 |
15+
| **激活调试模式** | `kvlang kvspace set /vthread/<vtid>/.debug "step"` | **时序控制** |
16+
| **追踪单步执行** | `kvlang kvspace trace <vtid>` | **NDJSON 轨迹** |
17+
| **单步/恢复/终止** | `kvlang kvspace notify /vthread/<vtid>/.debug.resume <cmd>` | **时序控制** |
18+
19+
检查变量仍然用 `kvspace dump /vthread/<vtid>/...`,不重复。
20+
21+
---
22+
23+
## 1. 架构原则
24+
25+
### 1.1 不需要特殊启动方式
26+
27+
所有通过 `kvlang run`(含 `kvlang serve`)执行的程序,其 kvcpu 实例均内置调试检查点,
28+
**无需** 使用特殊命令行参数启动,也无需重启程序即可激活调试。
29+
30+
> 即使程序已运行数分钟,agent 随时可以写 `.debug = "step"` 进入单步模式。
31+
32+
### 1.2 调试状态存储在 vthread 自身命名空间
33+
34+
调试相关键全部位于 `/vthread/<vtid>/` 下,以 `.` 开头(引擎保留):
35+
36+
```
37+
/vthread/<vtid>/
38+
.debug 调试控制键(agent 写,CPU 读)
39+
"" = 正常执行
40+
"step" = 每条指令后暂停
41+
.debug.pause 暂停事件键(CPU Notify,agent Watch 等待)
42+
值:JSON {"pc":"...","func":"...","frame":"...","op":"..."}
43+
.debug.resume 恢复命令键(agent Notify,CPU Watch 等待)
44+
值:"step"(步进)| "continue"(恢复全速)| "abort"(终止)
45+
```
46+
47+
**不引入任何全局命名空间**(无 `/dbg/` 等),与现有 `/vthread/` `/func/` `/src/` `/sys/` 完全融合。
48+
49+
### 1.3 性能策略
50+
51+
| 模式 | 检查频率 | 原因 |
52+
|------|----------|------|
53+
| 非单步(正常运行) | 仅在函数入口(`isFuncEntryPC`| 每次函数调用 1 次 KV 读,函数体内零开销 |
54+
| 单步模式 | 每条指令 | 已在调试中,overhead 可接受 |
55+
56+
`stepping``Execute` goroutine 的局部变量,无需加锁(每个 vthread 对应一个 goroutine)。
57+
58+
---
59+
60+
## 2. 实现(核心部分)
61+
62+
### 2.1 keytree 键定义(`internal/keytree/vthread.go`
63+
64+
```go
65+
func VThreadDebug(vtid string) string { return "/vthread/" + vtid + "/.debug" }
66+
func VThreadDebugPause(vtid string) string { return "/vthread/" + vtid + "/.debug.pause" }
67+
func VThreadDebugResume(vtid string) string { return "/vthread/" + vtid + "/.debug.resume" }
68+
```
69+
70+
### 2.2 execute.go 内联检查点
71+
72+
```go
73+
stepping := false // 局部变量,无需加锁
74+
75+
for {
76+
// ... decode ...
77+
78+
// 检查点:decode 之后、dispatch 之前(KV 状态一致的时间窗口)
79+
if stepping || isFuncEntryPC(pc) {
80+
v, _ := c.kv.Get(keytree.VThreadDebug(vtid))
81+
switch mode := v.Str(); {
82+
case mode == "" && stepping:
83+
stepping = false // agent 清除标志 → 退出单步
84+
case mode != "":
85+
if !stepping { stepping = true }
86+
debugNotifyPause(ctx, c.kv, vtid, pc, inst)
87+
switch cmd := debugWaitResume(c.kv, vtid); cmd {
88+
case "abort":
89+
vthread.SetError(ctx, c.kv, vtid, pc, "debug: aborted by agent")
90+
return fmt.Errorf("debug: aborted by agent")
91+
case "continue":
92+
stepping = false
93+
c.kv.Del(keytree.VThreadDebug(vtid)) // 恢复全速
94+
// "step" → 保持单步
95+
}
96+
}
97+
}
98+
99+
// ... dispatch ...
100+
}
101+
```
102+
103+
### 2.3 `kvlang kvspace trace <vtid>`
104+
105+
监听 `.debug.pause` 事件,输出 NDJSON,自动 step:
106+
107+
```go
108+
func kvTrace(kv kvspace.KVSpace, vtid string) {
109+
for {
110+
val, err := kv.Watch(pauseKey, 10*time.Second)
111+
if err != nil {
112+
// 超时:检查 vthread 是否已终止
113+
if 已终止 || idle >= 3 { return }
114+
continue
115+
}
116+
fmt.Println(val.Str()) // 输出 NDJSON
117+
kv.Notify(resumeKey, kvspace.Str("step")) // 自动 step
118+
}
119+
}
120+
```
121+
122+
---
123+
124+
## 3. Agent 工作流
125+
126+
### A. 轨迹录制(全自动)
127+
128+
```bash
129+
# 终端 1:启动程序
130+
kvlang run tutorial/06-while/main.kv &
131+
132+
# 获取 vtid(新建 vthread 后 seq 即为 vtid)
133+
VTID=$(kvlang kvspace get /vthread/seq)
134+
135+
# 激活单步模式
136+
kvlang kvspace set /vthread/$VTID/.debug "step"
137+
138+
# 终端 2:开始追踪(输出每条指令的 NDJSON)
139+
kvlang kvspace trace $VTID > /tmp/trace.ndjson
140+
```
141+
142+
### B. 交互式单步检查
143+
144+
```bash
145+
# 激活单步
146+
kvlang kvspace set /vthread/$VTID/.debug "step"
147+
148+
# 等待第一个暂停事件
149+
EVENT=$(kvlang kvspace watch /vthread/$VTID/.debug.pause)
150+
echo "$EVENT" | jq .
151+
152+
# 检查当前帧的变量
153+
FRAME=$(echo "$EVENT" | jq -r .frame)
154+
kvlang kvspace dump "$FRAME"
155+
156+
# 步进
157+
kvlang kvspace notify /vthread/$VTID/.debug.resume "step"
158+
159+
# 恢复全速
160+
kvlang kvspace notify /vthread/$VTID/.debug.resume "continue"
161+
162+
# 终止
163+
kvlang kvspace notify /vthread/$VTID/.debug.resume "abort"
164+
```
165+
166+
### C. 分析轨迹
167+
168+
```bash
169+
# 只看特定函数
170+
jq 'select(.func=="first_div7")' /tmp/trace.ndjson
171+
172+
# 统计各函数执行次数
173+
jq -r '.func' /tmp/trace.ndjson | sort | uniq -c | sort -rn
174+
175+
# 找 return 指令
176+
jq 'select(.op=="return")' /tmp/trace.ndjson
177+
```
178+
179+
---
180+
181+
## 4. 暂停事件 NDJSON 格式
182+
183+
每次暂停输出一行:
184+
185+
```json
186+
{"pc":"/vthread/42/[3,0]/_fn/[1,2]","func":"first_div7","frame":"/vthread/42/[3,0]","op":"ne"}
187+
{"pc":"/vthread/42/[3,0]/_fn/[1,3]","func":"first_div7","frame":"/vthread/42/[3,0]","op":"goto"}
188+
```
189+
190+
| 字段 | 含义 |
191+
|------|------|
192+
| `pc` | 当前指令绝对路径 |
193+
| `func` | 当前函数名(从 `frameRoot/.rootfunc` 读取) |
194+
| `frame` | 当前帧根路径(可用于 `kvspace dump` 检查局部变量) |
195+
| `op` | 即将执行的 opcode(尚未执行) |
196+
197+
---
198+
199+
## 5. 与 GDB / dlv / monkeypatch 对比
200+
201+
| 特性 | GDB | dlv | monkeypatch | kvlang 调试机制 |
202+
|------|-----|-----|-------------|-----------------|
203+
| 目标用户 | 人类 REPL | 人类 REPL | 测试框架 | **Agent / 自动化** |
204+
| 激活方式 | 特殊启动 | 特殊启动 | 代码注入 | **写 KV 键(随时)** |
205+
| 状态检查 | `print x` | `print x` | `assert` | `kvspace dump` |
206+
| 单步 | `next/step` | `next/step` || `notify .debug.resume "step"` |
207+
| 轨迹 || `trace` || `kvspace trace <vtid>` |
208+
| 热替换 | 复杂 || `setattr` | `kvspace set /func/...` |
209+
| 已运行程序 | 需 attach | 需 attach | 不可用 | **直接写 KV(无需 attach)** |
210+
211+
---
212+
213+
## 6. 高级特性(待实现 / 复杂)
214+
215+
### A. 函数入口断点(break:<func>)
216+
217+
`.debug` 写入 `"break:<funcname>"` 时,CPU 在 `isFuncEntryPC` 处比较函数名,
218+
仅命中指定函数时才暂停,其余函数继续全速执行。
219+
220+
### B. 源码位置映射(PC → file:line
221+
222+
需要 parser 在 AST 节点上记录 `Pos`,lower 保留,layoutcode 写入
223+
`/src/<pkg>/<func>/<pc>` = `"file.kv:18"`
224+
225+
### C. `next`(跳过函数调用)
226+
227+
执行到当前帧深度不增加为止:
228+
watch `.debug.pause` 直到 `strings.Count(pc, "/_fn/") == startDepth`
229+
230+
### D. Patch 模式(函数热替换)
231+
232+
```bash
233+
# 直接通过 kvspace 修改已加载函数的指令序列
234+
kvlang kvspace set /func/main/classify/[0,0] '"PATCHED"'
235+
kvlang kvspace notify /vthread/$VTID/.debug.resume "continue"
236+
```

0 commit comments

Comments
 (0)