本文整理自”Linux内核之旅开源社区“直播教学视频《BPF C编程入门》,主讲人为Linux内核之旅社区研三贺东升,喜欢看视频的小伙伴可以通过文章末尾的视频链接观看。
bcc是BPF高度封装的框架,我们使用的时候可以直接运行脚本,这对于使用上来说是非常友好的,但对于我们理解背后的原理来说是不清晰的,因为bcc的高度封装性,把背后的一些编译的过程都给屏蔽掉了,不利于我们去理解整个BPF机制的工作原理。使用c编程的完整的过程,更贴近于eBPF的机制流程。这是我们今天选择BPF C作为编程入门介绍的原因。
如图所示,左边是BPF工具,使用BPF工具需要我们自己来编写BPF程序,BPF程序经过Clang和LLVM会编译生成一段BPF字节码,通过用户空间的加载器将BPF字节码,通过系统调用的方式加载进内核。
进入到内核的流程:首先验证器verifier进行一些循环的检查,还有一些安全的验证,验证通过以后就可以交由BPF虚拟机执行。eBPF机制有许多的hook点,而这些hook点是事件触发类型。在函数上挂好这些钩子以后,当函数被调用的时候,就会触发这些点,进而执行我们的BPF程序。
如果我们想把数据存在一个地方的话,可以通过BPF的map。在BPF程序中创建map,把我们想获取的内核数据保存在map中。在用户态调用一些接口来读或写map,把保存的数据取出来。
简单来说我们可以通过在用户空间写BPF程序,拿取内核中的一些数据。例如内核中某个内核函数的调用次数或者某个内核函数的执行时间。在用户态编写好BPF程序以后把它加载到内核,就可以拿到我们想要的内核数据。
下载的内核源码与Ubuntu 18.04的内核版本一致。首先查看当前内核版本:uname -r。
看到在上一次的内容中我们提到了一个概念叫做BPF程序的节。SEC宏会把名字为括号中的字符串编译到elf的目标文件中。如图:
节本身是elf文件中的概念。SEC宏中第一个为程序类型,第二个是我们要跟踪的函数。对于SEC宏来说的话他会把整个kprobe/sys_write当成节的名字,编译到elf的目标文件中。
可以看到kern.o它是目标文件,格式为elf64,采用小端来存储。这里面就显示了我们节的一些信息。比如说我们用SEC定义的节名字叫做kprobe/sys_bpf,还有个licence许可证的节,说明我们用SEC宏把我们后面定义的一些名字编译到elf目标文件中的某个节中。现在我们应该理解了SEC宏的作用。
右边图中橙色框圈起来的就是BPF字节码,是左面灰色部分圈起来的代码经过clang和llvm来生成的BPF的字节码。这段字节码可以被虚拟机解码执行。
可以看到它的里面有文件格式是ELF64-BPF,第二行是我们用SEC宏定义的节的名字kprobe/sys_bpf,bpf_prog是我们定义的BPF程序的名字。
重点关注第10行,为汇编表示在汇编里面调用函数函数它是地址,但是我们可以看到6它并不是实际的地址,而是整形数,左面对应是汇编的BPF字节码。
我们首先来从两个头文件入手,也是我们最开始写BPF程序的时候两个必须包含的头文件。其中在bpf_helpers.h里面可以看到图示一段定义,函数指针的变量明正是我们在BPF程序里面调用的内核辅助函数的名字。在文件中是以函数指针的形式来定义的,我们可以看函数指针它是用以强制类型转换赋给了指针变量,我们在BPF程序中实际调用的并不是真正的内核辅助的函数接口,而是函数指针。
我们前面那两个宏展开以后得到的枚举类型。说func谁对应的枚举类型里面的某个整型值,替换一下后,最下面的一句就更容易我们理解。
接下来因为我们在bpf程序中调用的是trace_printk函数,我们还是以trace_printk来进行举例。
其中,BPF_CALL为真正的BPF调用指令,dst_reg为目的寄存器,sec_reg为源寄存器,off为偏移量,imm为立即数。
按位与后得到code的值为85,目的寄存器、源寄存器和off均被初始化为0,imm为枚举结构中的整型值,即为6。
bpf_insn表示的BPF指令级的格式,我们把里面的成员按照字节序的方式来组装一下就可以得到我们对应BPF字节码。我们进行组装的时候一定要按照BPF指令集定义的形式来组装。
我们写的BPF程序首先通过clang它是llvm的前端,它的作用来最后会生成ll IR文件,传给llvm的后端。llvm的后端可以支持BPF类型,会生成BPF类型的字节码。生成BPF字节码以后,通过用户态的加载器通过系统调用进入到内核。
先说一下llvm是什么?我们说最后要用llvm来编译但是并不是说l lv m它是编译器它本身并不是编译器它只是可以说它是提供了一些开发编译器还有些解释器些语言工具的一些库。同时ll v m我们可以理解成样那种框架框架里面它其实还包含那些工具链的组件比如说l lc也我们llvm的后端,或者说一些其他的工具。
IR文件 ,IR是llvm的中间表示它也是一种语言,类似于底层那种汇编语言。生成IR以后会把IR文件交给llvm的后端,在后端的时候生成对应平台的机器码。

