第四章:KGDB双机调试——内核配置、连接与断点实战

KGDB,说白了就是给Linux内核装了个“调试器”。

你写用户态程序时,GDB怎么用,KGDB就怎么用。只不过这次调试的对象是内核本身。我最早接触KGDB是在一次驱动崩溃的深夜,内核直接panic,printk加了一堆还是定位不到问题。后来同事说:“试试KGDB吧。” 嗯,从那以后,我再也没怕过内核死机。

4.1 KGDB内核配置——先把门打开

要使用KGDB,第一步是让内核支持它。这需要在编译内核时打开相关选项。我个人习惯把下面这些选项全部选上:

Kernel hacking  --->
    [*] KGDB: kernel debugger
    [*]   KGDB: use kgdb over serial console
    [*]   KGDB: support for serial over ethernet (kgdboe)
    [*]   KGDB: internal test suite
    [*]   Compile the kernel with debug info

这里有几个关键点:

  • KGDB: kernel debugger —— 总开关,必须打开
  • KGDB: use kgdb over serial console —— 串口调试必备
  • KGDB: support for serial over ethernet (kgdboe) —— 网络调试,速度更快
  • Compile the kernel with debug info —— 没有调试符号,你断点都打不了
注意: 调试信息会让内核体积变大不少。我曾在嵌入式设备上吃过亏,编译出来的内核直接撑爆了Flash分区。建议调试时用NFS挂载内核,或者单独留一个调试分区。

4.2 串口KGDB连接——最稳的方式

串口调试虽然慢,但胜在稳定。我调试早期启动阶段的代码时,网络还没起来,只能用串口。

配置方法很简单,在bootargs里加上:

kgdboc=ttyS0,115200 kgdbwait

解释一下:

  • kgdboc —— KGDB Over Console,指定串口设备和波特率
  • kgdbwait —— 内核启动时等待调试器连接

目标机启动后,会停在:

Waiting for connection from remote gdb...

这时候,在主机上启动GDB:

gdb vmlinux
(gdb) target remote /dev/ttyUSB0

连接成功后,你就可以看到熟悉的GDB提示符了。

小技巧: 如果串口连接不上,先检查一下物理连接和波特率是否一致。我曾经花了一下午,最后发现是串口线是交叉线还是直连线的问题。嗯,这种坑踩过一次就记住了。

4.3 网络KGDB连接——快,但需要小心

网络KGDB比串口快得多,适合调试大量代码的场景。配置方式:

kgdboe=eth0@192.168.1.100/,@192.168.1.200/

格式是:网卡@本机IP/,@主机IP/

主机端连接:

gdb vmlinux
(gdb) target remote udp:192.168.1.100:6443
注意: 网络KGDB依赖UDP,如果网络环境复杂,丢包会导致调试中断。我建议在调试前先用ping测试一下延迟和丢包率。延迟超过1ms,调试体验就会明显下降。

4.4 断点与单步调试——实战技巧

连接成功后,就可以像调试普通程序一样调试内核了。但有几个地方需要特别注意:

4.4.1 设置断点

(gdb) break do_fork
(gdb) break __do_softirq
(gdb) break my_driver_probe

断点可以打在函数入口,也可以打在具体行号:

(gdb) break drivers/net/phy/phy.c:456

4.4.2 单步执行

(gdb) step    # 进入函数内部
(gdb) next    # 跳过函数调用
(gdb) finish  # 执行完当前函数并返回

这里有个坑:单步执行时,如果遇到中断或调度点,可能会跳到意想不到的地方。 我调试网络驱动时,单步执行到spin_lock,结果直接跳到了调度器里。嗯,这是因为内核在等待锁时发生了上下文切换。

避坑指南: 我曾经在调试一个USB驱动时,单步执行到msleep(),结果等了10秒才回来。后来我学乖了,遇到这类函数直接用next跳过,或者把断点打在函数返回处。

4.4.3 查看内核状态

(gdb) print current->pid
(gdb) print current->comm
(gdb) info registers
(gdb) bt          # 查看调用栈

查看调用栈是我最常用的命令。内核崩溃时,bt能直接告诉你死在哪个函数里。

4.5 知识体系图

下面这张图总结了KGDB双机调试的核心流程:

KGDB双机调试知识体系 目标机(Target) 主机(Host) 内核配置 CONFIG_KGDB=y CONFIG_KGDB_SERIAL_CONSOLE=y CONFIG_DEBUG_INFO=y 连接方式 串口: kgdboc=ttyS0,115200 网络: kgdboe=eth0@IP/ 调试操作 设置断点 单步执行 查看调用栈 查看寄存器 GDB调试器 gdb vmlinux target remote /dev/ttyUSB0 target remote udp:IP:6443 常用命令 break / step / next print / bt / info reg continue / finish 调试技巧 跳过msleep/spin_lock 断点打在函数返回处 使用条件断点 串口/网络连接

4.6 实战经验总结

场景 推荐方式 注意事项
早期启动阶段调试 串口KGDB 网络还没起来,只能用串口
驱动功能调试 网络KGDB 速度快,适合大量单步操作
中断上下文调试 串口KGDB 网络中断可能导致调试中断
性能问题定位 网络KGDB + 条件断点 避免频繁中断影响性能
核心要点:
  • KGDB是内核调试的“最后手段”,当printk不够用时,用它
  • 串口稳但慢,网络快但不稳,根据场景选择
  • 单步执行时注意避开调度点和延时函数
  • 调试信息必须打开,否则断点打不上去

说实话,KGDB用熟了之后,你会发现内核调试其实没那么可怕。我见过很多工程师一遇到内核崩溃就慌了,其实只要把KGDB搭好,一步步跟进去,问题总能找到。

嗯,这一章的内容就到这里。记住,调试工具只是手段,真正重要的是你对内核代码的理解。工具用得再好,看不懂代码也是白搭。


公众号:蓝海资料掘金营,微信deep3321