Linux内核bug狩猎指南:从栈跟踪到修复,官方文档教你搞定系统核心故障 电子说
内核是 Linux 系统的 “心脏”—— 一旦它出 bug,小则功能异常,大则系统崩溃、死机。但内核 bug 往往藏在百万行代码中,想快速定位、修复绝非易事。

好在 Linux 内核官方提供了一份详细的《bug 狩猎手册》(kernel.org/doc/html/latest/admin-guide/bug-hunting.html),从识别日志信号到提交修复补丁,全程拆解实用方法。今天我们就基于这份权威文档,梳理一套“内核 bug 排查实操指南”,帮你高效解决内核故障。
当内核遇到 bug 时,不会 “沉默”—— 它会输出栈跟踪(stack dump) 日志,告诉你“哪里出了问题”。这类日志通常分两种场景:
比如文档中给出的例子,内核会明确标出出错的 CPU、进程、代码位置:
------------[ cut here ]------------WARNING: CPU: 1 PID: 28102 at kernel/module.c:1108 module_put+0x57/0x70Modules linked in: dvb_usb_gp8psk(-) dvb_usb dvb_core nvidia_drm(PO) ...CPU: 1 PID: 28102 Comm: rmmod Tainted: P WC O 4.8.4-build.1 #1Hardware name: MSI MS-7309/MS-7309, BIOS V1.12 02/23/2009...Call Trace: # 函数调用栈,记录bug触发时的代码执行路径[] ? dump_stack+0x44/0x64 [] ? __warn+0xfa/0x120 [] ? module_put+0x57/0x70 # 关键:出错函数及偏移 ...
如果 bug 导致内核无法继续运行,会输出带 “BUG” 或 “Oops” 的日志,比如空指针引用:
BUG: unable to handle kernel NULL pointer dereference at (null)IP: [] iret_exc+0x7d0/0xa59 # IP=指令指针,指向出错代码地址 Oops: 0002 [#1] PREEMPT SMP...
日志里“Modules linked in” 后的模块名,会带特殊标记,暗示模块状态:
•(PO):模块处于“待处理” 状态(Pending);
•(-):模块正在卸载;
•(+):模块正在加载;
•Tainted: P WC O:内核被“污染”(比如加载了非开源模块,影响调试)。
想定位 bug,首先得拿到完整的 Oops 日志 —— 内核会把日志存在不同地方,分场景获取:
内核默认通过klogd把日志传给syslogd,存到这些位置:
•传统系统:/var/log/messages(路径由/etc/syslog.conf配置);
•systemd 系统:用journalctl命令查看(比如journalctl -k只看内核日志)。
如果klogd进程意外退出,还能直接读内核缓冲区:
# 把缓冲区日志存到文件dmesg > kernel_bug.log# 或实时读取(按Ctrl+C停止)cat /proc/kmsg > kernel_bug.log
如果机器完全冻住,无法输入命令,试试这 3 招:
•应急方案:手抄屏幕日志(或拍照),重启后整理。若日志滚屏太快,可重启时加vga=791(高分辨率模式)显示更多内容(需开启vesafb驱动,早期启动阶段的 bug 无效);
•提前准备:用串口控制台(参考文档Documentation/admin-guide/serial-console.rst)—— 把两台机器用串口线连接,另一台用 Minicom 等工具捕获日志,适合长期调试;
•专业方案:开启Kdump(内核崩溃转储)—— 提前配置后,崩溃时会通过kexec启动备用内核,从内存中提取日志(具体看Documentation/admin-guide/kdump/gdbmacros.txt)。
拿到 Oops 日志后,下一步是找到 “具体哪行代码出了问题”。文档推荐两种工具,gdb最常用,objdump可备用。
gdb能直接把 Oops 中的内存地址,翻译成 “文件名 + 行号”—— 但前提是内核编译时开启了 **CONFIG_DEBUG_INFO**(调试信息)。
在 kernel 源码目录下,执行命令开启配置:
# 关闭COMPILE_TEST,开启DEBUG_KERNEL和DEBUG_INFO./scripts/config -d COMPILE_TEST -e DEBUG_KERNEL -e DEBUG_INFO# 重新编译内核(生成带调试信息的vmlinux文件)make vmlinux
根据 Oops 日志中的关键信息(EIP地址或函数偏移),用gdb解析:
•情况 1:有 EIP 地址(比如日志中EIP: 0060:[
gdb vmlinux # 加载带调试信息的内核文件(gdb) l *0xc021e50e # 查看该地址对应的代码
•情况 2:有函数偏移(比如日志中EIP is at vt_ioctl+0xda8/0x1482):
gdb vmlinux(gdb) l *vt_ioctl+0xda8 # 查看vt_ioctl函数偏移0xda8的代码# 输出会直接指向文件和行号,比如:# 0x1888 is in vt_ioctl (drivers/tty/vt/vt_ioctl.c:293)
如果 bug 在加载的模块中(比如日志中dvb_usb_adapter_frontend_exit+0x3a/0x70 [dvb_usb]),直接加载模块文件解析:
# 加载dvb-usb模块的.o文件gdb drivers/media/usb/dvb-usb/dvb-usb.o(gdb) l *dvb_usb_adapter_frontend_exit+0x3a # 定位模块内代码
如果没开启CONFIG_DEBUG_INFO,或只有模块文件,可用objdump反汇编代码,间接定位问题。
# -r:显示重定位信息;-S:混合显示源码和汇编;-l:显示行号objdump -r -S -l net/dccp/ipv4.o
如果连源码都没有,可提取 Oops 日志中 “Code:” 后的字节码,手动反汇编:
1.把Code:后的字节(比如44 24 04 e8 6f ...)存到foo.s文件:
.text.globl foofoo:.byte 0x44, 0x24, 0x04, 0xe8, 0x6f, ...
1.编译并反汇编:
gcc -c -o foo.o foo.sobjdump --disassemble foo.o # 查看汇编代码,推断逻辑
1.简化操作:用内核自带脚本scripts/decodecode自动处理(支持多架构)。
定位到 bug 后,若自己无法修复,需向上游报告 —— 关键是 “找对人”,让负责该模块的维护者看到。
内核源码中的scripts/get_maintainer.pl,能直接输出文件的维护者、邮件列表:
# 查看drivers/media/usb/gspca/sonixj.c的维护信息./scripts/get_maintainer.pl --bug -f drivers/media/usb/gspca/sonixj.c
输出结果会包含:
•模块维护者(比如Hans Verkuil
•子系统维护者(比如Mauro Carvalho Chehab
•相关邮件列表(比如linux-media@vger.kernel.org,模块专属列表);
•内核通用列表(linux-kernel@vger.kernel.org)。
•若输出中有“bug reporting URIs”(bug 跟踪链接),优先在跟踪系统提交;
•若无,发送邮件到“模块专属邮件列表”,并抄送维护者;
•完全没头绪时,直接发linux-kernel@vger.kernel.org(通用列表,覆盖所有维护者)。
若你有编程能力,可尝试修复 bug—— 提交补丁时,务必先读Documentation/process/submitting-patches.rst,遵守内核代码规范(比如补丁格式、commit 信息写法),这能大幅提高补丁被接受的概率。
毕竟开源的核心是协作,你的一个小补丁,可能让成千上万台 Linux 机器更稳定~
klogd(内核日志守护进程)是调试的“隐形助手”,注意两点:
1.用1.3-pl3 以上版本的sysklogd包,支持地址自动解析;
2.它会通过两种方式解析地址:
◦静态解析:用System.map文件(内核符号表);
◦动态解析:自动获取加载模块的符号表(支持动态调试模块 bug);
1.模块加载 / 卸载后,可重启klogd刷新符号表(具体看klogd手册)。
内核 bug 调试看似复杂,但只要跟着 “识别日志→获取日志→定位代码→报告 / 修复” 的流程走,再借助gdb、get_maintainer.pl等工具,就能从“无从下手” 变成 “有条理排查”。
这份指南的所有方法都来自内核官方文档,权威且实用—— 下次遇到内核 bug 时,不妨按这个流程试试,说不定你就是解决问题的关键人物~
如果有内核调试的经验,欢迎在评论区分享你的小技巧~
全部0条评论
快来发表一下你的评论吧 !