本文旨在介绍下几种常见的调试方法gdb、crash、kgdb and kdb 以及dynamic debug. 关于在 Linux 内核上使用debuggers,Linus Torvalds 长期以来对它们不太喜欢。简短地解释这种态度是,依赖调试器可能鼓励用权宜之计而非深思熟虑来解决问题,这会导致代码质量恶化。详细解释可以参考https://lwn.net/2000/0914/a/lt-debugger.php3
1. Linux 开发过程中会遇到的问题
• Oops:错误报告,可能导致系统不稳定。
• Kernel Crash:严重错误导致的系统完全崩溃。
• Panic:严重错误,系统停止运行,通常需要重启。
• OOM:内存耗尽,触发 OOM Killer。
1.1 Oops
• 定义:Oops 是 Linux 内核中的一种错误报告,它发生在内核检测到某些违反系统完整性的问题时。通常,这些问题包括非法内存访问、使用未初始化的内存、空指针解引用等。
• 影响:发生 Oops 后,内核会尝试继续运行,但系统的稳定性可能会受到影响,因为已经发生了内存损坏或其他严重的内核错误。
• 处理:内核通常会打印错误信息和内核调用栈到系统日志中,这有助于开发人员诊断和修复问题。
1.2 Kernel Crash
• 定义:Kernel Crash 指的是内核因为严重错误而完全失去功能的情况。它可能是由 Oops 导致的,也可能是由硬件故障、驱动程序错误或其他严重的内核级别问题引起的。
• 影响:当内核崩溃时,系统通常无法继续运行,需要重启。
• 处理:系统管理员需要查看崩溃转储或日志文件来分析原因,并采取措施防止未来发生类似崩溃。
1.3 Panic
• 定义:Kernel Panic 是一种特殊类型的错误,当内核检测到无法恢复的系统错误时触发。这通常表示系统的关键部分已损坏或遇到不可恢复的操作错误。
• 影响:Panic 通常会导致系统完全停止响应,需要重新启动。
• 处理:内核会在控制台输出 panic 相关的信息,包括错误描述和内核调用栈。系统通常需要重启才能恢复。
1.4 OOM (Out of Memory)
• 定义:OOM 错误发生在系统物理内存和交换空间都耗尽时,内核无法满足进程的内存分配请求。
• 影响:当发生 OOM 时,内核会触发 OOM Killer,尝试终止一个或多个进程来释放内存。
• 处理:内核选择杀死占用大量内存但相对不重要的进程。这个决定基于一系列启发式评分算法,以最小化对系统整体运行的影响。
2. Linux中常用的调试(debuggers)
2.1 gdb
代码语言:bash复制gdb /boot/vmlinux /proc/kcore
当使用上面的命令的时候,实际上是进行的事后调试Post-mortem Debugging
其中第一个参数是当前运行的未压缩的内核。要调试的内核必须用-g选线编译并且获得调试信息。vmlinuz 是 vmlinux 的压缩版本,添加了自解压头部,使其可以自我解压并执行。
上面的命令需要在编译内核的时候打开下面的选项,其实也就是CONFIG_DEBUG_INFO
代码语言:txt复制Kernel hacking --->
[*] Compile the kernel with debug info
/proc/kcore 是一个虚拟文件,提供了对当前运行系统物理内存的映射,其格式模仿了一个核心转储(core dump)。虽然 /proc/kcore 表现得像是一个内存转储文件,但它实际上是一个实时的视图,反映了当前系统的内存状态。
2.2 crash
使用 crash 工具来分析 Linux 内核崩溃是一个强大的方法,它可以帮助你理解内核崩溃时的状态,包括堆栈跟踪、内存状态、寄存器内容等。crash 主要用于分析由 kdump 服务生成的内核崩溃转储(vmcore 文件)。以下是如何设置和使用 crash 的步骤和示例:
代码语言:bash复制sudo apt install kdump-tools crash
sudo systemctl enable kdump
sudo systemctl start kdump
#trigger crash for test purpose
echo c > /proc/sysrq-trigger
sudo crash /path/to/vmlinux /path/to/vmcore
在 crash 环境中,你可以执行多种命令来分析崩溃:
代码语言:bash复制 bt:显示当前 CPU 或特定进程的堆栈跟踪。
ps:显示系统中的进程状态。
vm:查看内存信息。
log:显示内核日志。
例如,要获取当前环境的堆栈跟踪,可以运行:
bt
假设系统因为某个驱动错误而崩溃,已经通过上述步骤获得了 vmcore 文件。现在,可以使用 crash 来分析驱动中可能的错误位置,检查在崩溃时的函数调用堆栈,以及查看那时的内存状态和变量。
通过这样的分析,可以精确地定位到问题发生的代码行,从而更有针对性地解决问题。此外,分析内核日志(通过 log 命令)可以帮助了解crash前发生了什么,这对于理解错误的上下文非常有帮助。
2.3 kgdb
KGDB 适合深入的远程内核调试,而 KDB 更适合快速本地访问和简单问题的诊断。两者的使用依赖于具体的调试需求和环境设置。
kgdb的使用步骤如下:
2.3.1 准备内核
代码语言:txt复制Kernel hacking --->
<*> KGDB: kernel debugger --->
[*] KGDB: use kgdb over the serial console
[ ] KGDB: internal test suite
[ ] KGDB_KDB: include kdb frontend for kgdb
[ ] KGDB over Ethernet
2.3.2 添加启动参数
代码语言:txt复制kgdboc=ttyS0,115200 kgdbwait
2.3.3 调试机(host)上启动gdb作为前端
代码语言:bash复制gdb /path/to/vmlinux
2.3.4 设置远程调试目标:
代码语言:bash复制(gdb) target remote /dev/ttyS0
一旦连接成功,可以使用 GDB 的各种命令来进行断点设置、单步执行、变量检查等调试任务。
2.4 kdb
KDB 是内核内置的调试器,可以通过键盘直接激活
2.4.1 准备内核
代码语言:txt复制Kernel hacking --->
<*> Kernel debugging --->
<*> Kernel debugger (KDB)
<*> Support for kgdb over the serial console
2.4.2 启动配置了KDB支持的内核,无需额外启动参数
2.4.3 激活KDB
通过触发系统崩溃(如 Magic SysRq 键组合)或通过预设断点来激活 KDB。
在键盘上按下 Alt SysRq G 可以激活 KDB。
2.4.4 使用KDB
代码语言:txt复制 在 KDB 提示符下,你可以使用命令来查看堆栈、寄存器、内存等:
bt:查看当前的调用堆栈。
rd:查看寄存器内容。
md:查看内存地址的内容。
2.5 dynamic debug
dynamic debug 无需重新编译内核,可以根据需求打开特定的模块的打印选项。这对于理解和调试内核非常有用。可以参考下面的文档。
2.5.1 准备内核
代码语言:txt复制Kernel hacking --->
[*] Enable dynamic printk() call support
2.5.2 查看可用的动态调试点
代码语言:bash复制cat /sys/kernel/debug/dynamic_debug/control
2.5.3 启用调试命令
代码语言:bash复制echo 'func my_function p' > /sys/kernel/debug/dynamic_debug/control
更多细节请参考动态debug的内核
https://www.kernel.org/doc/html/v4.14/admin-guide/dynamic-debug-howto.html
3. 结束语
通过有效地使用这些工具,Linux 内核开发者可以更有效地定位和解决内核级别的问题。从实时调试复杂的驱动问题(使用 KGDB)到快速查看系统状态(使用 KDB),或者动态调整调试输出(使用 Dynamic Debug),这些工具为我们提供了强大的支持。随着技术的进步和内核的发展,这些调试方法将继续发挥关键作用,帮助开发者优化内核性能和稳定性。