什麼是Oops?從語言學的角度說,Oops應該是一個擬聲詞。當出了點小事故,或者做了比較尷尬的事之後,你可以說"Oops",翻譯成中國話就叫做“哎呦”。“哎呦,對不起,對不起,我真不是故意打碎您的杯子的”。看,Oops就是這個意思。
在Linux核心開發中的Oops是什麼呢?其實,它和上面的解釋也沒什麼本質的差別,只不過說話的主角變成了Linux。當某些比較致命的問題出現時,我們的Linux核心也會抱歉的對我們說:“哎呦(Oops),對不起,我把事情搞砸了”。Linux核心在發生kernel panic時會列印出Oops資訊,把目前的寄存器狀態、堆棧內容、以及完整的Call trace都show給我們看,這樣就可以協助我們定位錯誤。
下面,我們來看一個執行個體。為了突出本文的主角--Oops,這個例子唯一的作用就是造一個null 指標引用錯誤。
很明顯,錯誤的地方就是第8行。
接下來,我們把這個模組編譯出來,再用insmod來插入到核心空間,正如我們預期的那樣,Oops出現了。
[ 100.731783] f1b9ff88 c0101131 f82cf040 c076d240 fffffffc f82cf040 0072cff4 f82d2000
Oops首先描述了這是一個什麼樣的bug,然後指出了發生bug的位置,即“IP: [<f82d2005>] hello_init+0x5/0x11 [hello]”。
在這裡,我們需要用到一個協助工具輔助objdump來協助分析問題。objdump可以用來反組譯碼,命令格式如下:
objdump -S hello.o
下面是hello.o反組譯碼的結果,而且是和C代碼混排的,非常的直觀。
對照Oops的提示,我們可以很清楚的看到,出錯的位置hello_init+0x5的彙編代碼是:
1 |
5:c7 05 00 00 00 00 01 movl $0x1,0x0 |
這句代碼的作用是把數值1存入0這個地址,這個操作當然是非法的。
我們還能看到它對應的c代碼是:
Bingo!在Oops的協助下我們很快就解決了問題。
我們再回過頭來檢查一下上面的Oops,看看Linux核心還有沒有給我們留下其他的有用資訊。
Oops: 0002 [#1]
這裡面,0002表示Oops的錯誤碼(寫錯誤,發生在核心空間),#1表示這個錯誤發生一次。
Oops的錯誤碼根據錯誤的原因會有不同的定義,本文中的例子可以參考下面的定義(如果發現自己遇到的Oops和下面無法對應的話,最好去核心代碼裡尋找):
有時候,Oops還會列印出Tainted資訊。這個資訊用來指出核心是因何種原因被tainted(直譯為“玷汙”)。具體的定義如下:
基本上,這個Tainted資訊是留給核心開發人員看的。使用者在使用Linux的過程中如果遇到Oops,可以把Oops的內容發送給核心開發人員去debug,核心開發人員根據這個Tainted資訊大概可以判斷出kernel panic時核心啟動並執行環境。如果我們只是debug自己的驅動,這個資訊就沒什麼意義了。
本文的這個例子非常簡單,Oops發生以後沒有造成宕機,這樣我們就可以從dmesg中查看到完整的資訊。但更多的情況是Oops發生的同時系統也會宕機,此時這些出錯資訊是來不及存入檔案中的,關掉電源後就無法再看到了。我們只能通過其他的方式來記錄:手抄或者拍照。
還有更壞的情況,如果Oops資訊過多的話,一頁螢幕顯示不全,我們怎麼來查看完整的內容呢?第一種方法,在grub裡用vga參數指定更高的解析度以使螢幕可以顯示更多的內容。很明顯,這個方法其實解決不了太多的問題;第二種方法,使用兩台機器,把調試機的Oops資訊通過串口列印到宿主機的螢幕上。但現在大部分的膝上型電腦是沒有串口的,這個解決方案也有很大的局限性;第三種方法,使用核心轉儲工具kdump把發生Oops時的記憶體和CPU寄存器的內容dump到一個檔案裡,之後我們再用gdb來分析問題。
開發核心驅動的過程中可能遇到的問題是千奇百怪的,調試的方法也是多種多樣,Oops是Linux核心給我們的提示,我們要用好它。