fundou1081/sv-trace
GitHub: fundou1081/sv-trace
sv-trace是一款用于追踪和可视化数据流向的工具,帮助安全人员快速定位和识别系统漏洞。
Stars: 0 | Forks: 0
# sv-trace
## 人类友好箭头式输出 (M5.1j)
所有 trace 都能用箭头式表达数据流向 — 人眼在终端/文档/聊天里一眼看懂谁驱动谁、谁被读。
### 箭头语义 (固定)
| 符号 | 含义 |
|------|------|
| `←` | driver (信号被这个表达式驱动) |
| `→` | load (信号被这个表达式读取) |
| `⚠` | 多驱动冲突 |
| `✓` | verified (credibility >= 0.8) |
| `✗` | not verified (credibility < 0.8) |
| `⤴` | cross-file 跨文件 |
| `↻` | cycle detected |
### 5 个 API 层级 (都可以用箭头式)
```
from signal_tracer import trace_signal, SignalTracer
# 1. TraceResult / TraceSummary — 一键全部 drivers+loads
result = trace_signal("count", sv, "counter.sv")
print(result.to_arrow())
# DRIVERS (2):
# count ← 8'h00 @ counter.sv:9 [counter] ✓ cred=1.00
# count ← count + data_in @ counter.sv:10 [counter] ✓ cred=1.00
# LOADS (0):
# (none)
# 2. 单条 trace
for d in result.drivers:
print(d.to_arrow())
# count ← 8'h00 @ counter.sv:9 [counter] ✓ cred=1.00
# 3. SignalTracer — 一键多驱动
t = SignalTracer()
t.add_file("buggy.sv", multi_sv); t.build()
print(t.multi_drivers_to_arrow())
# data ⚠ 2 drivers:
# data ← 8'hAA @ buggy.sv:9 [buggy] ✓ cred=1.00
# data ← 8'h55 @ buggy.sv:12 [buggy] ✓ cred=1.00
# 4. 链追踪 — 完整上溯/下溯链
print(t.chain_to_arrow("data_out", direction="driver"))
# data_out ← c ⤴ ← b ← a
# 5. dump 转箭头 — 全链 + summary
print(t.dump_to_arrow("data_out"))
# Chain data_out: 4 hops, avg_cred=0.95, cross-file ✓, cycle ✗
# data_out ← c ✓ ← b ✓ ← a ✓
```
### 直接用 formatter 函数
```
from signal_tracer import format_driver, format_load, format_all, ARROW_DRIVER, ARROW_LOAD
print(format_driver(result.drivers[0]))
print(format_all(result))
print(ARROW_DRIVER) # '←'
print(ARROW_LOAD) # '→'
```
### 与 `summary()` 区别
| 方法 | 适合场景 |
|------|----------|
| `summary()` | 短/字段化/适合 LLM 当 context (e.g. 'counter.sv:10 (always_ff) clk=clk reset=rst_n cond=[!rst_n]') |
| `to_arrow()` | 箭头/数据流/适合人眼扫/聊天贴出来 (e.g. 'count ← count + data_in @ counter.sv:11 ✓ cred=1.0') |
两者并存, 根据场景选。
### Tree / Vertical 风格 (M5.1k) — 长链/文档/聊天友好
当链太长 (≥ 4 个信号) 或要贴到文档/聊天里, 一行箭头看不清楚。换成 **tree 风格** (类似 `tree(1)` 工具的输出) 或 **vertical 风格** (每行一个信号 + 箭头):
```
t = SignalTracer()
t.add_file('top.sv', top_code)
t.add_file('mid.sv', mid_code)
t.add_file('leaf.sv', leaf_code)
t.build()
```
**5 种风格 (全部带 tree 节点) — 选一个**:
```
# 1. arrow (默认): 一行, 短链友好
print(t.chain_to_arrow('top.u_mid.u_leaf_a.out_data', style='arrow'))
# out_data ← out_data ← mid_data (↻ cycle detected)
# 2. tree: tree 风格, Unicode box-drawing
print(t.chain_to_arrow('top.u_mid.u_leaf_a.out_data', style='tree'))
# Driver chain: top.u_mid.u_leaf_a.out_data (3 hops, ↻ cycle)
# ├─ out_data [leaf.sv:11] ✓ cred=1.00
# │ ← out_data [leaf.sv:12] ✓ cred=1.00
# └─ ← mid_data [leaf.sv:9] ✓ cred=1.00
# 3. ascii: 同 tree 但用 ASCII (老终端 / 邮件 / 纯文本 log)
print(t.chain_to_arrow('top.u_mid.u_leaf_a.out_data', style='ascii'))
# Driver chain: top.u_mid.u_leaf_a.out_data (3 hops, ↻ cycle)
# +-- out_data [leaf.sv:11] ✓ cred=1.00
# | ← out_data [leaf.sv:12] ✓ cred=1.00
# +-- ← mid_data [leaf.sv:9] ✓ cred=1.00
# 4. vertical: 每行一个信号, 缩进表示深度
print(t.chain_to_arrow('top.u_mid.u_leaf_a.out_data', style='vertical'))
# out_data @ leaf.sv:11 ✓ cred=1.00
# ← out_data @ leaf.sv:12 ✓ cred=1.00
# ← mid_data @ leaf.sv:9 ✓ cred=1.00
# 5. all / both: arrow + tree 两个都返
print(t.chain_to_arrow('top.u_mid.u_leaf_a.out_data', style='all'))
```
**dump 也支持 tree/vertical**:
```
# dump_to_arrow 默认 1 行, style='tree' 转 tree
print(t.dump_to_arrow('top.u_mid.u_leaf_a.out_data', style='tree'))
# Driver chain: top.u_mid.u_leaf_a.out_data (3 hops)
# ├─ out_data [leaf.sv:11]
# │ ← out_data [leaf.sv:12]
# └─ ← mid_data [leaf.sv:9]
# 还可以用 alias
t.chain_to_tree(signal, use_box=True) # tree style
t.chain_to_tree(signal, use_box=False) # ASCII
t.chain_to_vertical(signal) # vertical
t.dump_to_tree(signal, use_box=True) # dump + tree
t.dump_to_tree(signal, use_box=False) # dump + ascii
```
**`format_driver_chain` / `format_dump_summary` 也都接受 `style` 参数**, 给纯函数用户用。
**怎么选风格**:
- **短链 (≤ 3 个信号)**: `arrow` (默认) — 一行就够
- **中链 (4-7) + 看代码**: `tree` — 节点 + location + cred 一起看
- **中链 + 贴 chat/markdown**: `vertical` — 不依赖 box-drawing
- **老终端 / 邮件 / 纯文本 log**: `ascii` — 不需要 Unicode
- **要全面**: `all` — arrow + tree 都给
## 公开 API
### 函数式
```
from signal_tracer import trace_signal, trace_signal_from_file
result = trace_signal("signal_name", sv_code, "file.sv")
result = trace_signal_from_file("signal_name", "path/to/file.sv")
```
### 类式(多文件 + 层次路径)
```
from signal_tracer import SignalTracer, TraceSummary, ContextBundle
t = SignalTracer()
t.add_file('top.sv', top_code)
t.add_file('sub.sv', sub_code)
t.build()
result = t.trace("signal_name") # TraceSummary
```
### SignalTracer 主要方法
| 方法 | 说明 |
|------|------|
| `add_file(path, code)` | 加一个 .sv 文件到项目(链式) |
| `build()` | 解析所有文件,构建追踪索引(必须先调) |
| `trace(name)` | 追踪信号,返回 `TraceSummary`(智能匹配 hpath / leaf / 数组 / 后缀) |
| `trace_drivers(name)` | 只返回 driver 列表 |
| `trace_loads(name)` | 只返回 load 列表 |
| `find_multi_drivers()` | 找所有被 ≥2 个 scope 驱动的信号(多驱动检测) |
| `get_driver_count(name)` | 返回某信号的不同 scope 数 |
| `get_driver_chain(name, max_depth=10)` | 递归查上游 driver 链(带 cycle detection) |
### TraceSummary 方法
| 方法 | 说明 |
|------|------|
| `get_clock_domains()` | 该信号涉及的所有时钟 |
| `is_multi_driver()` | 是否被多个 scope 驱动 |
| `get_driver_scopes()` | 所有驱动 scope 源码(去重) |
| `to_contexts()` | 打包所有 driver 为 `List[ContextBundle]` |
### ContextBundle 字段
`ContextBundle`(frozen=True,不可变)打包:
- `file` / `line` / `char_offset` — 位置
- `scope_text` / `scope_line_start/end` / `scope_kind` — scope 信息
- `clock` / `reset` — 时钟/复位
- `condition` / `condition_stack` — 嵌套条件栈
- `is_port` / `port_direction` / `hierarchical_path` — 端口 + 层次
- `confidence` — 置信度
- `to_dict()` / `summary()` — 序列化 / 一行可读
## 状态
| 指标 | 数据 |
|------|------|
| 公开 API 测试 | **210/210 通过** (~4s) (含 50 个箭头式输出测试: 28 M5.1j + 22 M5.1k tree/vertical/ascii) |
| 跨版本验证 | ✅ pyslang 10.x **和** 11.x 都 210/210 (make test-cross-version) |
| 真实项目验证 | ✅ OpenTitan 6 模块 (30,218 drivers, 0 warning, 0 empty) |
| 跨文件 fixture | 3 文件 / 3 层 instance (`tests/fixtures/m3_hierarchical/`) |
| Benchmark | 11/11 (0 warning, 0 exception) |
| 旧架构测试 | 已迁移 `tests/_legacy/`, 主测试 68/68 干净通过 |
| 版本 | alpha |
跑测试:
```
python -m pytest tests/ -v
# 跨 pyslang 10/11 版本验证
make test-cross-version
```
## 测试覆盖 (M0–M4)
主测试 `tests/unit/test_signal_tracer.py` 包含 **23 个 TestClass, 117 个 测试**:
| 阶段 | TestClass | 测试数 | 覆盖点 |
|------|-----------|--------|--------|
| M0 | `TestBasic`, `TestControlFlow`, `TestArrays`, `TestNoCrashes` | — | 基础 always_ff/comb/latch, if/else/case 条件, 1D/2D 数组 |
| M1 | `TestTraceResultFields` | — | 完整 TraceResult 字段填充 |
| M1.5 | `TestMultiDriver`, `TestClockResetExtraction`, `TestDriverChain` | — | 多驱动检测, clock/reset 提取, driver_chain 递归 (cycle detection) |
| M2 | `TestContextAccuracy`, `TestContextBundle` | — | line/scope_text 准确性, ContextBundle frozen dataclass |
| M3 | `TestMultiFile` | — | 多文件 build, 层次路径 (`top.u_mid.u_leaf`), 后缀匹配 |
| M4 | `TestExpressionCoverage`, `TestContinuousAssignRobustness`, `TestMultiFileLineFallback`, `TestScopeFilePath`, `TestAdditionalExpressions` | +5 | 17 种 SV 表达式, InvalidExpression 防御, 跨文件行号 (SourceManager), TraceResult.file 精确, 嵌套 MemberAccess+RangeSelect |
| M4.1 | `TestInterfaceModport` | +6 | Interface/Modport 信号追踪 (HierarchicalValue), 跨 modport 读写, m.data[3:0] 位选 |
| M5.1 | `TestCodeEvidence` | +8 | 代码证据链 (CodeEvidence), credibility_score 0-1 量化, is_verified 标记, `trace_verified()` 自动验证 |
| M5.1b | `TestMultiDriverEvidence` | +4 | `find_multi_drivers(verify=True)` 默认自动带 evidence (看到冲突 + 真凭实据) |
| M5.1c | `TestDriverChainEvidence` | +4 | `get_driver_chain(verify=True)` 默认链上每跳自动带 evidence (顺藤摸瓜带 credibility) |
| M5.1d | `TestTraceLoadsEvidence` | +7 | `trace()`/`trace_drivers()`/`trace_loads()` 默认 verify=True, drivers 和 loads 都自动带 evidence (查谁读了某信号) |
| M5.1e | `TestLoadChainEvidence` | +5 | `get_load_chain(verify=True)` 顺藤摸瓜查下游 (与 driver chain 对称) |
| M5.1f | `TestDumpChain` | +9 | `dump_driver_chain()`/`dump_load_chain()` 一次 dump 整链为 dict (含 summary, LLM 友好) |
| M5.1g | `TestDumpMultiDrivers` | +6 | `dump_multi_drivers()` 一次 dump 多驱动检测 (冲突列表 + 每个 driver evidence) |
| M5.1h | `TestSyntaxNodeSnapshot` | +6 | syntax-based evidence 路径: SyntaxNodeSnapshot 冻结 + OpenTitan 跨文件 snippet 精度 |
| M5.1h+ | (Makefile target) | — | 跨 pyslang 10.x/11.x 验证 (`make test-cross-version` 双 venv 跑 160+160 tests) |
各阶段演进:
| 阶段 | 新增测试 | 累计 |
|------|---------|------|
| M0 | 13 | 13 |
| M1 | 13 | 26 |
| M1.5 | 20 | 46 |
| M2 | 13 | 59 |
| M3 | 9 | 68 |
| M4 | 5 | 73 |
| M4.1 | 6 | 74 |
| M5.1 | 8 | 82 |
| M5.1b | 4 | 86 |
| M5.1c | 4 | 90 |
| M5.1d | 7 | 97 |
| M5.1e | 5 | 102 |
| M5.1f | 9 | 111 |
| M5.1g | 6 | 117 |
| M5.1h | 6 | 123 |
主测试套件 (含 `test_signal_tracer.py` 和 `test_evidence_via_syntax.py`) 累计 **160 个** (其他测试文件: 边界/CI/legacy 37 个)。
详见 [tests/README.md](tests/README.md) 和 [TEST_PLAN.md](TEST_PLAN.md)。
## 代码证据链 (M5.1)
每个 trace 都带**可证伪的代码证据链** — 读回实际文件, 验证 `source_expr` 和 `signal_name` 真的在该行。LLM/用户能反查 trace 真的对, 而不是默默相信。
### 核心 API
```
# 方式 1: trace_signal + 传 file_content
result = trace_signal('count', sv_code, 'counter.sv')
for ctx in result.to_contexts(file_content=sv_code):
d = ctx.to_dict()
print(f" credibility={d['credibility_score']} is_verified={d['is_verified']}")
print(f" snippet: {d['evidence_snippet']}")
print(ctx.code_evidence.to_evidence_string())
# 方式 2: SignalTracer 多文件 + 自动 in-memory 验证
t = SignalTracer()
t.add_file('top.sv', top_code)
t.add_file('sub.sv', sub_code)
t.build()
result = t.trace_verified('top.u_sub.signal') # 自动用 self._files 验证
```
### 可信度评分 (credibility_score 0-1)
| 验证项 | 分值 | 说明 |
|--------|------|------|
| `file_readable` | +0.2 | 文件能读 |
| `snippet_present` | +0.2 | line 存在 |
| `matches_source_expr` | +0.4 | 文本里真找到 source_expr |
| `matches_signal_name` | +0.2 | 文本里真找到 signal_name |
`is_verified = file_readable ∧ snippet_present ∧ (matches_source ∨ matches_signal)`
### OpenTitan 验证
```
tx_enable @ uart_core.sv:77:
snippet: 'assign tx_enable = reg2hw.ctrl.tx.q;'
matches: source_expr ✓, signal_name ✓
credibility: 1.0/1.0 (VERIFIED)
context_before: ['']
context_after: [' assign rx_enable = reg2hw.ctrl.rx.q;', ...]
readbuf_threshold @ spi_device.sv:600:
snippet: 'assign readbuf_threshold = reg2hw.read_threshold.q[BufferAw:0];'
credibility: 1.0/1.0 (VERIFIED) — 含 BufferAw 的 RangeSelect 也 OK
```
### 防御性: 不匹配会真实反映
| 场景 | credibility | is_verified |
|------|-------------|-------------|
| 文件不存在 | 0.0 | ❌ |
| 可读但都不匹配 | 0.4 | ❌ |
| 仅 signal_name 匹配 | 0.6 | ✅ |
| 全部匹配 | 1.0 | ✅ |
evidence 不会"假装 OK",会真实反映可信度。
## 代码证据链语法路径 (M5.1h)
**核心问题**: file-based evidence 依赖 `
标签:API 开发, SV 代码分析, SV 语言, 代码追踪, 信号处理, 信号追踪, 可信度评估, 多层级追踪, 多文件分析, 多驱动检测, 完整性分析, 平均可信度, 循环检测, 数据流向, 数据管道, 格式化输出, 电子设计自动化, 箭头式输出, 系统级验证, 跨文件追踪, 软件工程, 逆向工具, 链追踪, 验证与测试