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 從舊記憶體中提取核心環形緩衝區。

查詢 bug 的位置

如果您能指出 bug 在核心原始檔中的位置,報告 bug 的效果最佳。有兩種方法可以做到這一點。通常,使用 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 程式碼也將可用。(可以在選單配置的核心 hacking 選單中啟用除錯符號。)例如

$ objdump -r -S -l --disassemble net/ipv4/tcp.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 架構。

報告 bug

一旦您透過檢查 bug 的位置,找到了 bug 發生的位置,您可以嘗試自己修復它或將其報告給上游。

為了將其報告給上游,您應該確定 bug 跟蹤器(如果有)或用於開發受影響程式碼的郵件列表。這可以透過使用 get_maintainer.pl 指令碼來完成。

例如,如果您在 gspca 的 sonixj.c 檔案中發現了一個 bug,您可以使用以下命令獲取其維護者

$ ./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);

  • 驅動程式/子系統的 bug 報告 URI(在上面的示例中沒有)。

如果列表末尾包含 bug 報告 URI,請優先選擇它們而不是電子郵件。否則,請將 bug 報告給用於開發程式碼的郵件列表(linux-media ML),並抄送驅動程式維護者 (Hans)。

如果您完全不知道該將報告發送給誰,並且 get_maintainer.pl 沒有為您提供任何有用的資訊,請將其傳送到 linux-kernel@vger.kernel.org

感謝您幫助使 Linux 儘可能穩定。

修復 bug

如果您懂程式設計,您不僅可以報告 bug,還可以為我們提供解決方案,從而幫助我們。畢竟,開源就是分享您所做的事情,難道您不想因您的才華而受到認可嗎?

如果您決定這樣做,一旦您解決了問題,請將其提交給上游。

請閱讀 Documentation/process/submitting-patches.rst,以幫助您的程式碼被接受。


關於使用 klogd 進行 Oops 跟蹤的說明

為了幫助 Linus 和其他核心開發人員,klogd 中已包含大量支援來處理保護錯誤。為了完全支援地址解析,應使用至少 1.3-pl3 版本的 sysklogd 包。

發生保護錯誤時,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