源码梳理
从bpf_check函数https://elixir.bootlin.com/linux/v6.14-rc5/source/kernel/bpf/verifier.c#L23039开始看起,这里我们会有一个全局的env:
然后经过若干检查之后会进入到do_check_subprogs函数,然后对于每一个subprog会执行一次do_check_common函数https://elixir.bootlin.com/linux/v6.14-rc5/source/kernel/bpf/verifier.c#L22162,这里会分配一个state结构体(相当于每一个subprog有一个):
关于state的几个成员进行解释:
curframe:当前栈帧
frame[]: 走到这个位置走过的所有调用栈,可以理解成backtrace;
frame[i]->regs:
first_insn_idx: 该subprog开始的位置;
last_insn_idx:
关于env此时我们需要关注:
cur_state:
subprog_info[]: 在check_subprogs函数中根据调用(call)划分子程序,这里边记录了所有子程序的开始与结束位置;
关于frame
curframe增加的地方在这里:https://elixir.bootlin.com/linux/v6.14-rc5/source/kernel/bpf/verifier.c#L9977
setup_func_entry函数:
可以看到,当发生调用的时候会增加curframe的值,并启用下一个frame结构来存放新的栈帧,所以基本可以确定frame就是用来记录函数调用栈的;就是发生call指令的时候才会走到这里,那么就相当于进入一个新的subprog了啊,也就是说每一个subprog会有一个state,但是并不意味着这个state不会走到别的subprog,这个state的frame就是作为一个栈,用来记录从该subprog开始走到的所有的subprog(函数调用);
函数调用时reg的关系
在setup_func_entry中调用了init_func_statehttps://elixir.bootlin.com/linux/v6.14-rc5/source/kernel/bpf/verifier.c#L2672:
重点关注其中的init_reg_state:
除了栈指针都标记为了未初始化的;并没有涉及参数的传递的问题啊;
在处理call指令的位置(check_func_call函数)发现setup_func_entry执行完之后会调用clear_caller_saved_regs函数:
该函数将r0 ~ r5标记为not_init,然后检查参数:
这里有一个赋值,像是参数的传递:
其实set_func_entry的第四个参数,会在初始化func_state之后调用:
附录
env
https://elixir.bootlin.com/linux/v6.14-rc5/source/include/linux/bpf_verifier.h#L701
state
https://elixir.bootlin.com/linux/v6.14-rc5/source/include/linux/bpf_verifier.h#L368