Bug hunting

内核错误报告通常带有如下所示的堆栈转储

------------[ cut here ]------------
WARNING: CPU: 1 PID: 28102 at kernel/module.c:1108 module_put+0x57/0x70
Modules linked in: dvb_usb_gp8psk(-) dvb_usb dvb_core nvidia_drm(PO) nvidia_modeset(PO) snd_hda_codec_hdmi snd_hda_intel snd_hda_codec snd_hwdep snd_hda_core snd_pcm snd_timer snd soundcore nvidia(PO) [last unloaded: rc_core]
CPU: 1 PID: 28102 Comm: rmmod Tainted: P        WC O 4.8.4-build.1 #1
Hardware name: MSI MS-7309/MS-7309, BIOS V1.12 02/23/2009
 00000000 c12ba080 00000000 00000000 c103ed6a c1616014 00000001 00006dc6
 c1615862 00000454 c109e8a7 c109e8a7 00000009 ffffffff 00000000 f13f6a10
 f5f5a600 c103ee33 00000009 00000000 00000000 c109e8a7 f80ca4d0 c109f617
Call Trace:
 [<c12ba080>] ? dump_stack+0x44/0x64
 [<c103ed6a>] ? __warn+0xfa/0x120
 [<c109e8a7>] ? module_put+0x57/0x70
 [<c109e8a7>] ? module_put+0x57/0x70
 [<c103ee33>] ? warn_slowpath_null+0x23/0x30
 [<c109e8a7>] ? module_put+0x57/0x70
 [<f80ca4d0>] ? gp8psk_fe_set_frontend+0x460/0x460 [dvb_usb_gp8psk]
 [<c109f617>] ? symbol_put_addr+0x27/0x50
 [<f80bc9ca>] ? dvb_usb_adapter_frontend_exit+0x3a/0x70 [dvb_usb]
 [<f80bb3bf>] ? dvb_usb_exit+0x2f/0xd0 [dvb_usb]
 [<c13d03bc>] ? usb_disable_endpoint+0x7c/0xb0
 [<f80bb48a>] ? dvb_usb_device_exit+0x2a/0x50 [dvb_usb]
 [<c13d2882>] ? usb_unbind_interface+0x62/0x250
 [<c136b514>] ? __pm_runtime_idle+0x44/0x70
 [<c13620d8>] ? __device_release_driver+0x78/0x120
 [<c1362907>] ? driver_detach+0x87/0x90
 [<c1361c48>] ? bus_remove_driver+0x38/0x90
 [<c13d1c18>] ? usb_deregister+0x58/0xb0
 [<c109fbb0>] ? SyS_delete_module+0x130/0x1f0
 [<c1055654>] ? task_work_run+0x64/0x80
 [<c1000fa5>] ? exit_to_usermode_loop+0x85/0x90
 [<c10013f0>] ? do_fast_syscall_32+0x80/0x130
 [<c1549f43>] ? sysenter_past_esp+0x40/0x6a
---[ end trace 6ebc60ef3981792f ]---

此类堆栈跟踪提供了足够的信息来识别内核源代码中发生错误的行。根据问题的严重性,它可能还包含单词 Oops,如此处所示

BUG: unable to handle kernel NULL pointer dereference at   (null)
IP: [<c06969d4>] iret_exc+0x7d0/0xa59
*pdpt = 000000002258a001 *pde = 0000000000000000
Oops: 0002 [#1] PREEMPT SMP
...

尽管是 Oops 或其他类型的堆栈跟踪,但通常需要冒犯的行来识别和处理错误。在本章中,我们将用 “Oops” 指代所有需要分析的堆栈跟踪。

如果内核使用 CONFIG_DEBUG_INFO 编译,则可以使用 file:scripts/decode_stacktrace.sh 来提高堆栈跟踪的质量。

链接的模块

被污染或正在加载或卸载的模块会用“(...)”标记,其中污染标志在 file:被污染的内核 中描述,“正在加载”用 “+” 注释,“正在卸载”用 “-” 注释。

Oops 消息位于何处?

通常,Oops 文本由 klogd 从内核缓冲区读取并交给 syslogd,后者将其写入 syslog 文件,通常为 /var/log/messages(取决于 /etc/syslog.conf)。在使用 systemd 的系统上,它也可能由 journald 守护进程存储,并通过运行 journalctl 命令访问。

有时 klogd 会死掉,在这种情况下,您可以运行 dmesg > file 从内核缓冲区读取数据并保存它。或者你可以 cat /proc/kmsg > file,但是你必须中断才能停止传输,因为 kmsg 是一个“永无止境的文件”。

如果机器崩溃得太严重,以至于您无法输入命令或磁盘不可用,那么您有三种选择

  1. 手动复制屏幕上的文本,并在机器重新启动后键入它。混乱,但如果您没有计划崩溃,这是唯一的选择。或者,您可以用数码相机拍摄屏幕照片 - 不好,但总比没有好。如果消息滚动到控制台顶部之外,您可能会发现以更高的分辨率启动(例如,vga=791)可以让您读取更多文本。(警告:这需要 vesafb,所以对“早期” oops 没有帮助。)

  2. 使用串行控制台启动(请参阅 Documentation/admin-guide/serial-console.rst),运行一个空调制解调器连接到第二台机器,并使用您喜欢的通信程序在那里捕获输出。Minicom 效果很好。

  3. 使用 Kdump(请参阅 Kdump 文档 - 基于 kexec 的崩溃转储解决方案),使用 Documentation/admin-guide/kdump/gdbmacros.txt 中的 dmesg gdbmacro 从旧内存中提取内核环形缓冲区。

查找错误的位置

如果您将错误的位置指向内核源文件,则报告错误的效果最佳。有两种方法可以做到这一点。通常,使用 gdb 更容易,但内核应该预先使用调试信息编译。

gdb

GNU 调试器 (gdb) 是从 vmlinux 文件中找出 OOPS 的确切文件和行号的最佳方法。

gdb 的使用在用 CONFIG_DEBUG_INFO 编译的内核上效果最佳。可以通过运行来设置

$ ./scripts/config -d COMPILE_TEST -e DEBUG_KERNEL -e DEBUG_INFO

在用 CONFIG_DEBUG_INFO 编译的内核上,您可以简单地从 OOPS 复制 EIP 值

EIP:    0060:[<c021e50e>]    Not tainted VLI

并使用 GDB 将其转换为人类可读的形式

$ gdb vmlinux
(gdb) l *0xc021e50e

如果您没有启用 CONFIG_DEBUG_INFO,则可以使用 OOPS 中的函数偏移量

EIP is at vt_ioctl+0xda8/0x1482

并重新编译启用了 CONFIG_DEBUG_INFO 的内核

$ ./scripts/config -d COMPILE_TEST -e DEBUG_KERNEL -e DEBUG_INFO
$ make vmlinux
$ gdb vmlinux
(gdb) l *vt_ioctl+0xda8
0x1888 is in vt_ioctl (drivers/tty/vt/vt_ioctl.c:293).
288   {
289           struct vc_data *vc = NULL;
290           int ret = 0;
291
292           console_lock();
293           if (VT_BUSY(vc_num))
294                   ret = -EBUSY;
295           else if (vc_num)
296                   vc = vc_deallocate(vc_num);
297           console_unlock();

或者,如果您想更详细

(gdb) p vt_ioctl
$1 = {int (struct tty_struct *, unsigned int, unsigned long)} 0xae0 <vt_ioctl>
(gdb) l *0xae0+0xda8

您可以改为使用目标文件

$ make drivers/tty/
$ gdb drivers/tty/vt/vt_ioctl.o
(gdb) l *vt_ioctl+0xda8

如果您有调用跟踪,例如

Call Trace:
 [<ffffffff8802c8e9>] :jbd:log_wait_commit+0xa3/0xf5
 [<ffffffff810482d9>] autoremove_wake_function+0x0/0x2e
 [<ffffffff8802770b>] :jbd:journal_stop+0x1be/0x1ee
 ...

这表明问题很可能出在 :jbd: 模块中。您可以在 gdb 中加载该模块并列出相关代码

$ gdb fs/jbd/jbd.ko
(gdb) l *log_wait_commit+0xa3

注意

您也可以对堆栈跟踪中的任何函数调用执行相同的操作,例如这个

[<f80bc9ca>] ? dvb_usb_adapter_frontend_exit+0x3a/0x70 [dvb_usb]

可以使用以下命令查看上述调用的发生位置

$ gdb drivers/media/usb/dvb-usb/dvb-usb.o
(gdb) l *dvb_usb_adapter_frontend_exit+0x3a

objdump

要调试内核,请使用 objdump 并查找崩溃输出中的十六进制偏移量,以找到有效的代码/汇编程序行。如果没有调试符号,您将看到所显示例程的汇编程序代码,但如果您的内核具有调试符号,C 代码也将可用。(可以在菜单配置的内核黑客菜单中启用调试符号。)例如

$ objdump -r -S -l --disassemble net/dccp/ipv4.o

注意

您需要位于内核树的顶层,以便拾取您的 C 文件。

如果您无法访问源代码,您仍然可以使用以下方法调试一些崩溃转储(Dave Miller 显示的示例崩溃转储输出)

EIP is at  +0x14/0x4c0
 ...
Code: 44 24 04 e8 6f 05 00 00 e9 e8 fe ff ff 8d 76 00 8d bc 27 00 00
00 00 55 57  56 53 81 ec bc 00 00 00 8b ac 24 d0 00 00 00 8b 5d 08
<8b> 83 3c 01 00 00 89 44  24 14 8b 45 28 85 c0 89 44 24 18 0f 85

Put the bytes into a "foo.s" file like this:

       .text
       .globl foo
foo:
       .byte  .... /* bytes from Code: part of OOPS dump */

Compile it with "gcc -c -o foo.o foo.s" then look at the output of
"objdump --disassemble foo.o".

Output:

ip_queue_xmit:
    push       %ebp
    push       %edi
    push       %esi
    push       %ebx
    sub        $0xbc, %esp
    mov        0xd0(%esp), %ebp        ! %ebp = arg0 (skb)
    mov        0x8(%ebp), %ebx         ! %ebx = skb->sk
    mov        0x13c(%ebx), %eax       ! %eax = inet_sk(sk)->opt

file:scripts/decodecode 可以用来自动执行大部分操作,具体取决于正在调试的 CPU 架构。

报告错误

一旦您通过检查其位置找到错误发生的位置,您可以尝试自己修复它或向上游报告它。

为了向上游报告它,您应该识别错误跟踪器(如果有)或用于受影响代码开发的邮件列表。这可以通过使用 get_maintainer.pl 脚本来完成。

例如,如果您在 gspca 的 sonixj.c 文件中发现错误,则可以使用以下命令获取其维护者

$ ./scripts/get_maintainer.pl --bug -f drivers/media/usb/gspca/sonixj.c
Hans Verkuil <hverkuil@xs4all.nl> (odd fixer:GSPCA USB WEBCAM DRIVER,commit_signer:1/1=100%)
Mauro Carvalho Chehab <mchehab@kernel.org> (maintainer:MEDIA INPUT INFRASTRUCTURE (V4L/DVB),commit_signer:1/1=100%)
Tejun Heo <tj@kernel.org> (commit_signer:1/1=100%)
Bhaktipriya Shridhar <bhaktipriya96@gmail.com> (commit_signer:1/1=100%,authored:1/1=100%,added_lines:4/4=100%,removed_lines:9/9=100%)
linux-media@vger.kernel.org (open list:GSPCA USB WEBCAM DRIVER)
linux-kernel@vger.kernel.org (open list)

请注意,它将指向

  • 最后接触源代码的开发人员(如果这是在 git 树内完成的)。在上面的示例中,Tejun 和 Bhaktipriya(在这种特定情况下,没有真正参与此文件的开发);

  • 驱动程序维护者(Hans Verkuil);

  • 子系统维护者(Mauro Carvalho Chehab);

  • 驱动程序和/或子系统邮件列表 (linux-media@vger.kernel.org);

  • Linux 内核邮件列表 (linux-kernel@vger.kernel.org);

  • 驱动程序/子系统的错误报告 URI(在上面的示例中没有)。

如果列表末尾包含错误报告 URI,请优先使用它们而不是电子邮件。否则,请向用于代码开发的邮件列表(linux-media ML)报告错误,并抄送驱动程序维护者(Hans)。

如果您完全不知道该将报告发送给谁,并且 get_maintainer.pl 没有提供任何有用的信息,请将其发送至 linux-kernel@vger.kernel.org

感谢您为使 Linux 尽可能稳定所做的贡献。

修复错误

如果您懂编程,不仅可以报告错误,还可以为我们提供解决方案,从而帮助我们。毕竟,开源是关于分享您的成果的,难道您不想因您的才华而获得认可吗?

如果您决定这样做,一旦您找到了修复方案,请将其提交到上游。

请阅读 Documentation/process/submitting-patches.rst,以帮助您的代码被接受。


关于使用 klogd 进行 Oops 跟踪的说明

为了帮助 Linus 和其他内核开发人员,klogd 中加入了大量支持来处理保护错误。为了完全支持地址解析,至少应使用 sysklogd 软件包的 1.3-pl3 版本。

当发生保护错误时,klogd 守护进程会自动将内核日志消息中的重要地址转换为它们的符号等效项。然后,这个转换后的内核消息通过 klogd 使用的任何报告机制转发。可以将保护错误消息简单地从消息文件中剪切出来并转发给内核开发人员。

klogd 执行两种类型的地址解析。第一种是静态转换,第二种是动态转换。静态转换使用 System.map 文件。为了进行静态转换,klogd 守护进程必须能够在守护进程初始化时找到系统映射文件。有关 klogd 如何搜索映射文件的信息,请参阅 klogd 手册页。

当使用内核可加载模块时,动态地址转换非常重要。由于内核模块的内存是从内核的动态内存池分配的,因此模块的起始位置以及模块中的函数和符号都没有固定位置。

内核支持系统调用,允许程序确定哪些模块已加载以及它们在内存中的位置。使用这些系统调用,klogd 守护进程构建一个符号表,可用于调试可加载内核模块中发生的保护错误。

至少 klogd 将提供生成保护错误的模块的名称。如果可加载模块的开发人员选择从模块导出符号信息,则可能有其他符号信息可用。

由于内核模块环境可能是动态的,因此必须有一种机制在模块环境发生变化时通知 klogd 守护进程。有一些可用的命令行选项,允许 klogd 向当前正在执行的守护进程发出信号,表明应刷新符号信息。有关详细信息,请参阅 klogd 手册页。

sysklogd 发行版中包含一个补丁,该补丁修改了 modules-2.0.0 软件包,以便在加载或卸载模块时自动向 klogd 发出信号。应用此补丁可以为调试可加载内核模块中发生的保护错误提供几乎无缝的支持。

以下是 klogd 处理的加载模块中保护错误的示例

Aug 29 09:51:01 blizard kernel: Unable to handle kernel paging request at virtual address f15e97cc
Aug 29 09:51:01 blizard kernel: current->tss.cr3 = 0062d000, %cr3 = 0062d000
Aug 29 09:51:01 blizard kernel: *pde = 00000000
Aug 29 09:51:01 blizard kernel: Oops: 0002
Aug 29 09:51:01 blizard kernel: CPU:    0
Aug 29 09:51:01 blizard kernel: EIP:    0010:[oops:_oops+16/3868]
Aug 29 09:51:01 blizard kernel: EFLAGS: 00010212
Aug 29 09:51:01 blizard kernel: eax: 315e97cc   ebx: 003a6f80   ecx: 001be77b   edx: 00237c0c
Aug 29 09:51:01 blizard kernel: esi: 00000000   edi: bffffdb3   ebp: 00589f90   esp: 00589f8c
Aug 29 09:51:01 blizard kernel: ds: 0018   es: 0018   fs: 002b   gs: 002b   ss: 0018
Aug 29 09:51:01 blizard kernel: Process oops_test (pid: 3374, process nr: 21, stackpage=00589000)
Aug 29 09:51:01 blizard kernel: Stack: 315e97cc 00589f98 0100b0b4 bffffed4 0012e38e 00240c64 003a6f80 00000001
Aug 29 09:51:01 blizard kernel:        00000000 00237810 bfffff00 0010a7fa 00000003 00000001 00000000 bfffff00
Aug 29 09:51:01 blizard kernel:        bffffdb3 bffffed4 ffffffda 0000002b 0007002b 0000002b 0000002b 00000036
Aug 29 09:51:01 blizard kernel: Call Trace: [oops:_oops_ioctl+48/80] [_sys_ioctl+254/272] [_system_call+82/128]
Aug 29 09:51:01 blizard kernel: Code: c7 00 05 00 00 00 eb 08 90 90 90 90 90 90 90 90 89 ec 5d c3

Dr. G.W. Wettstein           Oncology Research Div. Computing Facility
Roger Maris Cancer Center    INTERNET: greg@wind.rmcc.com
820 4th St. N.
Fargo, ND  58122
Phone: 701-234-7556