zoukankan      html  css  js  c++  java
  • BPF C编程入门

    Linux内核之旅-BPF C编程入门 的笔记

    先说一下,这里的BPF实际上是指eBPF,不是传统的cBPF

    1.搭建BPF程序运行环境

    1.1.下载内核源码

    下载的内核版本应与你系统的版本一致,查看当前内核版本 uname -r

    然后在源码镜像站点(http://ftp.sjtu.edu.cn/sites/ftp.kernel.org/pub/linux/kernel)下载对应版本的内核源码

    也可以通过Ubuntu apt仓库下载。Ubuntu官方自己维护了每个操作系统版本的背后的Linux内核代码,可以通过以下两种apt命令方式获取相关代码:

    # 第一种方式
    # 先搜索
    > apt-cache search linux-source
        linux-source - Linux kernel source with Ubuntu patches
        linux-source-4.15.0 - Linux kernel source for version 4.15.0 with Ubuntu patches
        linux-source-4.18.0 - Linux kernel source for version 4.18.0 with Ubuntu patches
        linux-source-5.0.0 - Linux kernel source for version 5.0.0 with Ubuntu patches
        linux-source-5.3.0 - Linux kernel source for version 5.3.0 with Ubuntu patches
    # 再安装
    > apt install linux-source-4.15.0
        
    # 第二种方式
    > apt-get source linux
        Reading package lists... Done
        NOTICE: 'linux' packaging is maintained in the 'Git' version control system at:
        git://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/bionic
        Please use:
        git clone git://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/bionic
        to retrieve the latest (possibly unreleased) updates to the package.
        Need to get 167 MB of source archives.
        Get:2 https://mirrors.ustc.edu.cn/ubuntu bionic-updates/main linux 4.15.0-99.100 (tar) [158 MB]
        ...
        
    # 以上两种方式,内核源代码均下载至/usr/src/目录下

    1.2.安装依赖项

    apt install libncurses5-dev flex bison libelf-dev binutils-dev libssl-dev

    1.3.安装Clang和LLVM

    然后使用以下两条命令分别安装 clang 和 llvm

    apt install clang
    apt install llvm

    1.4.配置内核

    在源码根目录下使用make defconfig生成.config<c/ode>文件

    1.5.解决modpost: not found错误

    因为直接make M=samples/bpf时,会报错缺少modules的错误。修复modpost的错误,以下两种解决方案二选一

    make modules_prepare

    make script

    1.6.关联内核头文件

    make headers_install

    1.7.编译内核程序样例

    在源码根目录下执行make M=samples/bpf,

    此时进入linux-source-4.15.0/smaples/bpf中,会看到生成了BPF字节码文件*_kern.o和用户态的可执行文件

    你可以运行几个试试,例如sockex1

    2. 使用BPF C编写hello world程序

    2.1.先了解一下原理吧

    BPF程序经过Clang/LLVM编译成BPF字节码,然后通过BPF系统调用的方式加载进内核,然后交给BPF虚拟机来执行,也是JIT的方式动态转成机器码

    内核有很多hook点,我们在写BPF程序时也会做事件源配置。当hook点上的事件发生时,就会执行我们的BPF程序。

    我们还可以在BPF程序中创建一个Map,把我们想拿到的数据保存在Map中,然后用户态程序就可以拿到。

    总之,就是我们可以通过BPF程序拿到内核的一些数据

    2.2.hello world程序

    进入samples/bpf目录,可以利用自带的Makefile编译,

    编写hello_kern.c:

    #include <linux/bpf.h>
    #include "bpf_helpers.h"
    #define SEC(NAME) __attribute__((section(NAME), used))
    
    SEC("tracepoint/syscalls/sys_enter_execve")
    int bpf_prog(void *ctx){
            char msg[] = "Hello World\n";
            bpf_trace_printk(msg, sizeof(msg));
            return 0;
    }
    
    char _license[] SEC("license") = "GPL";

    这个程序的作用就是当发生系统调用(sys_enter_execve)时在终端输出"Hello World",其实bpf_trace_printk只是将msg写到一个管道文件中

    编写hello_user.c:

    #include <stdio.h>
    #include "bpf_load.h"
    
    int main(int argc, char **argv){
            if(load_bpf_file("hello_kern.o")!=0){
                    printf("The kernel didn't load BPF program\n");
                    return -1;
            }
    
            read_trace_pipe();
            return 0;
    }

    这个程序的作用是将包含BPF的文件hello_kern.o通过系统调用的方式加载进内核,read_trace_pipe()读取管道文件并打印到终端

    2.3.修改Makefile

    模仿原有的,有四处需要修改:

    # List of programs to build
    hostprogs-y += hello
    
    # Libbpf dependencies
    hello-objs := bpf_load.o $(LIBBPF) hello_user.o
    
    # Tell kbuild to always build the programs
    always += hello_kern.o
    
    HOSTLOADLIBES_hello += -lelf

    2.4.编译

    可以返回源码根目录用 make M=samples/bpfmake samples/bpf/ 编译

    或者直接在当前目录(samples/bpf) 执行make 编译

    可以查看编译后的结果,生成了hello可执行文件

    2.5.运行

    3.进一步

    进一步学习BPF程序是如何转换成字节码的

    3.1.BPF程序中的节(section)

    SEC宏会将宏里面的内容(kprobe/sys_write)作为节的名字放到elf文件中,也就是目标文件,可以用readelf工具查看

    还用宏生成了一个名字为license的section

    3.2.BPF程序中的字节码(bytecode)

    可以用objdump工具查看

    可见是将我们的bpf程序编译到elf文件的某个节中,右边黄框内就是常说的bpf字节码,对应左边灰色内容

    接下来讲一下,bpf程序是如何转成字节码的

    3.3.BPF内核辅助函数调用转换为BPF字节码的过程

    我们用到的BPF内核辅助函数是bpf_trace_printk

    bpg_prog是我们的elf函数名字,分析下call 6是怎么得到的?

    BPF_FUNC_map_lookup_elem(BPF_FUNC_trace_printk类似)是在bpf.h中定义的,只不过是宏的形式,我们将其展开:

    可见BPF_FUNC_trace_printk的相对位置是6,

    一般BPF内核辅助函数转汇编是这样的:

    就是BPF_call id,id就是bpf_func_id中的id;

    进一步就是BPF_EMIT_CALL(func name)

    例如,在内核中的某一处代码,调用bpf_map_lookup_elem,在BPF指令集编程中,就是使用BPF_EMIT_CALL来调用的

    不难想象,我们调用bpf_trace_printk也是采用同样的调用方式

    BPF_EMIT_CALL(func name)是如何转化成字节码的呢?

    _bpf_call_base啥也没做,直接返回0,可见只是需要其地址,而差值就是在enum中的位置

    进一步分析

     

    所以,call 6对应的字节码就是85 00 00 00 06 00 00 00

    我们还可以进一步查看JIT前后字节码的变化:

    首先执行objdump -s hello_kern.o 得到JIT之前的字节码:

    在一直运行hello 

    进入linux-source-4.15.0/tools/bpf/bpftool目录,make,生成bpftool工具,

    通过 ./bpftool prog show 显示加载了哪些BPF程序:

    可见我们的hello程序对应的id为86,钩子类型为tracepoint

    再使用./bpftool prog dump xlated id 86 opcodes 即可查看JIT之后的字节码:

    对比起来看:

    其他的没变,可以看到这个变化,这是因为JIT前call使用的id,JIT后成了调用函数到这个指令的距离

    3.4.BPF程序到BPF字节码的编译过程:Clang与LLVM

    LLVM支持很多后端,通过命令llc -version

    bpf target有三种,不指定就根据系统的大小端法

    有两种方式编译BPF程序:

    gcc缺少BPF backend,幸运的是clang支持BPF. 之前的Makefile就是使用clang将hello_kern.c编译成hello_kern.o

    右边的图表示一步到位和分布编译的结果是一样的,而且是之前用Makefile编译的也一样

    分步编译是生成中间IR文件,默认是.ll格式

    除视频外还参考了:

    1.  https://blog.csdn.net/qq_34258344/article/details/108932912

    2. https://cloud.tencent.com/developer/article/1644458

    特别是修改Makefile的地方,网上的都只改了三个地方,CSDN杀我¥#……

    有个小问题是SEC("kprobe/sys_write")这个hook用不了,我只能换一个试了.

  • 相关阅读:
    python之sorted函数的三个参数
    python 局部变量和全局变量
    简单的理解https的连接过程
    关于web服务接口测试的一些问题及答案
    常用函数
    服务器并发处理能力
    JEMETER 录制
    resin
    loadrunner 计数器
    memcached and redis
  • 原文地址:https://www.cnblogs.com/lfri/p/15402973.html
Copyright © 2011-2022 走看看