eBPF的大部分实现都在Linux内核当中,uBPF项目在用户空间实现了eBPF虚拟机,可以在用户空间模拟执行eBPF程序,添加断点,帮助开发者调试程序。eBPF虚拟机运行在内核中,但是内核可以通过GDB进行调试,因此eBPF虚拟机也可以通过GDB进行调试。
在内核调试eBPF VM有什么益处吗?
调试可以很好的帮助理解其工作原理。
开发者编写的程序有时无法加载进内核,调试可以帮助定位问题。
本文记录了踩完众多坑的搭建环境过程,供需要的人进行参考。
所需环境
内核镜像
通过下载内核源码编译构建内核镜像文件,源码可以通过Linux官方网站,或者清华镜像网站选择需要的版本下载。本文采用了5.4.1版本内核。
编译内核镜像的步骤可以参考之前的文章,QEMU调试Linux内核环境搭建。
根文件系统
使用buildroot工具,构建根文件系统。从git仓库下载源码,也可以从官网下载并解压。
1 | git clone git://git.buildroot.net/buildroot /source/buildroot |
在buildroot需要进行几个关键的配置,才能在虚拟机中运行bpf程序。
- Target options
架构选择为x86_64,如下图。
Toolchain
编译工具链的配置是能否在qemu虚拟机中运行ebpf程序的关键。Kernel
buildroot也支持下载内核源码并编译内核镜像,建议勾不勾选,使用自己下载的内核。
Target packages
- Debugging, profiling and benchmark
该目录下勾选bpftool和libbpf,其他软件根据需要勾选,建议勾选strace,可以结合strace学习bpf工作原理,同时加载程序出错时也能看到更多的错误信息。
- Libraries → Other
勾选clang和llvm、BPF backend。
- Networking applications
添加对ssh的支持,方便宿主机往虚拟机里面传输编译完成的ebpf程序。
- Filesystem images
选择ext4 root filesystem,exact size改成256M,取消选择f2fs root filesystem。
- Bootloaders
取消U-Boot,可以加快编译速度。
最后别忘了选择Save保存到.config文件。
在make的过程中,buildroot需要从国外网站下载软件包,速度很慢,如果观察到下载速度慢,可以复制窗口日志中的下载地址,从其他渠道下载完成后放到buildroot/dl/目录下,再次make就能够直接从dl目录解压了。
make完成后,会在buildroot/output/images目录下生成rootfs.ext4文件。
eBPF程序
编写一个简单打印hello world的eBPF测试程序bpf_program.c,挂载在ext4_file_write_iter函数。
1 |
|
用户态加载程序loader.c
1 |
|
Makefile文件,使用sed命令替换为自己的内核源码路径。
1 | sed -i 's;/kernel-src;/home/szp/linux-5.4.1;' ./Makefile |
1 | CLANG = clang |
在宿主机编译eBPF代码,生成bpf_program.o文件和monitor-exec文件。
GDB
使用vscode可以将gdb调试可视化,推荐使用。所需插件
- Remote-SSH
- C/C++
- GDB Debug
使用远程资源管理器打开内核源码根目录,在“运行(Debug)”菜单下添加配置如下:
1 | { |
启动调试即可远程连接GDB进行调试。
启动QEMU
进入内核源码根目录下,使用buildroot构建的根文件系统启动QEMU虚拟机,将虚拟机ssh端口映射到宿主机2222。
1 | sudo qemu-system-x86_64 -kernel arch/x86/boot/bzImage --enable-kvm -s -smp 1 -nic user,hostfwd=tcp::2222-:22 -boot c -m 2049M -hda ../buildtool/output/images/rootfs.ext4 -append "root=/dev/sda rw console=ttyS0,115200 acpi=off nokaslr" -serial stdio -display none |
使用root用户登录虚拟机,配置网卡与ip地址:
1 | ifconfig eth0 10.0.2.15 |
修改/etc/ssh/sshd_config文件,将如下两项改为yes:
1 | PermitRootLogin yes |
现在就可以使用ssh登录该虚拟机了:
1 | ssh -p 2222 root@127.0.0.1 |
使用scp将bpf可执行文件拷贝到虚拟机中。
执行程序
在kernel/bpf/syscall.c:bpf_prog_load()函数中添加断点,启动GDB调试并运行bpf程序,就可以打印变量,进行调试。虽然内核优化掉了很多变量,但是还是可以通过现有的变量和函数调用栈直观的学习bpf程序的加载过程,map初始化过程。
参考资料:
https://youtu.be/W6rgaghycFI