前言
WireGuard® 是 Linux 的内核模块之一(主流发行版都已内置)。
最近会通过调试的方式来理解 WireGuard 的代码,本文是对这个过程(调试 WireGuard 内核模块源码)的记录。
挑战
- 内核(含调试信息)如何构建
- QEMU 如何启动调试环境
- GDB 如何进行远程调试
- VSCode 如何整合图形化调试流程
内容
思路
- 基于最新的 WireGuard 内核模块代码构建出含有 DEBUG symbol 的 Linux 内核
- 通过 QEMU 和内核(上一步构建出来的)启动虚拟机
- 通过 GDB 进行远程调试
准备
本地开发环境需要满足的条件:
操作
克隆 WireGuard 内核模块源码到本地(实际上就是含有最新 WireGuard 修改的内核源码):
git clone git://git.zx2c4.com/wireguard-linux
构建内核(需要等一会,我本地构建一次需 3 分钟)命令如下:
cd wireguard-linux
# 为了确保后面的操作体验一致,这里我默认所有操作基于:fa41884c1c6d commit
git checkout fa41884c1c6d
DEBUG_KERNEL=yes ARCH=x86_64 make -C tools/testing/selftests/wireguard/qemu build -j$(nproc)
如何你对手动构建内核感兴趣,可以看看 Arch Linux Wiki。 源码仓库中的 Makefile 封装了构建内核所需的所有操作, 上面的命令直接调用仓库中的 Makefile 来构建含有调试信息的内核。
构建成功会看到如下页面:
通过 QEMU 和该内核,在虚拟化内启动调试环境,命令如下:
# bzImage 是内核镜像,通过上面的构建命令产生
# 这里为了兼容项目中的测试样例(即具备:调试实际流量收发过程的能力),因此启动命令会比较复杂
qemu-system-x86_64 \
-nodefaults \
-nographic \
-no-reboot \
-m 1G -smp 4 \
-monitor none \
-serial stdio \
-cpu host -machine microvm,accel=kvm,pit=off,pic=off,rtc=off,acpi=off \
-S -gdb tcp::12345,server,nowait -append nokaslr \
-chardev file,path=tools/testing/selftests/wireguard/qemu/build/x86_64/result,id=result \
-device virtio-serial-device \
-device virtserialport,chardev=result \
-kernel tools/testing/selftests/wireguard/qemu/build/x86_64/kernel-debug/arch/x86_64/boot/bzImage
通过 GDB 发起调试,命令如下:
# vmlinux 是含有 DEBUG symbol 的内核文件,通过上面的构建命令产生
gdb tools/testing/selftests/wireguard/qemu/build/x86_64/kernel-debug/vmlinux
在 GDB 交互式窗口内,连接远程调试环境,打上断点后开始调试:
target remote localhost:12345
hbreak wg_mod_init
continue
效果如下:
操作见视频:
我本地使用了 GEF 来增强 GDB 的交互,比如配色和自动上下文(显示当前行)。
VSCode
下面是关于如何在 VSCode3 内调试 WireGuard 内核模块的操作。
需要安装 C/C++ 的官方插件,构建内核(参考上文)并在仓库根目录下的 .vscode
目录内,创建 VSCode 所需的任务配置和启动调试配置。
创建:.vscode/tasks.json
文件,内容如下:
{
"version": "2.0.0",
"tasks": [
{
"label": "Start QEMU",
"type": "shell",
"command": [
"qemu-system-x86_64",
"-nodefaults",
"-nographic",
"-no-reboot",
"-m", "1G", "-smp", "4",
"-monitor", "none", "-serial", "stdio",
"-cpu", "max", "-machine", "microvm,acpi=off",
"-S", "-gdb", "tcp::1234,server,nowait", "-append", "nokaslr",
"-chardev", "file,path=tools/testing/selftests/wireguard/qemu/build/x86_64/result,id=result",
"-device", "virtio-serial-device", "-device", "virtserialport,chardev=result",
"-kernel", "tools/testing/selftests/wireguard/qemu/build/x86_64/kernel-debug/arch/x86_64/boot/bzImage"
],
// isBackground 将当前任务作为 launch 的 preLaunchTask
"isBackground": true,
"problemMatcher": [],
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": true,
"close": true
}
},
]
}
相对于命令行调试方式的 QEMU 命令,VSCode 的 QEMU 命令会有一点不同,主要是在 -cpu
和 -machine
上,精简了部分配置(否则会导致无法进行 VSCode 的图形界面调试)。
创建:.vscode/launch.json
,内容如下:
{
"version": "0.2.0",
"configurations": [
{
"name": "Start WireGuard debugging",
"type": "cppdbg",
"request": "launch",
"preLaunchTask": "Start QEMU",
"program": "${workspaceFolder}/tools/testing/selftests/wireguard/qemu/build/x86_64/kernel-debug/vmlinux",
"miDebuggerServerAddress": "localhost:1234",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerArgs": "-n",
"setupCommands": [
{
"description": "Load kernel GDB scripts",
"text": "add-auto-load-safe-path ${workspaceFolder}/tools/testing/selftests/wireguard/qemu/build/x86_64/kernel-debug/",
"ignoreFailures": true
},
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
}
]
}
然后发起调试即可。
效果如下:
操作见视频:
总结
以上就是我基于 GDB 调试 WireGuard 内核源码的相关操作。
如果你发现本文中存在任何语法错误,欢迎通过 主页 中 Email 联系我,另外你也可以在 关于 里面找到我的微信二维码。
参考
-
我本地的开发环境是 Arch Linux,参考了 Arch Linux 官方包的依赖信息,如果你用的是其他发行版,请根据发行版官方文档安装对应的依赖包。 ↩︎
-
虚拟化嵌套即在虚拟机内创建(运行)虚拟机。比如你在云服务器上调试,就需要确保云服务器(本质上是台虚拟机)也可以创建虚拟机,即在虚拟机内创建虚拟机。 ↩︎
-
我本地的 VSCode 通过 Remote SSH 方式运行,实际的开发环境是在 Arch Linux 虚拟机上。VSCode Remote-SSH 插件,允许开发者将 VSCode 运行在服务器上(实际的代码和开发环境都在服务器上),本地的 VSCode 只是一个用于交互的 Client 端。 ↩︎