前言

WireGuard® 是 Linux 的内核模块之一(主流发行版都已内置)。

最近会通过调试的方式来理解 WireGuard 的代码,本文是对这个过程(调试 WireGuard 内核模块源码)的记录。

挑战

  1. 内核(含调试信息)如何构建
  2. QEMU 如何启动调试环境
  3. GDB 如何进行远程调试
  4. VSCode 如何整合图形化调试流程

内容

思路

  1. 基于最新的 WireGuard 内核模块代码构建出含有 DEBUG symbol 的 Linux 内核
  2. 通过 QEMU 和内核(上一步构建出来的)启动虚拟机
  3. 通过 GDB 进行远程调试

准备

本地开发环境需要满足的条件:

  1. 安装了构建内核所需依赖包1
  2. 安装了虚拟化所需 QEMU 包
  3. 安装了调试所需 GDB 包
  4. 当前开发环境支持虚拟化(如果是虚拟机,需确保当前环境支持虚拟化嵌套2

操作

克隆 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 来构建含有调试信息的内核。

构建成功会看到如下页面:

build-kernel-success.png

通过 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

效果如下:

wireguard-gdb-command-line-debug.png

操作见视频:

我本地使用了 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
          }
      ]
    }
  ]
}

然后发起调试即可。

效果如下:

vscode-debug-wireguard-operation.png

操作见视频:

总结

以上就是我基于 GDB 调试 WireGuard 内核源码的相关操作。

如果你发现本文中存在任何语法错误,欢迎通过 主页 中 Email 联系我,另外你也可以在 关于 里面找到我的微信二维码。

参考


  1. 我本地的开发环境是 Arch Linux,参考了 Arch Linux 官方包的依赖信息,如果你用的是其他发行版,请根据发行版官方文档安装对应的依赖包。 ↩︎

  2. 虚拟化嵌套即在虚拟机内创建(运行)虚拟机。比如你在云服务器上调试,就需要确保云服务器(本质上是台虚拟机)也可以创建虚拟机,即在虚拟机内创建虚拟机。 ↩︎

  3. 我本地的 VSCode 通过 Remote SSH 方式运行,实际的开发环境是在 Arch Linux 虚拟机上。VSCode Remote-SSH 插件,允许开发者将 VSCode 运行在服务器上(实际的代码和开发环境都在服务器上),本地的 VSCode 只是一个用于交互的 Client 端。 ↩︎