作者:闻茂泉

参考之资

了解和把握纯c言语的ebpf编译和运用,有助于咱们加深对于eBPF技能原理的进一步把握,也有助于开发符合自己业务需求的高性能的ebpf程序。现在常见和干流的纯c言语的ebpf编译运用方法,主要是两种。一种是内核源码中原生供给的编译方法。别的一种是libbpf-bootstrap项目中供给的skeleton编译方法。libbpf-bootstrap方法和社区5.x以上内核结合的比较好,以后再做介绍,今日咱们挑选根据4.18内核的根据内核源码的原生编译方法做介绍。

在国内学习ebpf技能,就不得不提到《Linux内核观测技能BPF》书籍译者狄卫华教师。狄教师还有一个网站《深化浅出 eBPF》。在网站里,他专门用一篇文章介绍了根据内核源码方法编译ebpf的方法,文章内容叫《【BPF入门系列-3】BPF 环境搭建》

网址:www.ebpf.top/post/ebpf_c…

咱们今日将参考这篇文章内容,对根据内核源码方法的纯c言语的ebpf编译方法做进一步剖析。

获取内核源码

现在干流的服务器的操作体系环境还是以8u + 4.18内核为主。因而,本文以4.18版别内核为主要剖析目标。咱们供给如下操作体系环境的获取建议:

获取操作体系环境

假如你自己有centos8u兼容环境操作体系,则能够运用已有的环境。假如没有,能够经过阿里云官网购买阿里云主机,挑选挑选centos8或者anolis8操作体系环境。

$  cat /etc/centos-release
CentOS Linux release 8.5.2111
$  uname -r
4.18.0-348.7.1.el8_5.x86_64

获取开源的内核源码

能够运用wget,从aliyun官网镜像,获取开源的4.18内核源码。

$  cd /tmp/$
wget https://mirrors.aliyun.com/linux-kernel/v4.x/linux-4.18.
tar.gz$  tar -zxvf linux-4.18.tar.gz$  
cd linux-4.18

下载内核源码一定要确保内核版别与操作体系的共同。原因是ebpf会用到VERSION、PATCHLEVEL和SUBLEVEL这3个宏的值与内核做内核版别校验。假如版别传的不对,ebpf校验会失利。

$  cat Makefile  | grep -P '^VERSION|^PATCHLEVEL|^SUBLEVEL'
VERSION = 4
PATCHLEVEL = 18
SUBLEVEL = 0

初始化基础环境

需求装置ebpf编译时依赖的llvm和clang等rpm包。此外内核编译还需求依赖openssl-devel等rpm包。

$  sudo yum install bison flex openssl-devel
$  sudo yum install clang llvm elfutils-libelf-devel

具体每个试验机器的环境可能略有差别,需求根据自己的状况做细节调整。

编译内核源码中ebpf程序样例

编译环境初始化

狄教师的文章中这里履行的是make scripts,在内核源码编译时此进程前通常还需求履行make prepare。而make init正好包括这两步make prepare && make scripts。因而,咱们将指令按照如下方法优化,根本能够一遍跑过:

$  cd /tmp/linux-4.18
$  make oldconfig && make init  # make oldconfig && make prepare && make scripts
$  make headers_install

编译内核源码样例

总算履行到了内核源码中供给的ebpf程序样例的编译。

$  make M=samples/bpf

履行样例程序

咱们能够经过对样例程序的履行,对编译效果进行验证。成果显现履行成功,狄教师文章中的进程验证经过,有点小激动。

$  sudo ./samples/bpf/trace_output
recv 1766352 events per sec

内核源码的ebpf编译要害进程提取

接下去就是本文最重点的部分,对ebpf编译进程的剖析。咱们主要分剖析headers_install和对samples/bpf目录的make这2个进程。

头文件装置 make headers_install

从头获取一个干净的内核源码,再次履行上面的编译进程。这次咱们对编译进程增加一些观察进程。

$  cd  /tmp/
$  rm -fr /tmp/linux-4.18
$  tar -zxvf linux-4.18.tar.gz 
$  cd /tmp/linux-4.18
$  make oldconfig && make init
$  ls usr/include/
ls: cannot access usr/include/: No such file or directory      # 此时include目录不存在
$  make headers_install
$  ls usr/include/ -R  | grep -v -P ':$' | grep -v -P '^$' | wc -l931                                                      #  此时include目录下有931个文件
$  diff -rs usr/include/ /usr/include/|grep -P '^Files .+ and .+ are identical$'|wc -l677

这说明内核源码目录下,headers_install进程生成的usr/include/目录下功用900多个文件,其间大多数(677个)文件都能在操作体系环境的/usr/include/下找到彻底一摸一样的同名文件,而且内容也彻底相同。

$  rpm -ql kernel-headers | wc -l
964  
$  rpm -ql kernel-headers | head 
/usr/include/asm
/usr/include/asm-generic
/usr/include/asm-generic/bpf_perf_event.h

而操作体系环境的/usr/include/目录正好是kernel-headers包的装置目录。所以编译进程中headers_install进程就是在内核源码目录生成了kernel-headers包效果一样的内容。

eBPF样例编译 make M=samples/bpf

ebpf样例的编译进程,咱们做一下改进,经过SHELL选项翻开shell的调试选项。具体指令如下:

$  make M=samples/bpf --debug=v,m SHELL="bash -x" > make.log 2>&1

经过剖析make.log,再结合其他一些黑科技,能够大概找出内核源码样例中trace_output指令的编译头绪。其间用户态编译头绪如下。为了表述上更加突出主题,此处只显现编译指令的要害信息,下一节会给出完好编译指令。

$  gcc -g -fPIC -c -o libbpf.o libbpf.c
$  gcc -g -fPIC -c -o bpf.o bpf.c
$  gcc -g -fPIC -c -o btf.o btf.c
$  gcc -g -fPIC -c -o nlattr.o nlattr.c
$  ld -r -o libbpf-in.o libbpf.o bpf.o nlattr.o btf.o
$  ar rcs libbpf.a libbpf-in.o
$  gcc -O2 -std=gnu89 -c -o bpf_load.o bpf_load.c
$  gcc -O2 -std=gnu89 -c -o trace_output_user.o trace_output_user.c
$  gcc -O2 -std=gnu89 -c -o trace_helpers.o trace_helpers.c
$  gcc -o trace_output bpf_load.o trace_output_user.o trace_helpers.o libbpf.a -lelf -lrt

其间内核态编译头绪如下:

$  clang -O2 -emit-llvm -c trace_output_kern.c -o -
$  llc -march=bpf -filetype=obj -o trace_output_kern.o

其间前一行最后的横线-表明 这里是输出给shell管道,所以这两行实际是能够经过shell管道拼接成一个指令来履行的。

手艺编译内核源码中的eBPF样例剖析

经过上一节对要害进程make M=samples/bpf的实践,咱们已经能够编译出内核源码中供给的ebpf样例。但这还不行咱们充分地了解这个编译进程,咱们将这编译进程持续拆解一下,拆解成能够一步步履行的那种,为了方便大家了解,我将这个进程分解为 A-H 6大手艺进程,里面还会包括一些细分的小进程:

$  cd  /tmp/
$  rm -fr /tmp/linux-4.18$  tar -zxvf linux-4.18.tar.gz 
$  cd /tmp/linux-4.18
$  make oldconfig && make init
$  make headers_install
$  cd tools/lib/bpf/

手艺进程A进程解析

手艺进程A1:

$  # gcc -g -fPIC -c -o libbpf.o libbpf.c
$  gcc -g -DHAVE_LIBELF_MMAP_SUPPORT -DCOMPAT_NEED_REALLOCARRAY -fPIC -I. -I/tmp/linux-4.18/tools/include -I/tmp/linux-4.18/tools/arch/x86/include/uapi -I/tmp/linux-4.18/tools/include/uapi -I/tmp/linux-4.18/tools/perf -D"BUILD_STR(s)=#s" -c -o libbpf.o libbpf.c

手艺进程A2:

$ # gcc -g -fPIC -c -o bpf.o bpf.c
$  gcc -g -DHAVE_LIBELF_MMAP_SUPPORT -DCOMPAT_NEED_REALLOCARRAY -fPIC -I. -I/tmp/linux-4.18/tools/include -I/tmp/linux-4.18/tools/arch/x86/include/uapi -I/tmp/linux-4.18/tools/include/uapi -I/tmp/linux-4.18/tools/perf -D"BUILD_STR(s)=#s" -c -o bpf.o bpf.c

手艺进程A3:

$ # gcc -g -fPIC -c -o btf.o btf.c
$  gcc -g -DHAVE_LIBELF_MMAP_SUPPORT -DCOMPAT_NEED_REALLOCARRAY -fPIC -I. -I/tmp/linux-4.18/tools/include -I/tmp/linux-4.18/tools/arch/x86/include/uapi -I/tmp/linux-4.18/tools/include/uapi -I/tmp/linux-4.18/tools/perf -D"BUILD_STR(s)=#s" -c -o btf.o btf.c

手艺进程A4:

$ # gcc -g -fPIC -c -o nlattr.o nlattr.c
$  gcc -g -DHAVE_LIBELF_MMAP_SUPPORT -DCOMPAT_NEED_REALLOCARRAY -fPIC -I. -I/tmp/linux-4.18/tools/include -I/tmp/linux-4.18/tools/arch/x86/include/uapi -I/tmp/linux-4.18/tools/include/uapi -I/tmp/linux-4.18/tools/perf -D"BUILD_STR(s)=#s" -c -o nlattr.o nlattr.c

针对手艺进程A1到A4的要害编译选项做一些介绍。

  • -fPIC,告诉编译器输出方位无关目标,为后边生成共享库埋下伏笔。

  • -I. 表明需求包括当时目录下的头文件。

  • -I/tmp/linux-4.18/tools/include -I/tmp/linux-4.18/tools/arch/x86/include/uapi -I/tmp/linux-4.18/tools/include/uapi -I/tmp/linux-4.18/tools/perf,这4个头文件,是用户态ebpf程序所依赖tool目录下的头文件方位。

手艺进程B进程解析

$  ld -r -o libbpf-in.o  libbpf.o bpf.o nlattr.o btf.o

手艺进程B是将进程A中产生4个.o文件进行链接。

手艺进程C进程解析

$  ar rcs libbpf.a libbpf-in.o

手艺进程C是从链接后的文件中提取静态库文件。

手艺进程D/E/F进程解析

手艺进程D:

$  # gcc -O2 -std=gnu89 -c -o bpf_load.o bpf_load.c
$  gcc -O2 -fomit-frame-pointer -std=gnu89 -I./usr/include -I./tools/lib/ -I./tools/testing/selftests/bpf/ -I./tools/lib/ -I./tools/include -I./tools/perf -I./usr/include -Wno-unused-variable -c -o samples/bpf/bpf_load.o samples/bpf/bpf_load.c

手艺进程E:

$  # gcc -O2 -std=gnu89 -c -o trace_output_user.o trace_output_user.c
$  gcc -O2 -fomit-frame-pointer -std=gnu89 -I./usr/include -I./tools/lib/ -I./tools/testing/selftests/bpf/ -I./tools/lib/ -I./tools/include -I./tools/perf -I./tools/lib/bpf/ -c -o samples/bpf/trace_output_user.o samples/bpf/trace_output_user.c

手艺进程F:

$  # gcc -O2 -std=gnu89 -c -o trace_helpers.o trace_helpers.c
$  gcc -O2 -fomit-frame-pointer -std=gnu89 -I./usr/include -I./tools/lib/ -I./tools/testing/selftests/bpf/ -I./tools/lib/ -I./tools/include -I./tools/perf -I./tools/lib/bpf/ -c -o samples/bpf/../../tools/testing/selftests/bpf/trace_helpers.o samples/bpf/../../tools/testing/selftests/bpf/trace_helpers.c

针对手艺进程E的要害编译选项做一些介绍。手艺进程D和手艺进程F与此相似。

  • O2 和 -std=gnu89 是两个中心选项。

  • include选项,一共有6个,咱们将其分为3组。第一组是-I./usr/include ,这表明包括等同于kernel-headers的内容。● 第二组是-I./tools/lib/, -I./tools/include,-I./tools/perf,-I./tools/lib/bpf/● 第三组是-I./tools/testing/selftests/bpf/。之所以把这一组独自独立出来,是因为它和样例代码处于相同的途径。

手艺进程G进程解析

$  # gcc -o trace_output bpf_load.o trace_output_user.o trace_helpers.o libbpf.a -lelf -lrt
$  gcc -o samples/bpf/trace_output samples/bpf/bpf_load.o samples/bpf/trace_output_user.o samples/bpf/../../tools/testing/selftests/bpf/trace_helpers.o /tmp/linux-4.18/samples/bpf/../../tools/lib/bpf/libbpf.a -lelf -lrt

针对手艺进程G的要害编译选项做一些介绍。

  • -lelf -lrt链接两个类库

  • libbpf.a表明以静态链接库的方法链接libbpf的类库。● 最要害的是,没有增加-static选项,没有增加-static选项,没有增加-static选项,重要的事情说三遍。

6.6 手艺进程H进程解析

$  clang -nostdinc -isystem /usr/lib/gcc/x86_64-redhat-linux/8/include -I./arch/x86/include -I./arch/x86/include/generated -I./include -I./arch/x86/include/uapi -I./arch/x86/include/generated/uapi -I./include/uapi -I./include/generated/uapi -include ./include/linux/kconfig.h -Isamples/bpf -I./tools/testing/selftests/bpf/ -D__KERNEL__ -D__BPF_TRACING__ -D__TARGET_ARCH_x86 -O2 -emit-llvm -c samples/bpf/trace_output_kern.c -o - | llc -march=bpf -filetype=obj -o samples/bpf/trace_output_kern.o

针对手艺进程H的要害编译选项做一些介绍。

  • -nostdinc -isystem /usr/lib/gcc/x86_64-redhat-linux/8/include,这2个选项是一组。nostdinc表明屏蔽掉体系默许的include环境,替换成当时gcc编译器自带的include头文件环境。

  • -I./arch/x86/include,-I./arch/x86/include/generated,-I./include,-I./arch/x86/include/uapi,-I./arch/x86/include/generated/uapi,-I./include/uapi,-I./include/generated/uapi。这7个头文件很要害,是内核态ebpf程序所依赖的绝大多数头文件的方位。● -include ./include/linux/kconfig.h,这个头文件也很要害,是让上面7个头文件收效的前提条件。● -I samples/bpf 和 -I ./tools/testing/selftests/bpf/,这2个头文件是和ebpf样例所处方位相同,独自独立出来看。● llc是llvm的连接器。内核是将clang的编译和llc的链接独立成两步完结,在llc进程才指定-march=bpf。对编译成果进行验证,完美验证经过,第2次有点小激动。

$  sudo ./samples/bpf/trace_outputrecv 1760674 events per sec

要害进程抽取不是最终意图,根本意图是能让咱们实现脱离内核源码进行独立的纯C言语编译。咱们将在后续的文章中进一步阐述。

关于4.9版别内核

按照内核的原生进程,对4.9内核进行一次编译,咱们会发现对应手艺进程E的这一步,编译代码有点不一样,具体代码如下。

$ gcc -o samples/bpf/trace_output samples/bpf/bpf_load.o samples/bpf/libbpf.o samples/bpf/trace_output_user.o -lelf -lrt

其间没有了对libbpf.a静态库的链接,但却多了一个libbpf.o文件的链接。

$  cd /tmp/linux-4.9/
$  find . -name libbpf.c
./samples/bpf/libbpf.c
./tools/lib/bpf/libbpf.c

查询内核源码,能够发现,在4.9内核下,有2个libbpf.c文件,别离处于./tools/lib/bpf/目录和./samples/bpf/目录。而内核ebpf样例暂时运用的还是老的./samples/bpf/libbpf.c文件。

进一步探究

本文为eBPF着手实践系列的第一篇,咱们实现了根据内核源码框架的一步一步的纯C言语编译,下一篇咱们会对这个编译进程持续深化探究,实现脱离内核源码后的纯C言语编译。

欢迎有想法或者有问题的同学,加群交流eBPF技能以及工程实践。

SREWorks数智运维工程群(钉钉群号:35853026)

附录: eBPF手艺纯C编译完好指令清单

cd  /tmp/rm -fr /tmp/linux-4.18tar -zxvf linux-4.18.tar.gz cd /tmp/linux-4.18make oldconfig && make initmake headers_installcd tools/lib/bpf/
# 进程A1# gcc -g -fPIC -c -o libbpf.o libbpf.cgcc -g -DHAVE_LIBELF_MMAP_SUPPORT -DCOMPAT_NEED_REALLOCARRAY -fPIC -I. -I/tmp/linux-4.18/tools/include -I/tmp/linux-4.18/tools/arch/x86/include/uapi -I/tmp/linux-4.18/tools/include/uapi -I/tmp/linux-4.18/tools/perf -D"BUILD_STR(s)=#s" -c -o libbpf.o libbpf.c
# 进程A2# gcc -g -fPIC -c -o bpf.o bpf.cgcc -g -DHAVE_LIBELF_MMAP_SUPPORT -DCOMPAT_NEED_REALLOCARRAY -fPIC -I. -I/tmp/linux-4.18/tools/include -I/tmp/linux-4.18/tools/arch/x86/include/uapi -I/tmp/linux-4.18/tools/include/uapi -I/tmp/linux-4.18/tools/perf -D"BUILD_STR(s)=#s" -c -o bpf.o bpf.c
# 进程A3# gcc -g -fPIC -c -o btf.o btf.cgcc -g -DHAVE_LIBELF_MMAP_SUPPORT -DCOMPAT_NEED_REALLOCARRAY -fPIC -I. -I/tmp/linux-4.18/tools/include -I/tmp/linux-4.18/tools/arch/x86/include/uapi -I/tmp/linux-4.18/tools/include/uapi -I/tmp/linux-4.18/tools/perf -D"BUILD_STR(s)=#s" -c -o btf.o btf.c
# 进程A4# gcc -g -fPIC -c -o nlattr.o nlattr.cgcc -g -DHAVE_LIBELF_MMAP_SUPPORT -DCOMPAT_NEED_REALLOCARRAY -fPIC -I. -I/tmp/linux-4.18/tools/include -I/tmp/linux-4.18/tools/arch/x86/include/uapi -I/tmp/linux-4.18/tools/include/uapi -I/tmp/linux-4.18/tools/perf -D"BUILD_STR(s)=#s" -c -o nlattr.o nlattr.c
# 进程Bld -r -o libbpf-in.o  libbpf.o bpf.o nlattr.o btf.o
# 进程Car rcs libbpf.a libbpf-in.o 
cd /tmp/linux-4.18/
# 进程D# gcc -O2 -std=gnu89 -c -o bpf_load.o bpf_load.cgcc -O2 -fomit-frame-pointer -std=gnu89 -I./usr/include -I./tools/lib/ -I./tools/testing/selftests/bpf/ -I./tools/lib/ -I./tools/include -I./tools/perf -I./usr/include -Wno-unused-variable -c -o samples/bpf/bpf_load.o samples/bpf/bpf_load.c
# 进程E# gcc -O2 -std=gnu89 -c -o trace_output_user.o trace_output_user.cgcc -O2 -fomit-frame-pointer -std=gnu89 -I./usr/include -I./tools/lib/ -I./tools/testing/selftests/bpf/ -I./tools/lib/ -I./tools/include -I./tools/perf -I./tools/lib/bpf/ -c -o samples/bpf/trace_output_user.o samples/bpf/trace_output_user.c
# 进程F# gcc -O2 -std=gnu89 -c -o trace_helpers.o trace_helpers.cgcc -O2 -fomit-frame-pointer -std=gnu89 -I./usr/include -I./tools/lib/ -I./tools/testing/selftests/bpf/ -I./tools/lib/ -I./tools/include -I./tools/perf -I./tools/lib/bpf/ -c -o samples/bpf/../../tools/testing/selftests/bpf/trace_helpers.o samples/bpf/../../tools/testing/selftests/bpf/trace_helpers.c
# 进程G# gcc -o trace_output bpf_load.o trace_output_user.o trace_helpers.o libbpf.a -lelf -lrtgcc -o samples/bpf/trace_output samples/bpf/bpf_load.o samples/bpf/trace_output_user.o samples/bpf/../../tools/testing/selftests/bpf/trace_helpers.o /tmp/linux-4.18/samples/bpf/../../tools/lib/bpf/libbpf.a -lelf -lrt
# 进程Hclang -nostdinc -isystem /usr/lib/gcc/x86_64-redhat-linux/8/include -I./arch/x86/include -I./arch/x86/include/generated -I./include -I./arch/x86/include/uapi -I./arch/x86/include/generated/uapi -I./include/uapi -I./include/generated/uapi -include ./include/linux/kconfig.h -Isamples/bpf -I./tools/testing/selftests/bpf/ -D__KERNEL__ -D__BPF_TRACING__ -D__TARGET_ARCH_x86 -O2 -emit-llvm -c samples/bpf/trace_output_kern.c -o - | llc -march=bpf -filetype=obj -o samples/bpf/trace_output_kern.o