Linux内核bug狩猎指南:从栈跟踪到修复,官方文档教你搞定系统核心故障

电子说

1.4w人已加入

描述

 

 

内核是 Linux 系统的 心脏”—— 一旦它出 bug,小则功能异常,大则系统崩溃、死机。但内核 bug 往往藏在百万行代码中,想快速定位、修复绝非易事。

Linux

好在 Linux 内核官方提供了一份详细的《bug 狩猎手册》(kernel.org/doc/html/latest/admin-guide/bug-hunting.html),从识别日志信号到提交修复补丁,全程拆解实用方法。今天我们就基于这份权威文档,梳理一套内核 bug 排查实操指南,帮你高效解决内核故障。

 

 

一、先看懂内核的求救信号:栈跟踪与 Oops 信息

 

当内核遇到 bug 时,不会 沉默”—— 它会输出栈跟踪(stack dump 日志,告诉你哪里出了问题。这类日志通常分两种场景:

 

 

1. 常见的 警告型” 日志(WARNING

 

比如文档中给出的例子,内核会明确标出出错的 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  # 关键:出错函数及偏移...

2. 严重的 崩溃型” 日志(Oops/BUG

 

如果 bug 导致内核无法继续运行,会输出带 “BUG” 或 “Oops” 的日志,比如空指针引用:

 

 

  •  
  •  
  •  
  •  
BUG: unable to handle kernel NULL pointer dereference at   (null)IP: [] iret_exc+0x7d0/0xa59  # IP=指令指针,指向出错代码地址Oops: 0002 [#1] PREEMPT SMP...

3. 日志中的 模块标记” 要注意

 

日志里“Modules linked in” 后的模块名,会带特殊标记,暗示模块状态:

 

 

(PO):模块处于待处理” 状态(Pending);

 

 

(-):模块正在卸载;

 

 

(+):模块正在加载;

 

 

Tainted: P WC O:内核被污染(比如加载了非开源模块,影响调试)。

 

 

二、别让关键日志溜走:找到 Oops 信息的 种场景

 

想定位 bug,首先得拿到完整的 Oops 日志 —— 内核会把日志存在不同地方,分场景获取:

 

 

1. 系统还能操作:从常规日志文件拿

 

内核默认通过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

2. 系统崩溃卡死:种 救命” 方法

 

如果机器完全冻住,无法输入命令,试试这 3 招:

 

 

应急方案:手抄屏幕日志(或拍照),重启后整理。若日志滚屏太快,可重启时加vga=791(高分辨率模式)显示更多内容(需开启vesafb驱动,早期启动阶段的 bug 无效);

 

 

提前准备:用串口控制台(参考文档Documentation/admin-guide/serial-console.rst—— 把两台机器用串口线连接,另一台用 Minicom 等工具捕获日志,适合长期调试;

 

 

专业方案:开启Kdump(内核崩溃转储)—— 提前配置后,崩溃时会通过kexec启动备用内核,从内存中提取日志(具体看Documentation/admin-guide/kdump/gdbmacros.txt)。

 

 

三、核心步骤:精准定位 bug 的代码行

 

拿到 Oops 日志后,下一步是找到 具体哪行代码出了问题。文档推荐两种工具,gdb最常用,objdump可备用。

 

 

1. 首选工具:gdb(需开启调试信息)

 

gdb能直接把 Oops 中的内存地址,翻译成 文件名 行号”—— 但前提是内核编译时开启了 **CONFIG_DEBUG_INFO**(调试信息)。

 

 

步骤 1:开启内核调试信息

 

 kernel 源码目录下,执行命令开启配置:

 

 

  •  
  •  
  •  
  •  
# 关闭COMPILE_TEST,开启DEBUG_KERNEL和DEBUG_INFO./scripts/config -d COMPILE_TEST -e DEBUG_KERNEL -e DEBUG_INFO# 重新编译内核(生成带调试信息的vmlinux文件)make vmlinux

步骤 2:用 gdb 定位代码

 

根据 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)

步骤 3:模块级定位

 

如果 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  # 定位模块内代码

2. 备用工具:objdump(无调试信息也能用)

 

如果没开启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 0x440x240x040xe80x6f, ...  # 替换成实际字节

1.编译并反汇编:

 

 

  •  
  •  
gcc -c -o foo.o foo.sobjdump --disassemble foo.o  # 查看汇编代码,推断逻辑

1.简化操作:用内核自带脚本scripts/decodecode自动处理(支持多架构)。

 

 

四、报告 bug:让维护者快速接手

 

定位到 bug 后,若自己无法修复,需向上游报告 —— 关键是 找对人,让负责该模块的维护者看到。

 

 

1. 用脚本找维护者:get_maintainer.pl

 

内核源码中的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)。

 

 

2. 报告优先级:先 bug tracker,再邮件

 

若输出中有“bug reporting URIs”bug 跟踪链接),优先在跟踪系统提交;

 

 

若无,发送邮件到模块专属邮件列表,并抄送维护者;

 

 

完全没头绪时,直接发linux-kernel@vger.kernel.org(通用列表,覆盖所有维护者)。

 

 

五、修复 bug:从定位到提交的最后一步

 

若你有编程能力,可尝试修复 bug—— 提交补丁时,务必先读Documentation/process/submitting-patches.rst,遵守内核代码规范(比如补丁格式、commit 信息写法),这能大幅提高补丁被接受的概率。

 

 

毕竟开源的核心是协作,你的一个小补丁,可能让成千上万台 Linux 机器更稳定~

 

 

最后:klogd 的小技巧

 

klogd(内核日志守护进程)是调试的隐形助手,注意两点:

 

 

1.1.3-pl3 以上版本sysklogd包,支持地址自动解析;

 

 

2.它会通过两种方式解析地址:

 

 

静态解析:用System.map文件(内核符号表);

 

 

动态解析:自动获取加载模块的符号表(支持动态调试模块 bug);

 

 

1.模块加载 / 卸载后,可重启klogd刷新符号表(具体看klogd手册)。

 

 

总结

 

内核 bug 调试看似复杂,但只要跟着 识别日志获取日志定位代码报告 修复” 的流程走,再借助gdbget_maintainer.pl等工具,就能从无从下手” 变成 有条理排查

 

 

这份指南的所有方法都来自内核官方文档,权威且实用—— 下次遇到内核 bug 时,不妨按这个流程试试,说不定你就是解决问题的关键人物~

 

 

如果有内核调试的经验,欢迎在评论区分享你的小技巧~

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分