用显式栈遍历 BPF 控制流
Linux BPF 验证器的 check_cfg 使用显式指令栈执行非递归 DFS,检查跳转目标、可达性及控制流结构。
相关概念
工程问题#
待验证程序的跳转会形成有向图;遍历必须区分尚未探索和已完成的指令,同时避免深调用链消耗内核 C 调用栈。
实际实现#
insn_stack 数组保存当前探索路径,cur_stack 指向栈顶之后。push_insn 将未发现目标压栈;当前指令的边探索完成后,标记 EXPLORED 并弹栈。
处理流程#
- 分配指令状态数组与显式栈。
- 将入口指令标为已发现并压栈。
- 访问栈顶指令,继续探索未访问后继。
- 后继处理完毕后弹栈,并检查未遍历指令。
源码片段#
固定版本 · 原文摘录kernel/bpf/verifier.cv6.12 · L16515–L16534
16515walk_cfg:16516 while (env->cfg.cur_stack > 0) {16517 int t = insn_stack[env->cfg.cur_stack - 1];1651816519 ret = visit_insn(t, env);16520 switch (ret) {16521 case DONE_EXPLORING:16522 insn_state[t] = EXPLORED;16523 env->cfg.cur_stack--;16524 break;16525 case KEEP_EXPLORING:16526 break;16527 default:16528 if (ret > 0) {16529 verbose(env, "visit_insn internal bug\n");16530 ret = -EFAULT;16531 }16532 goto err_free;16533 }16534 }check_cfg
用 cur_stack - 1 读取栈顶;visit_insn 若发现新分支则继续深入,返回 DONE_EXPLORING 时标记完成并递减栈指针。这是源码明确声明的非递归深度优先遍历,错误跳转也在该遍历中向上传递。
连续片段展示取栈顶与完成后的弹栈;压栈发生在 visit_insn 调用链中的 push_insn。片段外还有异常回调入口及不可达指令检查。
收益与取舍#
工程分析带来的收益
- 显式管理遍历深度,不依赖递归调用栈。
- 访问状态用于区分树边、回边和已完成分支。
付出的代价
- 按指令数量分配辅助数组,额外占用内存。
- 结构检查不能独立证明内存访问安全或循环终止。
可比较的选择
递归 DFS写法更接近图遍历定义,但深路径会消耗内核调用栈。
版本与适用范围#
这里是验证器工作栈,不是 BPF 程序的运行时栈。v6.12 的回边处理受 bpf_capable 等规则影响,不能描述为全面禁止循环。
参考来源#
源码、官方文档与原始论文相关实现与比较#
4 个相关案例HNSW 从稀疏上层快速定位区域,再在底层扩大候选搜索。
附源码片段 · 检索与计算
借助计算依赖图与使用者计数,清理既不影响输出也无副作用的节点。
附源码片段 · 图处理与编译
nginx 解析 upstream 块时,用 ngx_array_t 连续保存服务器条目;每遇到一条 server 指令便追加一个元素。
附源码片段 · 通信与流量
Linux IIO 的 kfifo 缓冲在采样进入内核与用户态读取之间暂存数据,按先进先出顺序交付采样记录。
附源码片段 · 内存管理