bpftool prog load my_prog.o /sys/fs/bpf/my_prog
返回
Error: program rejected by verifier。verifier
日志从某一行开始输出,但你不知道它是在哪一步检查失败的——是在控制流图构建时就死了,还是在逐条模拟执行指令时发现
R1 的类型不对?不知道 verifier 的执行顺序和阶段划分,读
verifier 日志就像摸黑走路。
源码基准:本系列以 Linux 6.6 / 6.8 LTS 为主线;本篇引用的路径与行号以 Linux 6.6 为准。
本文跟踪 bpf(BPF_PROG_LOAD)
系统调用从用户态到 verifier 主循环的完整内核路径。建立
bpf_verifier_env 这个核心状态的字段理解,拆解
check_cfg()
如何构建控制流图并检测不可达代码和死循环,最后进入
do_check() 的主指令模拟循环。读完这篇,你知道
verifier
分为几个阶段,每个阶段做什么,以及在哪个阶段你的程序更可能被拒绝。
一、系统调用入口:从用户态到内核
用户态调用 bpf() 系统调用,内核通过
CONFIG_BPF_SYSCALL 编译的支持代码分派:
/* Linux 6.6: kernel/bpf/syscall.c:5006 */
SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, size)
{
return __sys_bpf(cmd, USER_BPFPTR(uattr), size);
}
/* Linux 6.6: kernel/bpf/syscall.c:5013 */
static int __sys_bpf(int cmd, bpfptr_t uattr, unsigned int size)
{
...
switch (cmd) {
case BPF_PROG_LOAD:
err = bpf_prog_load(&attr, uattr, size);
break;
case BPF_MAP_CREATE:
...
}
}当 cmd == BPF_PROG_LOAD 时,控制流进入
bpf_prog_load(),这是 BPF
程序加载的核心入口。
1.1
bpf_prog_load() 的职责
bpf_prog_load()(kernel/bpf/syscall.c)执行以下步骤(简化):
/* Linux 6.6: kernel/bpf/syscall.c */
static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
{
struct bpf_prog *prog;
int ret;
...
/* 1. 将 BPF 指令从用户态复制到内核缓冲区 */
prog = bpf_prog_alloc(bpf_prog_size(attr->insn_cnt), GFP_USER);
if (copy_from_bpfptr(prog->insns,
make_bpfptr(attr->insns, uattr.is_kernel),
bpf_prog_insn_size(prog)))
goto free_prog;
prog->len = attr->insn_cnt;
/* 2. 分配辅助信息(每个指令的 verifier 元数据 + func_info) */
ret = bpf_prog_alloc_aux(prog, attr);
/* 3. 分配和复制程序名 */
...
/* 4. 调用 verifier —— 核心安全检查 */
ret = bpf_check(&prog, attr);
/* 5. 验证通过后,分配 prog_array 引用 ID */
...
/* 6. JIT 编译(如果启用) */
...
/* 7. 分配文件描述符,返回给用户态 */
...
}第 4 步的 bpf_check() 就是我们关注的
verifier 入口。整个调用链为:
bpf(BPF_PROG_LOAD)
└── __sys_bpf(BPF_PROG_LOAD)
└── bpf_prog_load(attr)
├── bpf_prog_alloc() // 分配 bpf_prog + 复制指令
├── bpf_prog_alloc_aux() // 分配辅助数据
└── bpf_check(&prog, attr) // ★ 验证器入口
├── check_cfg() // 阶段 1:控制流图构建
├── check_subprogs() // 阶段 2:子程序边界分析
├── do_check_main() // 阶段 3:主验证循环
│ └── do_check() // 逐条指令模拟执行
└── check_max_stack_depth() // 阶段 4:栈深度检查
(固定栈大小 512)1.2
bpf_verifier_env:验证器的全局状态
bpf_verifier_env
是整个验证过程的上下文结构体,定义了 verifier
需要的所有状态(kernel/bpf/verifier.c):
/* Linux 6.6: kernel/bpf/verifier.c */
struct bpf_verifier_env {
struct bpf_prog *prog; /* 正在验证的程序 */
struct bpf_verifier_stack_elem *head;/* 验证 DFS 栈顶 */
int stack_size; /* 当前栈深度 */
struct bpf_verifier_state cur_state;/* 当前分析点的寄存器/栈状态 */
struct bpf_verifier_state_list **explored_states;
/* 已探索状态哈希表(用于 pruning) */
struct bpf_insn_aux_data *insn_aux_data;
/* 每条指令的辅助数据 */
u32 pass_cnt; /* 验证的 pass 计数 */
u32 subprog_cnt; /* 子程序数量 */
/* 日志控制 */
u32 log_level;
char *log_buf;
u32 log_size;
u32 log_len;
/* 限制 */
u32 insn_processed; /* 已处理指令数 */
u32 prev_insn_processed;
/* BTF 相关信息 */
struct btf *btf;
struct btf *btf_vmlinux;
...
};关键字段解释:
cur_state:当前正在分析的寄存器/栈状态。verifier 在每条指令执行后更新这个状态。当遇到条件跳转时,会 fork 出新的状态(分支验证)。explored_states:哈希表,存储已探索的程序点的状态。verifier 在遇到已经分析过的状态时执行 pruning(裁剪),避免重复验证相同的路径。这是第 03 篇的核心主题。insn_aux_data:为每条 BPF 指令分配的辅助元数据数组。包含该指令的引用计数、循环深度标记、是否需要精确跟踪等信息。pass_cnt:验证的”轮次”计数。verifier 至少执行一次完整验证;如果遇到需要 backtrack 的情况(精度跟踪),可能执行多轮。insn_processed:已模拟执行的指令总数。当超过BPF_COMPLEXITY_LIMIT_INSNS(1,000,000)时,verifier 拒绝程序。
二、bpf_check():验证总控
bpf_check() 是 verifier 的顶层协调函数:
/* Linux 6.6: kernel/bpf/verifier.c - bpf_check() 简化 */
int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, ...)
{
struct bpf_verifier_env *env;
int ret;
/* 1. 分配和初始化 bpf_verifier_env */
env = kzalloc(sizeof(struct bpf_verifier_env), GFP_KERNEL);
/* 2. 设置日志级别 */
env->log_level = attr->log_level;
/* 3. 初始化每条指令的辅助数据 */
env->insn_aux_data = kvcalloc(prog->len, sizeof(*env->insn_aux_data), ...);
/* 4. 阶段 1:构建控制流图 + 检测不可达代码 */
ret = check_cfg(env);
if (ret < 0)
goto err_release;
/* 5. 阶段 2:分析子程序边界(如果程序包含 BPF_PSEUDO_CALL) */
ret = check_subprogs(env);
if (ret < 0)
goto err_release;
/* 6. 阶段 3:主验证循环 —— 逐条模拟执行所有指令 */
ret = do_check_main(env);
if (ret < 0)
goto err_release;
/* 7. 阶段 4:检查最大栈深度(确保在 512 字节内) */
ret = check_max_stack_depth(env);
if (ret < 0)
goto err_release;
/* 8. 优化:移除不需要精确跟踪的指令标记 */
...
}三、check_cfg():控制流图构建
3.1 控制流图要解决的问题
check_cfg()(kernel/bpf/verifier.c)构建程序的控制流图(CFG),并回答以下问题:
- 是否有不可达指令(DAG 中的孤立节点)?
- 是否所有路径都到达
BPF_EXIT(无死循环/无限循环)? - 是否有回边(back-edge),即循环的存在?如果有,是否可接受?
- 每个基本块的后继和前驱是否正确?
控制流图是后续 do_check()
逐条模拟执行的前提——没有有效的
CFG,就没有安全的路径遍历。
3.2 DFS 遍历算法
check_cfg()
使用深度优先搜索(DFS)遍历指令的跳转图:
/* Linux 6.6: kernel/bpf/verifier.c - check_cfg() 核心逻辑简化 */
static int check_cfg(struct bpf_verifier_env *env)
{
struct bpf_insn *insns = env->prog->insnsi;
int insn_cnt = env->prog->len;
int *insn_state; /* 每条指令的遍历状态 */
int *insn_stack; /* DFS 栈 */
int i, w = 0, peek_insn = 0;
int t;
insn_state = kvcalloc(insn_cnt, sizeof(int), GFP_KERNEL);
insn_stack = kvcalloc(insn_cnt, sizeof(int), GFP_KERNEL);
/* 状态值 */
/* 0 = NOT_VISITED, 1 = EXPLORING, 2 = DONE */
/* 将入口基本块推入栈 */
insn_state[0] = EXPLORING;
insn_stack[0] = 0;
w = 1; /* 栈指针 */
while (w > 0) {
t = insn_stack[w - 1]; /* peek 栈顶指令索引 */
...核心逻辑从指令 0(入口)开始,追踪所有跳转边:
对于每条指令 t:
- 如果 t 是 BPF_JA (无条件跳转):边 t → t + off + 1
- 如果 t 是条件跳转 (BPF_JEQ 等):
两条边:t → t + 1 (fallthrough) 和 t → t + off + 1 (branch)
- 如果 t 是 BPF_EXIT 或 caller 的末尾:叶子节点
- 否则:单条边 t → t + 1 (顺序执行)算法检测以下错误:
- 不可达指令:DFS
遍历结束后,
insn_state[i] == NOT_VISITED的指令不可达,报"unreachable insn %d"。 - 无限循环:回边检测——如果跳转目标指向已标记为
EXPLORING的指令(即当前 DFS 栈中的一条指令),这构成循环。verifier 记录循环深度,但允许有界循环。 - 多个 EXIT:程序可以有多个
BPF_EXIT(例如在不同分支中的提前返回),但 verifier 检查是否有路径没有到达 EXIT。
3.3
insn_aux_data 的元数据填充
check_cfg() 在遍历过程中为每条指令填充
insn_aux_data:
/* kernel/bpf/verifier.c - insn_aux_data 结构体(简化) */
struct bpf_insn_aux_data {
union {
struct bpf_loop_info loop_info; /* 循环信息 */
};
u64 seen; /* 指令被访问的次数 */
u32 loop_depth; /* 当前循环嵌套深度 */
bool goto_visited; /* 跳转目标是否已访问 */
...
};循环深度跟踪对于 verifier 的性能至关重要——深度嵌套的循环导致状态爆炸,verifier 会在复杂度过高时拒绝程序。
四、do_check_main()
和 do_check():主验证循环
4.1 入口设置
do_check_main() 为验证做最后的准备:
/* Linux 6.6: kernel/bpf/verifier.c - do_check_main() 简化 */
static int do_check_main(struct bpf_verifier_env *env)
{
struct bpf_verifier_state *state;
/* 创建入口状态 */
state = kzalloc(sizeof(*state), GFP_KERNEL);
/* 初始化 R1 = ctx (PTR_TO_CTX) */
state->regs[BPF_REG_1].type = PTR_TO_CTX;
state->regs[BPF_REG_1].off = 0;
/* 初始化 R10 = frame pointer */
state->regs[BPF_REG_10].type = PTR_TO_STACK;
state->regs[BPF_REG_10].frameno = 0;
/* 其他寄存器标记为 NOT_INIT */
for (i = 0; i < MAX_BPF_REG; i++) {
if (i != BPF_REG_1 && i != BPF_REG_10)
mark_reg_not_init(env, state->regs, i);
}
/* 启动主循环——从基本块 0 开始 */
ret = do_check(env);
return ret;
}关键状态初始化:
- R1 标记为
PTR_TO_CTX:这是因为BPF_PROG_LOAD时,verifier 假设程序被调用时 R1 包含上下文指针。对于 XDP 程序,这是struct xdp_md *;对于 socket filter,这是struct __sk_buff *。 - R10 标记为
PTR_TO_STACK:帧指针,指向 512 字节栈顶。 - 所有其他寄存器标记为
NOT_INIT:在被赋值之前,任何对这些寄存器的读取都被拒绝(“R2 !read_ok”)。 - 每个栈槽初始化为
STACK_INVALID(在被写入前不能读取)。
4.2 主循环结构
do_check() 是 verifier
的核心引擎——它逐条模拟执行 BPF
指令,跟踪每条指令执行后的寄存器/栈状态:
/* Linux 6.6: kernel/bpf/verifier.c - do_check() 循环简化 */
static int do_check(struct bpf_verifier_env *env)
{
struct bpf_insn *insns = env->prog->insnsi;
struct bpf_reg_state *regs = state->regs;
int insn_cnt = env->prog->len;
env->prev_insn_processed = 0;
for (;;) {
struct bpf_insn *insn;
u32 class;
int err;
/* 1. 获取当前状态下的下一条指令 */
insn = &insns[env->insn_idx];
/* 2. 复杂度检查(每次处理前检查) */
if (env->insn_processed >= BPF_COMPLEXITY_LIMIT_INSNS) {
verbose(env, "BPF program is too large. "
"Processed %d insn\n", env->insn_processed);
return -E2BIG;
}
/* 3. 分类并分发到类型特定的检查器 */
class = BPF_CLASS(insn->code);
switch (class) {
case BPF_ALU:
case BPF_ALU64:
err = check_alu_op(env, insn);
break;
case BPF_LDX:
err = check_mem_access(env, insn_idx, insn->src_reg,
insn->off, BPF_SIZE(insn->code),
BPF_READ, insn->dst_reg, false, false);
break;
case BPF_STX:
err = check_mem_access(env, insn_idx, insn->dst_reg,
insn->off, BPF_SIZE(insn->code),
BPF_WRITE, insn->src_reg, false, false);
break;
case BPF_ST:
err = check_mem_access(env, insn_idx, insn->dst_reg,
insn->off, BPF_SIZE(insn->code),
BPF_WRITE, -1, false, false);
break;
case BPF_JMP:
case BPF_JMP32:
err = check_jmp_op(env, insn_idx, insn);
break;
case BPF_LD:
err = check_ld_imm(env, insn);
break;
}
/* 4. 错误检查 */
if (err)
return err;
/* 5. 更新指令计数器 */
env->insn_processed++;
}
}说明:以上为教学用简化伪代码。内核
do_check()的实际主循环通过pop_stack()取待验证状态、更新env->insn_idx,在 fallthrough 路径上顺序推进,遇到条件跳转时push_stack()保存分支;并非简单的for循环线性扫描全部指令。
4.3
分支处理:check_cond_jmp_op()
条件跳转是 verifier 最复杂的地方。当遇到条件跳转时,verifier 必须考虑两条路径(分支和 fallthrough):
/* kernel/bpf/verifier.c - check_cond_jmp_op() 简化 */
static int check_cond_jmp_op(struct bpf_verifier_env *env,
struct bpf_insn *insn, int insn_idx)
{
struct bpf_verifier_state *other_branch;
int err;
/* fork 当前状态——用于分支路径 */
other_branch = push_stack(env, insn_idx + insn->off + 1, insn_idx);
/* 在当前状态中,应用条件为 FALSE 时的范围约束(fallthrough) */
/* 例如:if r0 > 5 ... 则 fallthrough 时 r0 <= 5 */
find_good_pkt_pointers(other_branch, insn, insn->dst_reg);
reg_set_min_max(&other_branch->regs[insn->dst_reg], ...);
/* 在分支状态中,应用条件为 TRUE 时的范围约束 */
/* 例如:if r0 > 5 ... 则分支中 r0 > 5 */
reg_set_min_max_inv(&state->regs[insn->dst_reg], ...);
/* 继续在当前路径上执行(fallthrough) */
/* 分支路径中的状态会在之后被 pop 出来处理 */
return 0;
}push_stack()
创建一个新的验证状态,保存分支路径的寄存器/栈快照。当
verifier 完成了当前路径的验证(到达 EXIT
或遇到已探索状态),它通过 pop_stack()
回溯到之前的 fork 点,继续验证另一条路径。这套 DFS
状态探索逻辑内嵌在 do_check() +
pop_stack() 中(详见第 03 篇)。
4.4 helper 调用检查
check_helper_call() 是
do_check() 中最复杂的子函数之一:
/* kernel/bpf/verifier.c - check_helper_call() 简化 */
static int check_helper_call(struct bpf_verifier_env *env,
struct bpf_insn *insn, int insn_idx_p)
{
struct bpf_func_proto *fn;
int meta_size, i;
/* 1. 查找 helper 函数原型 */
fn = env->prog->aux->ops->get_func_proto(insn->imm, env->prog);
if (!fn) {
verbose(env, "unknown func %s#%d\n", func_id_name(insn->imm), insn->imm);
return -EINVAL;
}
/* 2. 验证每个参数的类型匹配 */
for (i = 0; i < MAX_BPF_FUNC_REG_ARGS; i++) { /* R1-R5 */
if (fn->arg_type[i] == ARG_DONTCARE)
continue;
err = check_func_arg(env, i, &meta, fn, insn_idx_p);
if (err)
return err;
}
/* 3. R1-R5 在调用后被标记为 NOT_INIT */
for (i = BPF_REG_1; i <= BPF_REG_5; i++)
mark_reg_not_init(env, regs, i);
/* 4. R0 根据 helper 的返回类型设置 */
if (fn->ret_type == RET_PTR_TO_MAP_VALUE_OR_NULL)
mark_reg_ptr_or_null(env, regs, BPF_REG_0, PTR_TO_MAP_VALUE);
else if (fn->ret_type == RET_INTEGER)
mark_reg_unknown(env, regs, BPF_REG_0);
return 0;
}helper 调用的验证步骤:
- 通过函数的
imm字段查找bpf_func_proto。 - 遍历
R1–R5,检查每个参数的类型是否匹配(
PTR_TO_CTX,PTR_TO_MAP_KEY,ARG_CONST_SIZE等)。类型不匹配时返回错误。 - 调用后,R1–R5 被标记为
NOT_INIT——调用者不能信任 helper 调用后这些寄存器的值。 - R0 被设置为 helper 的返回类型和范围。例如
bpf_map_lookup_elem返回PTR_TO_MAP_VALUE_OR_NULL,verifier 同时设置ref_obj_id用于后续的 NULL 检查强制要求。
五、verifier 日志级别
verifier 日志是排障的最重要工具。通过
log_level 控制详细程度:
| log_level | 输出内容 |
|---|---|
| 0 | 无输出(默认) |
| 1 | 基本日志:每个基本块的处理、错误信息 |
| 2 | 完整日志:每条指令执行后的寄存器状态、栈状态、分支探索、pruning 决策 |
获取日志的典型命令:
# 通过 bpftool 加载并获取完整日志
bpftool prog load my_prog.o /sys/fs/bpf/my_prog \
type xdp \
log_level 2 \
log_buf /dev/stdout
# 或通过 bpftool 查看已加载程序的 verifier 日志
bpftool prog show id <PROG_ID> --verbose日志解读示例:
0: (b7) r1 = 0
1: (7b) *(u64 *)(r10 - 8) = r1
2: (bf) r2 = r10
3: (07) r2 += -8
4: (b7) r3 = 0
5: (85) call bpf_map_lookup_elem#1
6: (15) if r0 == 0x0 goto pc+2
R0=map_value(id=0,off=0,ks=4,vs=8,imm=0) R2_w=ptr_to_stack(id=0,off=0)
R10=fp0
7: (61) r1 = *(u32 *)(r0 + 0)
8: (95) exit
from 6 to 9: R0=inv(id=0) R10=fp0
9: (b7) r0 = 0
10: (95) exit
注释: - 指令 6 之后:R0 被标记为
map_value(非 NULL 路径继续),R2 标记为
ptr_to_stack(带范围信息)。 -
from 6 to 9:分支路径——当 R0 == NULL
时,跳转到指令 9,R0 被标记为 inv(类型变为
scalar,值为 0)。
六、Mermaid:BPF_PROG_LOAD
完整调用链
flowchart TD
subgraph USER["用户态"]
BPFTOOL["bpftool prog load<br/>my_prog.o"]
LIBCALL["libbpf: bpf_object__load()"]
end
subgraph SYSCALL["bpf() 系统调用"]
SYSCALL_ENTRY["__sys_bpf(BPF_PROG_LOAD)"]
PROG_LOAD["bpf_prog_load()"]
end
subgraph VERIFY["verifier 框架 (kernel/bpf/verifier.c)"]
B_CHECK["bpf_check()<br/>verifier 总控"]
subgraph PHASE1["阶段 1: 控制流图"]
CFG["check_cfg()<br/>DFS 遍历指令<br/>构建 CFG<br/>检测不可达代码"]
end
subgraph PHASE2["阶段 2: 子程序"]
SUBPROG["check_subprogs()<br/>分析 BPF_PSEUDO_CALL<br/>标记子程序边界"]
end
subgraph PHASE3["阶段 3: 主验证"]
DO_MAIN["do_check_main()<br/>初始化入口状态<br/>R1=PTR_TO_CTX<br/>R10=PTR_TO_STACK"]
DO_CHECK["do_check()<br/>逐条指令模拟执行<br/>for each bpf_insn:"]
CHECK_OPS["check_alu_op()<br/>check_mem_access()<br/>check_helper_call()<br/>check_cond_jmp_op()<br/>check_ld_imm()"]
end
subgraph PHASE4["阶段 4: 收尾"]
STACK_DEPTH["check_max_stack_depth()<br/>验证栈深度 ≤ 512"]
end
end
subgraph POST["验证通过后"]
JIT["bpf_jit_compile()"]
ALLOC_FD["分配文件描述符"]
RETURN["返回 FD 给用户态"]
end
subgraph ERROR["验证失败"]
LOG["生成 verifier log<br/>返回 -EACCES / -EINVAL"]
end
BPFTOOL --> LIBCALL --> SYSCALL_ENTRY --> PROG_LOAD
PROG_LOAD --> B_CHECK
B_CHECK --> CFG
CFG -->|CFG 有效| SUBPROG
CFG -->|不可达/循环| LOG
SUBPROG --> DO_MAIN
DO_MAIN --> DO_CHECK
DO_CHECK --> CHECK_OPS
CHECK_OPS -->|指令序列结束| STACK_DEPTH
CHECK_OPS -->|检查失败| LOG
STACK_DEPTH -->|栈深度 OK| JIT
STACK_DEPTH -->|栈深度超限| LOG
JIT --> ALLOC_FD --> RETURN
七、小结
verifier 框架不是黑盒魔法,而是一个结构清晰的四阶段流水线:
check_cfg()构建指令的控制流图,确保所有指令可达且所有路径都返回。这是最快速的失败点——如果程序有不可达代码或无限循环,在此阶段就会被拒绝。check_subprogs()分析函数调用边界,标记子程序的入口和出口。do_check()是核心引擎——逐条模拟执行每条 BPF 指令,调用类型特定的检查器(ALU、内存访问、helper 调用、条件跳转)。对于每个条件跳转,fork 出分支状态并在两条路径上继续检查。这是最耗时也最容易失败的阶段。check_max_stack_depth()确保程序使用的栈空间不超过 512 字节。
bpf_verifier_env
伴随整个验证生命周期,cur_state
跟踪当前寄存器/栈状态,explored_states
记录已探索的程序点用于状态裁剪。这两者之间如何交互——也就是
verifier 如何通过抽象解释和状态等价判定来裁剪搜索空间——是第
03 篇的核心内容。
参考
内核源码(A 级,Linux 6.6)
kernel/bpf/verifier.c—bpf_check()、check_cfg()、do_check_main()、do_check()、push_stack()/pop_stack()kernel/bpf/syscall.c—bpf_prog_load()、__sys_bpf(BPF_PROG_LOAD)include/linux/bpf_verifier.h—struct bpf_verifier_env、struct bpf_verifier_state
下一篇:验证器核心算法:抽象解释、状态跟踪与路径裁剪(第 03 篇)
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【eBPF 内核实现深度拆解】Helper 函数子系统:注册、类型检查与参数传递
从 bpf_func_proto 结构体出发,讲解 helper 函数的注册机制(BPF_CALL_n 宏链)、参数类型编码(ARG_PTR_TO_MAP_KEY 等枚举)、返回值策略,以及 verifier 在 check_helper_call() 中对每个参数的类型与边界检查。
【eBPF 内核实现深度拆解】从验证器到 JIT,从 BTF 到调度器
eBPF 内核虚拟机内部实现系统讲解:BPF 指令集与寄存器机器、验证器的抽象解释与状态裁剪、JIT 编译器后端、Map 各类型的并发与内存模型、helper 函数注册与类型检查、BTF 格式规范与 CO-RE 重定位引擎、libbpf 加载器工程、fentry/fexit 蹦床机制、sched_ext 调度器内核接口。面向想读懂 eBPF 内核源码、写生产级 BPF 程序的系统工程师。
【eBPF 内核实现深度拆解】BPF 指令集解码:寄存器机器、调用约定与指令编码
从 eBPF 虚拟机的 11 个 64-bit 寄存器和 struct bpf_insn 出发,逐条拆解 ALU64/ALU32、跳转、加载存储、call 四类指令的字段语义与编码格式,建立后续 verifier 和 JIT 讨论的精确基础。
【eBPF 内核实现深度拆解】验证器核心算法:抽象解释、状态跟踪与路径裁剪
深入 verifier 的静态分析引擎——寄存器状态 reg_state 的类型/值域表示、栈状态 stack_state 的初始化标记、explore_state 的 DFS 搜索、states_equal 的等价判定、precision tracking——这是整个系列最难也最核心的一篇。