ebpf原理小结

Posted by 婷 on October 24, 2024 本文总阅读量

简介

大概讲下对ebpf的一些概念跟原理的理解,其实ebpf就是对bpf一些指令的拓展,下面讲bpf原理部分其实跟ebpf是差不多的。分析的内核代码版本为5.4.0

BPF程序如何运行

这部分就讲BPF程序怎么运行的,顺着这个过程,可以把各种原理理顺下

image-20241023234121668

BPF程序

比方说像sockex1_kern.chello_kern.c这种程序,都会有个SEC这样的标志,这种就是BPF程序代码

img

字节码

BPF程序->CLANG->LLVM得到BPF字节码。编译过程中也可以看到CLANG-BPF的字样

img

而编译得到的hello_kern.o文件存储了BPF字节码

img

img

我们进行反汇编操作,可以看到具体的字节码以及对应的指令

比如BPF字节码bf a1 00 00 00 00 00 00 对应的BPF指令就是r1 = r10

img

系统调用

我们分析load_bpf_file函数,里面揭示了如何将字节码送入内核的

load_bpf_file
    do_load_bpf_file
        bpf_load_program /*tools/lib/bpf/bpf.c*/
            bpf_load_program_xattr
                sys_bpf_prog_load
                    sys_bpf(BPF_PROG_LOAD, attr, size)
                        syscall(__NR_bpf,cmd,attr,size)

sys_bpf函数如下,走的系统调用bpf

img

从另一个角度我们可以用strace工具跟踪下,也可以看到系统调用bpf(BPF_PROG_LOAD,XXXX,....)

img

img

kernel/bpf/syscall.c中定义了bpf的系统调用

img

我们使用的是BPF_PROG_LOAD类型的,所以对应的内核函数是bpf_prog_load

img

该函数的内容呢简单列一下大概是

/*kernel/syscall/bpf.c*/
static int bpf_prog_load(union bpf_attr *attr, union bpf_attr __user *uattr)
{
    ...
    
    /*run eBPF verifier*/
    /*使用verifer对BPF程序进行合法扫描*/
    err = bpf_check(&prog, attr, uattr);
    
    /*分配一个fd与prog关联,最终这个fd会返回用户空间*/
    err = bpf_prog_new_fd(prog);
    if (err < 0)
         bpf_prog_put(prog);
    return err;
    ...
}

校验器verifier

前面分析系统调用的时候,提到内核的bpf_prog_load函数中,有个bpf_check,其实就是verifier。函数定义在kernel/bpf/verifier.c文件中。这里就不去分析啦,反正如果校验器安全校验不过,也就注入不了啦。

解释器

首先先理解一个概念,BPF虚拟机。其实本质上就是内核自己模拟了一个小型的PC

image-20241024013540046

其组成有

  • 11个64bit的寄存器,寄存器被命名为r0- r10。操作模式默认为 64 位。64位的寄存器也可作32 位子寄存器使用,它们只能通过特殊的 ALU(算术逻辑单元)操作访问,使用低32位,高32位使用零填充。
寄存器 使用
r0 包含 BPF 程序退出值的寄存器。退出值的语义由程序类型定义。此外,当将执行交还给内核时,退出值作为 32 位值传递。
r1-r5 保存从 BPF 程序到内核辅助函数的参数。其中 r1 寄存器指向程序的上下文(例如,网络程序可以将网络数据包 ( skb)的内核表示作为输入参数)。
r6-r9 通用寄存器
r10 唯一的只读寄存器,包含用于访问BPF堆栈空间的帧指针地址
  • 一个程序计数器
  • 存储模块,也就是图中的MAP,可以被用户空间程序用来进行访问
  • 解释器,将BPF字节码转换为特定CPU架构能用的机器码。一共两个,看内核是否使能JIT,不使能则用Interpreter,使能则用JIT

机器码

就是解释器最后把BPF字节码变成特定架构能用的机器码啦。总的流程如下BPF程序-> clang-> llvm -> BPF字节码-> JIT/内核计时器-> 机器码

触发运行

前面其实相当于一个注册的流程,那怎么注入运行的呢,感觉很神奇,不过这里就不研究了。类比就是insmod了, 但是没有ioctl那样。

ebpf

前面很多概念都是BPF的,其实eBPFBPF差不多,只是extend了,扩展了一下而已,有两项。

常规项扩展

对比项 BPF eBPF
寄存器数量 2个:寄存器A和寄存器X 10个:R0-R9,此外R10是只读的帧指针寄存器
寄存器宽度 32位 64位
存储 16个内存槽位:M[0-15] 512字节大小的栈空间,外加无限制的映射型存储Map
受限的内核调用 非常受限,JIT专用 可用,通过BPF_CALL指令
支持的事件类型 网络数据包,seccomp-BPF 网络数据包,内核函数,用户态函数,跟踪点,用户态标记,PMC

x86-64上,所有寄存器都一一映射到硬件寄存器,例如,x86-64JIT编译器可以将它们映射为:R0 - raxR1 - rdiR2 - rsi R3 -rdx R4 - rcxR5 - r8 R6 - rbx R7 - r13 R8 - r14 R9 - r15R10 - rbp

指令集扩展

img

编程方式

image-20241024013019053

参考链接