# vgi-symbols
**原生符号化即 SQL JOIN** — 将一列 `(build_id, address)`
堆栈帧解析为 `function` + `file` + `line` 以及 **内联调用链**,
通过精确解析原生调试信息(ELF / Mach-O / dSYM 的 DWARF,Windows PDB),
就像 `addr2line` / `llvm-symbolizer` 所做的那样,并由一个 **持久的、以 build-id 为键的
调试信息缓存** 作为支撑,使得数以百万计的帧能够在引擎内进行符号化,而每个调试模块**只需解析一次** — 无需外部符号化服务,无需逐行重新解析,也无需将崩溃数据发送至 SaaS。
一个用于 DuckDB 的 [VGI](https://query.farm) worker。这是崩溃/性能分析浪潮的 **解析后端**:[`vgi-minidump`](https://query.farm)、
[`vgi-pprof`](https://query.farm) 和 [`vgi-perf`](https://query.farm) 提取
`(build_id, address)` 帧并将其输入至此。
## 为什么缓存是核心产品
*一旦模块被解析*,解析一个地址是非常廉价的 — 在已排序的行表中进行二分查找并遍历内联树。解析模块则**不然**:一个发布版的 `libchrome.so.debug` 或游戏引擎的 PDB 是数百 MB 的 DWARF/PDB。
简单的 `addr2line` 逐行处理会为**每个**帧重新解析它;在整个性能分析集群中,这就是全部的成本所在。缓存将 `O(frames × parse)` 转化为
`O(modules × parse + frames × lookup)`。**这正是该 worker 的核心价值。**
缓存以格式规范化的 **debug-id** 作为键(ELF GNU build-id →
GUID+age,Mach-O `LC_UUID`,PDB GUID+age),因此一列混合平台的帧可以通过一个缓存进行解析。它具有大小限制(按字节数和模块数的 LRU),对被证实缺失的 build-id 设有 **负缓存**,并带有一个支持序列化的 **清单**(源索引 + 来源信息 + 负缓存),该清单能够跨批次边界存续并驱动冷启动。
## 快速开始
```
INSTALL vgi FROM community;
LOAD vgi;
ATTACH 'symbols' AS symbols (TYPE vgi, LOCATION '/path/to/symbols-worker');
-- Point the worker at where debug files live (LOCAL by default; zero egress).
CALL symbols.add_source('dir', path => '/srv/debug');
CALL symbols.add_source('glob', path => '/builds/**/*.{debug,pdb,dSYM}');
-- Resolve a single (build_id, address) frame to function/file/line + inline chain.
SELECT symbols.main.symbolicate('76ff8518da153e64a8403892fcbf11250', 4294968116) AS frame;
-- → {function: 'apply', file: '…/prog.c', line: 16,
-- inlined: [{function:'inner_lo', …}, {function:'inner_hi', …}], status: 'ok', …}
```
## SQL 接口
```
-- 1. Resolve one frame to a STRUCT with the inline chain collapsed (scalar).
SELECT symbols.main.symbolicate(build_id, address) FROM crash_top_frames;
-- 2. Just a label (the fast path for a flamegraph leaf / GROUP BY key).
SELECT symbols.main.function_name(top.build_id, top.address) AS crash_site, count(*) AS hits
FROM crash_top_frames top
GROUP BY crash_site ORDER BY hits DESC; -- "which function are we crashing in"
-- 3. THE WORKLOAD: bulk-symbolicate a frame table, fanning each address out to
-- its inline chain via the scalar struct + UNNEST (innermost-first).
SELECT f.thread_id, f.frame_idx, u.depth, u.fr.function, u.fr.file, u.fr.line
FROM stack_frames f,
UNNEST(list_transform(
symbols.main.symbolicate(f.build_id, f.address).inlined,
(x, i) -> {'depth': i, 'fr': x})) AS t(u)
ORDER BY f.thread_id, f.frame_idx, u.depth;
-- 4. Just the inline chain at an address.
SELECT symbols.main.inline_frames(build_id, address) FROM frames;
-- 5. Demangle a raw symbol (Itanium C++ / Rust legacy+v0 / MSVC / Swift).
SELECT symbols.main.demangle('_ZN3foo3barEv'); -- → 'foo::bar'
-- 6. Inspect a module before resolving: is it usable, and does its debug-id match?
SELECT * FROM symbols.main.module_info('/srv/debug/libssl.so.debug');
-- → format MachO/ELF/PE/PDB, build_id, debug_id, arch, has_dwarf, has_line_table, symbol_count
-- 7. Cache observability (the stateful part) — what is parsed and resident now.
SELECT debug_id, name, format, bytes_resident, rows_resolved, last_used
FROM symbols.main.cache_status() ORDER BY bytes_resident DESC;
-- 8. Audit where symbols come from (and whether any source egresses).
SELECT * FROM symbols.main.sources();
```
### 函数
| 领域 | 签名 | 类型 |
| --- | --- | --- |
| 解析单个帧 | `symbolicate(build_id VARCHAR, address UBIGINT) → STRUCT(function, file, line, inlined LIST
, module, debug_id, status)` | 标量 |
| 仅函数名(快速路径) | `function_name(build_id, address) → VARCHAR` | 标量 |
| 仅内联链 | `inline_frames(build_id, address) → LIST` | 标量 |
| 名称还原 (Demangle) | `demangle(mangled VARCHAR [, lang]) → VARCHAR` | 标量 |
| 解析列(逐行) | `resolve(build_id, address) → TABLE(...)` | 表输入输出 † |
| 一次性解析列表 | `resolve_batch(LIST) → TABLE(frame_idx, ...)` | 表输入输出 † |
| 检查调试文件 | `module_info(blob BLOB)` / `module_info(path VARCHAR) → TABLE(...)` | 表 |
| 符号源 | `add_source(kind [, path=>, url=>, bucket=>, enabled=>, secret=>])`, `sources()`, `drop_source(id)` | 表 |
| 缓存 | `cache_status() → TABLE(...)`, `cache_evict(debug_id := NULL) → BIGINT` | 表 |
解析后的行结构(`resolve` / `resolve_batch`)输出 **每(输入帧 × 内联层级)一行**,按照从最内层到最外层的顺序排列,物理帧位于最后:
`build_id, address, inline_depth, is_inline, function, function_raw, file, line,
column, module, debug_id, status`。没有匹配模块的帧会产生 **一** 行,
`status='not_found'`,所有符号列均为 NULL — 解析过程 **绝不会丢失帧,也绝不会导致扫描报错**。
### 地址模型(调用者契约)
`address` 是 **模块相对虚拟地址**(位于模块映像内,在 ASLR 偏移 / 加载基址之前)。调用者 — 或者是拥有模块映射的上游
`vgi-minidump`/`vgi-pprof`/`vgi-perf` 帧提取器 —
**首先减去模块基址**。`module_info` 会报告映像的首选基址以供参考。
### 符号源(本地优先,远程可选开启)
| 来源 | 配置 | 出站流量 | 说明 |
| --- | --- | --- | --- |
| `dir` | `path => '/srv/debug'` | 无 | 首次使用时按 debug-id 索引 |
| `glob` | `path => '/builds/**/*.{debug,pdb,dSYM}'` | 无 | 递归;支持 `{a,b}` 花括号交替匹配 |
| `debuginfod` / `s3` / `http` | `url=>` / `bucket=>`, `enabled => true`, `secret =>` | **网络** | 可选开启,**默认关闭**;通过 SDK 密钥提供程序传递凭据 |
默认姿态是 **气隙隔离安全** 的:在未启用任何远程源的情况下,
网络出站流量为零。(远程 *获取* 已注册并受出站流量限制,但在当前版本中不予执行 — 设计上本地优先;参见 [更新日志](CHANGELOG.md)。)
## 安全加固(不受信任的调试文件)
调试文件会受到攻击者的影响(崩溃语料库 / 恶意软件样本自带的符号)。格式错误的 ELF/PDB 会产生清晰的逐行错误,**绝不会**导致崩溃、
挂起或 OOM:
- **逐行故障隔离** — 格式错误的模块会为需要它的帧产生 `status='error:'`
(`kind ∈ {truncated, bad-magic, bad-build-id,
corrupt-line-program, corrupt-inline-tree, unsupported-format, nesting-limit,
alloc-cap}`);所有其他模块和帧均不受影响。
- **受限的分配与递归** — 对解析大小、内联树深度和
单元数量设有上限;恶意的二进制数据块无法引发 OOM 或栈溢出。
- **`catch_unwind` 遏制** — 底层的 DWARF/PDB 库在对抗性输入下可能会 `panic`
(例如算术溢出);该 panic 会被捕获并
转换为清晰的错误行。
- **`cargo-fuzz` 零 panic 门禁** + 针对 ELF/Mach-O/
PE/PDB 和 build-id 路径的 proptest 无 panic 测试套件 (CI)。
- **负缓存** 可防止放大效应 — 缺失的 build-id 仅会被记录一次
并短路处理。
## 构建与测试
```
cargo test --workspace # unit + golden DWARF + cache + proptest no-panic
cargo clippy --all-targets -- -D warnings
cargo build --release # → target/release/symbols-worker
./run_tests.sh # haybarn SQLLogic e2e
```
端到端测试套件需要 haybarn 工具(一次性):
```
uv tool install haybarn-unittest
echo "INSTALL vgi FROM community;" | uvx haybarn-cli
```
请参阅 [`CLAUDE.md`](CLAUDE.md) 了解架构、缓存内部原理以及如何
重新生成 Mach-O dSYM 黄金测试用例。
## 许可证
Query Farm 源码可用 — 参见 [`LICENSE`](LICENSE)。版权所有 2026 Query Farm
LLC — https://query.farm