调试技巧

本文分享一些高级的调试技巧, 不使用纯 gdb 命令行而是在 vscode 配合 c++ 插件更好的可视化调试

.gdbinit

.gdbinit 是 GDB 的初始化文件,它用于在启动 GDB 时自动执行一些预定义的命令.这个文件可以包含用户自定义的设置、命令别名、断点、脚本等,方便调试环境的个性化配置

由于 .gdbinit 文件可以执行任意 GDB 命令,因此它具有很高的权限,可能被用于执行恶意命令.为了安全起见,GDB 默认会限制从当前工作目录加载 .gdbinit 文件. 因此首先创建 ~/.config/gdb/gdbinit 赋予安全权限

set auto-load safe-path /

linux kernel 提供了一个脚本用于调试

add-auto-load-safe-path ./scripts/gdb/vmlinux-gdb.py

gdb 还有一些其他的配置选项, 您可以创建 .gdbinit 文件(参考 xv6) 来辅助调试

使用 python 辅助调试 gdb 参见 python-gdb

监控变量

默认调试时只会显示当前局部变量, 可以直接在 vscode 左侧监控填入变量名字

默认变量是以 10 进制显示, 如果希望以 2 进制或者以 16 进制查看可以修改为

var,b
var,x

如果变量是一个数组, 可以手动标注它的类型展示每一个元素

*(u8 *)numa_distance@10

20241013144801

sudo

如果调试的程序需要 sudo 权限去读取某些内核文件, 可以先调试一下, 此时会打开一个调试的 cppdbg 的终端, 在此终端中输入 su 进入 root 用户, 再次点击调试即可

20241016231056

调试可执行文件

当前 gdb 调试的对象是操作系统内核的代码, 如果我们希望执行一个可执行文件, 并且在可执行文件的代码中也加入断点

以下述代码为例, 其中使用到了 fork 多进程, 代码逻辑比较简单, 就是三个进程随机打印 ABC

#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sched.h>
#include <unistd.h>

char bar[3968] = "\n";
char foo[4096] = "this is not a test\n";

void output_loop(char *str)
{
    int i;
    for (i = 0; i < 20; i++) {
        write(2, str, strlen(str));
        sched_yield();
    }
}

int main()
{
    int pid1, pid2, status;

    write(2, foo, strlen(foo));
    strcpy(foo, "you are modified\n");
    write(2, foo, strlen(foo));

    if (!(pid1 = fork())) {
        output_loop("B  ");
        exit(0);
    }

    if (!(pid2 = fork())) {
        output_loop("C  ");
        exit(0);
    }

    output_loop("A  ");
    waitpid(pid1, &status, 0);
    waitpid(pid2, &status, 0);
    write(2, "\n", 1);

    while (1)
        ;

    exit(0);
}

使用静态链接编译, 并加上调试选项 -g

gcc -static -g a.c -o a

使用 readelf 找到该文件的 .text 段的虚拟地址, 即 0x4011c0

$ readelf -S a | grep .text
  [ 7] .text             PROGBITS         00000000004011c0  000011c0

将该可执行文件拷贝到磁盘镜像当中, 比如前文的 initramfs/bin 下, 或者 mount 磁盘然后 copy 过去

修改 .gdbinit, 其中 add-symbol-file 表示添加一个文件, 然后跟一个代码段起始地址, 然后我们在 23 行(即write)的位置打一个断点

注意不要在 main 上打断点, 停不住

add-auto-load-safe-path ./scripts/gdb/vmlinux-gdb.py

add-symbol-file /home/kamilu/workspace/a 0x4011c0
b a.c:23

启动 qemu, 执行 /bin/a 即可发现程序停在 23 行的位置了

20240919162236

调试 32 位内核

调试 32 位 x86 内核的情况, 需要指定 archi 为 i386, 添加 gdb 的配置信息

# .gdbinit
set archi i386:x86-64

修改 launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "Set debuggee architecture",
                    "text": "set archi i386:x86-64",
                    "ignoreFailures": true
                }
            ]
        }
    ]
}

targetArchitecture in launch.json not working

调试模块

参考

zood