基于eBPF的嵌入式应用调试
笔者之前写过几篇有关于使用eBPF调试Linux内核和应用的博客,其中提到,在嵌入式设备上使用BCC或bpftrace是不可行的;主要原因在于嵌入式设备的资源有限,而这两个调试工具依赖python
/clang
/llvm
等库,需要将脚本编译为eBPF
字节码加载入Linux内核来执行。然而,随着嵌入式设备的性能越来越强,板载资源越来越多,在嵌入式设备上运行bpftrace
工具已成为可能。本文整理了笔者为raspberrypi 4
设备编译bpftrace
工具的过程,按照这些操作,笔者为工作中的嵌入式设备也制作了可用的bpftrace
调试工具。
另外,22.04版本的Ubuntu
系统自带的bpftrace
已经不能用了,主要的原因是它不带符号表(strip -s
);本文的内容也可用于PC
端的bpftrace
工具编译构建:
root@ubuntu:/usr/sbin# uname -a
Linux ubuntu 5.15.0-97-generic #107-Ubuntu SMP Wed Feb 7 13:26:48 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
root@ubuntu:/usr/sbin# ./execsnoop.bt
Attaching 3 probes...
ERROR: Could not resolve symbol: /proc/self/exe:BEGIN_trigger
编译使用的设备
首先,笔者使用的raspberrypi 4
可插入两张TF卡,两张卡分别安装了debian
系统和笔者编译的openwrt
系统,二者的Linux内核版本大致相同,均为6.1版本。其次,树莓派设备的内存大小为2GB,这一点非常重要;因为bcc/bpftrace
是由C++编写的,编译过程非常耗内存(笔者使用clang编译器构建)。最后,笔者是为了在openwrt
系统中使用bpftrace
,因此要求在构建openwrt
系统时,选择glibc库。
零,安装clang编译器
编译在是树莓派的debian系统上完成的。需要按照apt.llvm.org的说明操作来安装,笔者选择了安装clang-10
编译器:
sh
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
./llvm.sh 10
之后,我们需要在debian安装其他依赖库(而不编译所有的依赖库,否则耗时太久了)。以下需要安装的库,是在raspberrypi/openwrt
系统上缺少的,后面会直接复制到安装有openwrt
系统的TF卡上:
sh
sudo apt install flex bison zlib1g-dev liblzma-dev libbz2-dev cmake \
libzstd-dev dwarves build-essential python3-setuptools libpython3-dev
一,编译elfutils
笔者下载了最新版本的elfutils,它提供了libelf
依赖库,该软件包的配置、编译操作如下:
sh
../configure --prefix=/opt/bpftrace CC=clang-10 CXX=clang++-10 \
CFLAGS='-Wall -fPIC -O2 -D_GNU_SOURCE -ggdb -fno-omit-frame-pointer -Wno-unused-parameter' \
CXXFLAGS='-Wall -fPIC -O2 -D_GNU_SOURCE -ggdb -fno-omit-frame-pointer -Wno-unused-parameter' \
--disable-nls --enable-libdebuginfod=dummy --disable-debuginfod --with-zlib --with-lzma \
--with-bzlib --with-zstd LDFLAGS='-Wl,-rpath=/opt/bpftrace/lib'
make && sudo make install
以上配置完成后,再执行make && sudo make install
即可安装。
二,编译libbpf库
这里需要强调的是,依笔者的经验,libbpf
库以及bcc、bpftrace版本的选择,不能任意;一定要根据Linux内核版本来选择。例如,树苺派的Linux内核版本为6.1,那么确认该版本内核的(初始)发布日期(是2023年初发布的),然后选择时间上相近的libbpf
库。这里笔者选择的版本为libbpf-1.2.2
。然而工作中,笔者因使用的嵌入式设备比较老,使用的版本为0.1.1
。
该库的编译操作如下:
sh
cd $HOME ; tar -zxf libbpf-1.2.2.tar.gz
cd libbpf-1.2.2/src
make V=1 PREFIX=/opt/bpftrace CC=clang-10 CFLAGS='-fPIC -O2 -ggdb -Wall -fno-omit-frame-pointer -I/opt/bpftrace/include' LDFLAGS='-L/opt/bpftrace/lib -Wl,-rpath=/opt/bpftrace/lib'
sudo make V=1 PREFIX=/opt/bpftrace CC=clang-10 CFLAGS='-fPIC -O2 -ggdb -Wall -fno-omit-frame-pointer -I/opt/bpftrace/include' LDFLAGS='-L/opt/bpftrace/lib -Wl,-rpath=/opt/bpftrace/lib' install
注意,以上编译的库在debian/arm64
系统下会被安装到/opt/bpftrace/lib64
路径下,需要移到/opt/bpftrace/lib
路径下,并修改/opt/bpftrace/lib/pkgconfig/libbpf.pc
文件。
三,安装cereal头文件
早期的bpftrace
工具不依赖该库,但现在是需要了,笔者选择了cereal-1.3.2.tar.gz
版本:
sh
mkdir -p build_aarch64 && cd build_aarch64
cmake -DCMAKE_BUILD_TYPE=release -DCMAKE_INSTALL_PREFIX=/opt/bpftrace \
-DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10 \
-DCMAKE_C_FLAGS='-fPIC -O2 -D_GNU_SOURCE -ggdb -fno-omit-frame-pointer' \
-DCMAKE_CXX_FLAGS='-fPIC -O2 -D_GNU_SOURCE -ggdb -fno-omit-frame-pointer' \
-DBUILD_DOC=OFF -DBUILD_SANDBOX=OFF -DSKIP_PERFORMANCE_COMPARISON=ON -DBUILD_TESTS=OFF ..
sudo make install
注意,该库以头文件的形式提供,以上不会有动态库编译生成。
四,编译bcc
bcc的版本为v0.27.0,笔者已验证最新版本的bcc
编译得到的bpftrace
存在异常。需要注意的是,笔者下载的源代码文件名为bcc-src-with-submodule.tar.gz
。以下是配置、编译安装的操作:
sh
mkdir -p build_aarch64 && cd build_aarch64
cmake -DCMAKE_BUILD_TYPE=release -DCMAKE_INSTALL_PREFIX=/opt/bpftrace \
-DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10 \
-DCMAKE_C_FLAGS='-fPIC -O2 -D_GNU_SOURCE -ggdb -fno-omit-frame-pointer -I/opt/bpftrace/include' \
-DCMAKE_CXX_FLAGS='-fPIC -O2 -D_GNU_SOURCE -ggdb -fno-omit-frame-pointer -I/opt/bpftrace/include' \
-DCMAKE_EXE_LINKER_FLAGS='-L/opt/bpftrace/lib -Wl,-rpath=/opt/bpftrace/lib' \
-DCMAKE_MODULE_LINKER_FLAGS='-L/opt/bpftrace/lib -Wl,-rpath=/opt/bpftrace/lib' \
-DCMAKE_SHARED_LINKER_FLAGS='-L/opt/bpftrace/lib -Wl,-rpath=/opt/bpftrace/lib' \
-DENABLE_LLVM_SHARED=ON -DENABLE_TESTS=OFF -DRUN_LUA_TESTS=OFF -DENABLE_LIBDEBUGINFOD=OFF ..
make && sudo make install
编译完成后,笔者删除了所有的静态库(因为笔者希望所有的库是动态链接的):sudo rm -rf /opt/bpftrace/lib/*.a
。注意,这个编译过程比较耗时,大约持继在一小时以上。因rasbperrypi 4
内存有2GB,上面可以make -j2
来编译加速。
五,编译bpftrace
笔者选择的bpftrace
版本为v0.17.1
(正如上面提到的,笔者验证最新版本的bpftrace
在arm64/linux-6.1
系统下运行不正常。配置、编译安装的操作如下:
sh
mkdir -p build_aarch64 && cd build_aarch64
cmake -DCMAKE_BUILD_TYPE=release -DCMAKE_INSTALL_PREFIX=/opt/bpftrace \
-DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10 \
-DCMAKE_C_FLAGS='-fPIC -O2 -D_GNU_SOURCE -ggdb -fno-omit-frame-pointer -I/opt/bpftrace/include -I${HOME}/libbpf-1.2.2/include -I${HOME}/libbpf-1.2.2/include/uapi' \
-DCMAKE_CXX_FLAGS='-fPIC -O2 -D_GNU_SOURCE -ggdb -fno-omit-frame-pointer -I/opt/bpftrace/include -I${HOME}/libbpf-1.2.2/include -I${HOME}/libbpf-1.2.2/include/uapi' \
-DCMAKE_EXE_LINKER_FLAGS='-L/opt/bpftrace/lib -Wl,-rpath=/opt/bpftrace/lib' \
-DCMAKE_MODULE_LINKER_FLAGS='-L/opt/bpftrace/lib -Wl,-rpath=/opt/bpftrace/lib' \
-DCMAKE_SHARED_LINKER_FLAGS='-L/opt/bpftrace/lib -Wl,-rpath=/opt/bpftrace/lib' \
-DUSE_SYSTEM_BPF_BCC=ON -DSTATIC_LINKING=OFF -DALLOW_UNSAFE_PROBE=ON -DBUILD_TESTING=OFF ..
make && sudo make install
需要注意的是,上面的编译选项,直接指定了libbpf-1.2.2
头文件包含的路径。至此,笔者的编译就结束了,接下来将编译结果复制到安装有openwrt
系统的TF卡中。
在raspberrypi/openwrt系统下使用bpftrace
首先,除了/opt/bpftrace
外,笔者从树莓派的debian系统中复制了以下库到openwrt
系统:
root@OpenWrt:/lib/aarch64-linux-gnu# ls -lh -t | head -n 8
-rw-r--r-- 1 root root 64.8M Sep 9 22:40 libLLVM-10.so.1
-rw-r--r-- 1 root root 65.0K Sep 9 22:42 libbz2.so.1.0
-rw-r--r-- 1 root root 27.7M Sep 9 22:41 libclang-10.so.1
-rw-r--r-- 1 root root 179.1K Sep 9 22:43 libedit.so.2
-rw-r--r-- 1 root root 30.2K Sep 9 22:42 libffi.so.6
-rw-r--r-- 1 root root 130.2K Sep 9 22:41 liblzma.so.5
-rw-r--r-- 1 root root 158.6K Sep 9 22:43 libtinfo.so.5
-rw-r--r-- 1 root root 405.8K Sep 9 22:41 libzstd.so.1
然后,笔者为树莓派的内核使能了相关的内核选项,详见此处的详细说明。还有,bpftrace
的正常运行,可能还需要访问内核的头文件,这就需要使能CONFIG_IKHEADERS=m
,在树莓派启动后,手动加载该内核模块:insmod kheaders.ko
。最后,笔者在树莓派的openwrt
系统下使用bpftrace
的结果如下:
root@OpenWrt:~# insmod kheaders.ko
module is already loaded - kheaders
root@OpenWrt:~# uname -a
Linux OpenWrt 6.1.50 #0 SMP Fri Sep 1 21:45:47 2023 aarch64 GNU/Linux
root@OpenWrt:~# /opt/bpftrace/bin/bpftrace --info
System
OS: Linux 6.1.50 #0 SMP Fri Sep 1 21:45:47 2023
Arch: aarch64
Build
version: v0.17.1
LLVM: 10.0.1
unsafe uprobe: no
bfd: no
libdw (DWARF support): yes
libbpf: failed to find valid kernel BTF
Kernel helpers
probe_read: yes
probe_read_str: yes
probe_read_user: yes
probe_read_user_str: yes
probe_read_kernel: yes
probe_read_kernel_str: yes
get_current_cgroup_id: yes
send_signal: yes
override_return: no
get_boot_ns: yes
dpath: no
skboutput: no
Kernel features
Instruction limit: 1000000
Loop support: yes
btf: no
map batch: yes
uprobe refcount (depends on Build:bcc bpf_attach_uprobe refcount): yes
Map types
hash: yes
percpu hash: yes
array: yes
percpu array: yes
stack_trace: yes
perf_event_array: yes
Probe types
kprobe: yes
tracepoint: yes
perf_event: yes
kfunc: no
iter:task: no
iter:task_file: no
kprobe_multi: no
raw_tp_special: yes
root@OpenWrt:~# cd /opt/bpftrace/share/bpftrace/tools/
root@OpenWrt:/opt/bpftrace/share/bpftrace/tools# ls
bashreadline.bt gethostlatency.bt runqlen.bt tcpdrop.bt
biolatency.bt killsnoop.bt setuids.bt tcplife.bt
biosnoop.bt loads.bt ssllatency.bt tcpretrans.bt
biostacks.bt mdflush.bt sslsnoop.bt tcpsynbl.bt
bitesize.bt naptime.bt statsnoop.bt threadsnoop.bt
capable.bt old swapin.bt undump.bt
cpuwalk.bt oomkill.bt syncsnoop.bt vfscount.bt
dcsnoop.bt opensnoop.bt syscount.bt vfsstat.bt
doc pidpersec.bt tcpaccept.bt writeback.bt
execsnoop.bt runqlat.bt tcpconnect.bt xfsdist.bt
root@OpenWrt:/opt/bpftrace/share/bpftrace/tools# /opt/bpftrace/bin/bpftrace ./execsnoop.bt
libbpf: failed to find valid kernel BTF
libbpf: failed to find valid kernel BTF
Attaching 3 probes...
TIME(ms) PID ARGS
26138 2490 /etc/init.d/firewall reload
26168 2502 readlink /etc/init.d/firewall
26170 2503 basename /etc/init.d/firewall
26172 2504 flock -n 1000
26174 2505 flock 1000
26178 2506 readlink /etc/init.d/firewall
26181 2507 readlink /etc/init.d/firewall
26183 2508 basename /etc/init.d/firewall
26186 2509 flock -n 1000
26188 2510 fw4 reload
26191 2511 flock -x 1000
26193 2512 rm -f /var/run/fw4.state
26195 2513 utpl -S /usr/share/firewall4/main.uc
26195 2514 nft -f /dev/stdin
26303 2515 sh -c /usr/sbin/nft --terse --json list flowtables inet
26305 2515 /usr/sbin/nft --terse --json list flowtables inet
26393 2516 utpl -S /usr/share/firewall4/main.uc
29341 2517 /etc/init.d/firewall status
29371 2529 readlink /etc/init.d/firewall
29373 2530 basename /etc/init.d/firewall
29375 2531 flock -n 1000
29377 2532 flock 1000
29381 2533 readlink /etc/init.d/firewall
29384 2534 basename /etc/init.d/firewall
29387 2537 jsonfilter -e @["firewall"]
29388 2539 jshn -w
29391 2540 ubus call service list { "name": "firewall" }
29397 2543 jsonfilter -e $.instances
63078 2544 /etc/init.d/network status
63108 2556 readlink /etc/init.d/network
63110 2557 basename /etc/init.d/network
63112 2558 flock -n 1000
63114 2559 flock 1000
63118 2560 readlink /etc/init.d/network
63121 2561 basename /etc/init.d/network
63124 2564 jsonfilter -e @["network"]
63125 2566 jshn -w
63128 2567 ubus call service list { "name": "network" }
63134 2570 jsonfilter -e $.instances
63137 2571 jsonfilter -s { "instance1": { "running": true, "pid": 769, "command": [ "\/sbin\/netifd" ], "term_timeout": 5,
"limits": { "core": "unlimited" }, "respawn": { "threshold": 3600, "timeout": 5, "retry": 5 } } } -e $[*].running
^C
root@OpenWrt:/opt/bpftrace/share/bpftrace/tools#
至此,可以说明我们编译的bpftrace
工具可以正常工作了。