传统 strace 输出难以阅读与关联分析的挑战
在 Linux 上调试复杂程序时,strace 能够捕获系统调用,但其文本流式输出往往缺乏结构化信息,导致以下痛点:① 多进程/线程的调用交叉难以辨认;② 文件描述符生命周期与网络套接字未被区分;③ 内存操作(brk、mmap)与实际页错误混杂,难以聚合统计;④ 手工过滤噪声(如库内部调用)需要额外脚本。上述限制使得工程师在分析 I/O 性能、资源泄漏或安全审计时,需要耗费大量时间进行后期整理。
Compendium 架构与核心实现细节
项目模块化布局
Compendium 采用 Rust 语言实现,代码库按功能划分如下:
src/
├── main.rs ─ CLI 与 Tracer 主循环
├── types.rs ─ FdTable、ProcessState、MemoryStats 等共享结构
├── events.rs ─ TraceEvent 与 EventKind 定义
├── memory.rs ─ 读取被追踪进程的内存内容
├── perf.rs ─ 通过 perf_event_open 捕获页错误
├── ptrace_ops.rs ─ Ptrace 生命周期管理
├── summary.rs ─ 运行结束后统计与报告生成
├── syscalls.rs ─ x86_64 系统调用号 ↔ 名称映射(360+ 条目)
├── handlers/
│ ├── fd.rs ─ 处理 open、socket、pipe、dup、close 等 FD 系统调用
│ ├── io.rs ─ 处理 read、write、send、recv、sendfile 等 I/O 调用
│ ├── mem.rs ─ 处理 brk、mmap、munmap 等内存调用
│ ├── page_faults.rs ─ 页错误分组与统计
│ └── utils.rs ─ 路径过滤逻辑
└── report/
├── mod.rs ─ HTML 报告生成(内嵌 CSS/JS)
├── report.js ─ 交互式时间线与事件表
└── report.css ─ 样式表

Tracer 核心状态
Tracer 在 main.rs 中维护全局运行时信息:
struct Tracer {
config: Config, // 命令行选项:verbose、report_path 等
processes: HashMap<Pid, ProcessState>, // 每个被追踪进程的状态
memory: HashMap<Pid, MemoryStats>, // 进程级别的内存统计
io: IoStats, // 累计 I/O 计数
summary: Summary, // 最终摘要信息
initial_pid: Option<Pid>, // 启动或附加的根进程
start_time: Instant, // 追踪起始时间点
perf: PerfState, // perf_event_open 启用状态及相关参数
output_file: Option<File>, // 可选的文本日志文件
events: Vec<TraceEvent>, // 按时间顺序记录的所有事件
event_count: usize, // 已记录事件数量
total_heap_bytes: u64, // 堆内存累计大小
interrupt_count: u8, // 处理信号中断次数
}
processes 通过 Pid 键映射到 **ProcessState**,该结构记录文件描述符表、线程列表以及当前系统调用栈; memory 与 perf 结合,实现 页面错误(perf_event_open)的实时捕获; events 按 **TraceEvent**(包括 EventKind::SyscallEnter、EventKind::SyscallExit)顺序保存,后续用于生成交互式 HTML。
Ptrace 捕获系统调用
Compendium 通过 ptrace 实现系统调用的入口/退出拦截。核心流程:
- 使用
ptrace(PTRACE_SEIZE, pid, ...) 启动或附加目标进程。 - 在
ptrace_ops.rs 中注册 SIGTRAP 回调,并在每次系统调用进入或返回时读取寄存器状态 (regs.rax 为系统调用号,regs.rdi、regs.rsi 等为参数)。 - 将获取的原始信息映射到
syscalls.rs 中的名称表,生成 TraceEvent 并推入 Tracer.events。
文件描述符生命周期追踪
handlers/fd.rs 负责 open、socket、pipe、dup、close 等调用的处理。关键实现点:
- 打开 时解析路径并将 **
FdEntry**(包括 fd, type(File/Socket/Pipe),以及 open_flags)写入 **FdTable**。 - 关闭 时从表中删除对应条目,并在
summary 中累计关闭计数。 - 网络套接字 通过
socket 系统调用的返回值关联到 IP/Port,后续 I/O 统计(send、recv)能够区分文件 I/O 与网络流量。
内存操作与页面错误
handlers/mem.rs 捕获 **brk、mmap、munmap**,并更新 **MemoryStats**(包括 heap_bytes、mmap_regions、total_bytes)。
perf.rs 通过 perf_event_open 创建对 PAGE_FAULTS 的监视,配合 handlers/page_faults.rs 将同一进程的连续缺页事件聚合,生成如下统计:
Memory:
Heap: 132.0 KB
Mmap: 8.5 MB (24 regions)
Total: 8.6 MB
事件循环与实时渲染
main.rs 中的 poll 循环使用 **mio**(或标准 select/poll)监听以下文件描述符:
- 被追踪进程的 ptrace 事件(通过
waitpid); perf 产生的 page‑fault 事件;- 用户通过
--report 选项开启的 HTML 报告写入(异步刷新)。
循环每次迭代:
loop {
let events = poll.poll(&mut events, timeout)?;
for event in events.iter() {
match event.token() {
Token::Ptrace => handle_ptrace(&mut tracer)?,
Token::Perf => handle_perf(&mut tracer)?,
Token::Signal => handle_signal(&mut tracer)?,
}
}
if tracer.interrupt_count > 0 { break; }
}
此设计保证 单线程 实时响应,同时避免了多线程同步开销。
交互式 HTML 报告
report/mod.rs 将 Tracer.events 序列化为 JSON,嵌入至 HTML 页面。report.js 提供时间轴缩放、事件过滤(基于路径白名单或系统调用类型)以及 I/O、内存、网络 三大统计的可视化面板。

性能收益与实现复杂度的权衡
Compendium 在 ptrace + perf_event_open 的组合下,能够在毫秒级捕获系统调用与缺页事件,提供比传统 strace 更结构化的统计信息。相对代价是:
- 实现复杂度:需要维护多进程/线程的状态同步、FD 表一致性以及 perf 事件的内核交互,代码基线约 3k 行 Rust。
- 资源占用:在高并发追踪场景下,
Tracer.events 会占用数十 MB 内存(取决于 max_report_events 参数)。 - 兼容性:仅限 x86_64 Linux(基于
syscalls.rs 中的 360+ 条目),对其他架构需重新生成映射表。
因此,在需要实时、可视化审计且对资源消耗有容忍度的场景(如 CI 测试、性能基线评估)中,Compendium 的优势明显;而在极端资源受限的嵌入式或跨平台调试时,仍可能选择轻量的 strace。
落地建议与核心价值概括
- 将
--report 与 --faults 结合使用,可在 CI 中自动生成可比对的 HTML 报告; - 对长期运行的服务,可通过
--pid 附加方式,仅监控关键子进程以控制内存占用; - 通过
types.rs 中的 FdTable 与 MemoryStats,可直接在业务代码中集成资源泄漏检测。
核心价值:提供结构化、实时的系统调用与内存/网络行为视图,显著降低调试与性能分析的认知成本。