4、KGDB双机调试:KGDB内核配置、串口/网络连接设置、调试会话建立与断点管理
KGDB,说白了就是让内核可以被GDB远程调试。你想想看,内核跑在目标机上,调试器跑在开发机上,两边通过串口或网络连起来——这就是双机调试的核心思路。
我最早接触KGDB是在调试一个USB驱动的时候。那驱动一加载就死机,printk打出来的日志根本不够用。后来配了KGDB,单步跟踪进去,才发现是个DMA缓冲区的对齐问题。嗯,从那以后,KGDB就成了我调试内核的标配工具。
4.1 KGDB内核配置
要启用KGDB,第一步就是重新编译内核。我个人习惯在现有内核配置基础上,打开以下几个关键选项:
Kernel hacking --->
[*] KGDB: kernel debugger
[*] KGDB: use kgdb over serial console
[*] KGDB: internal debug console
[*] KGDB: allow debugging with USB debug port
[*] KGDB: allow debugging with ethernet
这里有几个坑,我踩过不止一次:
注意:
KGDB: use kgdb over serial console必须打开,否则串口调试用不了- 如果要用网络调试,
KGDB: allow debugging with ethernet也要勾上,但需要额外的驱动支持 - 调试符号必须保留——编译时加
-g选项,且不要 strip 掉 vmlinux
配置完以后,编译安装新内核:
make -j$(nproc)
make modules_install
make install
重启后,用新内核启动目标机。这时候KGDB其实还没激活,需要你在启动参数里告诉内核:我要进调试模式。
4.2 串口连接设置
串口是最常用的KGDB连接方式。为什么?因为它简单、稳定、不依赖网络协议栈。我在项目里遇到过网络驱动本身就有bug的情况,这时候串口就是唯一的救命稻草。
硬件连接方面,你需要一根交叉串口线(null modem cable),把开发机和目标机的串口连起来。波特率我一般设115200,再高容易丢数据。
目标机的内核启动参数要加上:
kgdboc=ttyS0,115200 kgdbwait
解释一下:
kgdboc指定调试用的串口设备和波特率kgdbwait让内核在启动时等待GDB连接
开发机这边,用minicom或screen打开串口:
screen /dev/ttyUSB0 115200
你会看到目标机内核启动到某个点就停住了,打印出类似这样的信息:
Waiting for connection from remote gdb...
嗯,这时候目标机就在等你了。
4.3 网络连接设置
串口虽然稳,但速度慢。如果你要传大量数据(比如调试带符号的内核模块),串口能急死人。这时候网络调试就派上用场了。
网络KGDB需要内核支持 KGDB: allow debugging with ethernet,并且网卡驱动要支持 polling 模式。为什么不能用中断?因为内核被KGDB停住时,中断是关闭的,只能用轮询方式收发包。
目标机启动参数改成:
kgdboe=@192.168.1.10/,@192.168.1.20/ kgdbwait
格式是 kgdboe=本地IP/本地MAC,远程IP/远程MAC。MAC地址可以省略,让KGDB自己广播发现。
小技巧:
我建议网络调试时,把开发机和目标机用一根直连网线连起来,不要经过交换机。这样能避免很多网络层面的干扰。曾经有一次,交换机上的广播风暴直接把我的KGDB会话冲断了,排查了半天才找到原因。
4.4 调试会话建立
连接建立的过程,我画了张图帮你理解:
具体操作步骤是这样的:
目标机启动后停在 Waiting for connection,这时候在开发机上执行:
gdb vmlinux
(gdb) target remote /dev/ttyS0
如果是网络连接:
gdb vmlinux
(gdb) target remote 192.168.1.10:6443
连接成功后,GDB会打印出目标机的内核版本和当前CPU状态。这时候你就可以开始调试了。
关键点:
vmlinux 必须带调试符号,而且要和目标机上运行的内核完全一致。我见过有人拿错了vmlinux,结果GDB显示的地址全是错的,断点根本打不进去。
4.5 断点管理
断点管理是KGDB调试的核心操作。常用的断点命令和普通GDB一样:
| 命令 | 说明 | 示例 |
|---|---|---|
break |
设置断点 | break do_fork |
break file:line |
按文件行号设断点 | break kernel/fork.c:123 |
hbreak |
硬件断点 | hbreak *0xc0100120 |
watch |
数据断点 | watch my_var |
info breakpoints |
查看所有断点 | info break |
delete |
删除断点 | delete 1 |
disable/enable |
禁用/启用断点 | disable 2 |
这里有个重要的区别:软件断点和硬件断点。软件断点(break)是在指令码里插入断点指令,内核需要能写代码段。硬件断点(hbreak)用的是CPU的调试寄存器,不修改内存。我在调试只读内存区域时,就必须用硬件断点。
实战经验:
我曾经调试一个内核模块的初始化函数,那个函数在 module_init 里注册后就再也没被调用过。我在 init_module 入口设了断点,然后 continue 让内核继续跑。结果断点根本没命中——后来发现是模块加载路径和我想的不一样,函数地址被重定位了。解决办法是用 break module_init 而不是 break init_module,因为前者是内核模块加载器调用的入口。
调试会话的控制命令也很关键:
continue(或c):让内核继续执行step(或s):单步进入函数next(或n):单步跳过函数finish:执行到当前函数返回backtrace(或bt):查看调用栈info registers:查看CPU寄存器
调试过程中,如果目标机死机或者断点打不进去,别慌。先检查连接是否正常,再确认vmlinux版本是否匹配。我遇到过最诡异的一次,是串口线接触不良,导致GDB收发的数据包偶尔丢字节,断点设置命令被截断了。换了一根线就好了。
重要提醒:
KGDB调试时,整个内核是暂停的。这意味着所有进程都停止运行,包括时钟中断、磁盘IO、网络协议栈。如果你在调试文件系统相关的代码,小心别让日志写操作把自己卡死——因为写日志也需要内核服务,而内核正被你停着呢。
嗯,KGDB双机调试的基本配置和操作就这些。串口适合快速上手和网络不可用的情况,网络适合需要传输大量数据的场景。断点管理这块,多练几次就熟了。记住一点:调试内核和调试用户态程序不一样,你停住的是整个系统,所以操作要快,别让硬件看门狗超时复位了目标机。