Contents

在 WSL 上使用 eBPF

1. 什么是 WSL

WSL(Windows Subsystem for Linux) 可以理解为 Windows 为 Linux 提供的一个子系统。在Win系统上,对于基本的业务开发,WSL可以满足我们对 Linux 操作系统的基本需求,而不是重新装双系统。一些开发诉求:

  • 特定的 Linux 发行版(Ubuntu or Debian)
  • 更友好的命令行界面及工具(cat, ls)
  • 运行在 Linux 环境中的各种软件(GCC, vim, ssh)

WSL 已经发展到第二代了(官方称 WSL2,本文也使用的是 WSL2,但为了表述方便,后文都简称为 WSL)。它拥有完整的 Linux 内核,几乎就是一个独立的虚拟机了。

但是 WSL 也没有完全基于社区版的 Linux 内核,也是进行了一些定制化魔改,所以在一些功能上还是有些区别。例如,在构建 eBPF 程序时,就会遇到这个错误:

modprobe: FATAL: Module kheaders not found in directory /lib/modules/xxx

另外, WSL 默认也没有开启 BTF 这个内核选项,在使用CO,RE的特性时也会有一点问题。

写文章时,内核版本为 5.10.6 的 WSL 还没有开启BTF选项,后面对 WSL 整体升级后(5.10.102),BTF 选项自动开启了。

要解决上面的问题,就需要重新编译内核了。

2. 编译内核

2.1 获取源码

首先查看自己机器的内核版本,本人的 WSL 基于 Ubuntu。

/posts/2022/06/Snipaste_2022-08-16_23-08-09.jpg

/posts/2022/06/Snipaste_2022-08-16_23-40-24.jpg

然后去官方仓库(https://github.com/microsoft/WSL2-Linux-Kernel/releases)的 release 页面下载和自己内核版本对应的源代码。

解压,进入该目录,得到如下内容(目录名太长了,我改了名)。

/posts/2022/06/Snipaste_2022-08-16_23-34-12.jpg

1
2
3
4
5
# 编译内核时需要用到
sudo apt install flex bison build-essential libelf-dev libncurses-dev libssl-dev dwarves

# 拷贝一份编译配置文件,开始定制我们需要的功能
cp Microsoft/config-wsl .config

2.2 更改编译选项

如果是高版本内核,一些最基础的eBPF功能默认都是开启的(Kprobe 和 Tracepoint 这类),这里不全部列出了,只列出WSL内核未开启的,以及一些可选高级功能。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
vim .config  # 以下功能,选择性开启

# eBPF TC 相关功能,classifier 和 action
CONFIG_NET_CLS_BPF=y
CONFIG_NET_CLS_ACT=y

# LSM 安全能力,能完成一些函数级别阻断
CONFIG_BPF_LSM=y
CONFIG_LSM="lockdown,yama,loadpin,safesetid,integrity,bpf" # 包含 bpf 即可

# bpf_override_return 函数级别阻断
CONFIG_FUNCTION_ERROR_INJECTION=y
CONFIG_BPF_KPROBE_OVERRIDE=y

# BTF 支持
CONFIG_DEBUG_INFO_BTF=y

# 解决 Module kheaders not found 问题
CONFIG_IKHEADERS=y

修改完后,保存退出,下面开始编译内核。

2.3 编译及模块安装

1
2
3
4
5
export VERSION=$(uname -r)

make -j 8
make modules -j 4
sudo make modules_install 

第一步 make 时间比较久,如果看到下面的结果,就说明编译成功了。若 echo 的结果不为 0,就说明上一条指令异常退出,需要解决错误后重新编译。

/posts/2022/06/Snipaste_2022-08-17_00-15-57.jpg

/posts/2022/06/Snipaste_2022-08-17_00-21-16.jpg

可以在/lib/modules/$(uname -r)下看到安装的一些模块。

/posts/2022/06/Snipaste_2022-08-17_00-23-45.jpg

刚生成的 BTF 信息:

/posts/2022/06/Snipaste_2022-08-17_00-25-20.jpg

/sys/kernel/debug/路径下也有比较重要的信息:trace_pipe,tracepoint 的各种定义等。这个目录默认是空的,我们需要手动挂载一下。

1
sudo mount -t debugfs debugfs /sys/kernel/debug

/posts/2022/06/Snipaste_2022-08-17_00-46-05.jpg

3. 其它工具的安装

bpftool是比较常用的一个工具,可用来基于 libbpf 开发生成 skeleton,查看系统上已加载的 eBPF 程序等。详细的使用技巧可以参考:bpftool-features-thread

内核源码中也有bpftool的源代码,因此直接编译安装是最推荐的做法。

/posts/2022/06/Snipaste_2022-08-17_00-38-33.jpg

1
2
make
sudo make install

/posts/2022/06/Snipaste_2022-08-17_00-38-50.jpg

4. bpftrace 测试

统计一段时间内,各应用执行系统调用的次数。

1
sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'

/posts/2022/06/Snipaste_2022-08-17_00-49-31.jpg

使用 bpftool 来查看刚才加载到内核的 eBPF 程序。 /posts/2022/06/Snipaste_2022-08-17_00-52-58.jpg

这说明WSL下的 eBPF 环境应该是搭建好了,后面就可以在该环境下开发和运行 eBPF 程序了。