轉載文章請註明做者和二維碼及全文信息。linux
轉自:http://blog.csdn.net/swingwang/article/details/72331196程序員
不會編程的程序員,不是好的架構師,編程和內核調試也是出色架構師的必修課。談起編程人員的數量,基於Linux平臺的軟件工程師確定是最多的,沒有之一。那今天咱們就以Linux爲例,深刻講一下內核模塊和內核的調試技術和調試工具KGDB。編程
KGDB是在內核2.6.26版本中正式支持的,對應發行版即SLES11及以上、RHEL6及以上,在此以前的內核版本由Linsyssoft Technologies公司提供補丁以支持KGDB,但並非全部內核版本都有補丁可用,同時打補丁操做也比較繁瑣且問題多多,所以可用性不高。微信
注:如下稱 「被調試的主機」爲目標機,運行gdb進行調試的主機爲開發機網絡
2.1 目標機配置架構
2.1.1 配置串口函數
物理機串口根據實際環境要求配置,虛擬機按以下方式配置,pipe名字能夠修改,但要保證和開發機一致:工具
2.1.2 更新內核以支持kgdb.net
注:本文以SLES11SP1做爲目標機爲例,內核源碼直接安裝RPM包就可使用,RHEL要稍微麻煩一些,須要下載源碼包,進行編譯後進行安裝。線程
更新內核前準備
加入調試信息後內核及各個ko的體積會增大數倍,所以編譯內核前必定要確認磁盤有7G以上剩餘空間(保險起見建議預留10G),執行make後源碼目錄空間佔用超過5G。
執行make modules_install後/lib/modules目錄還要佔用1.4G
SLES系列默認內核源碼目錄是/usr/src/linux-xxx/,但因爲試驗用的虛擬機建立時磁盤選擇默認大小隻有8G,所以額外建立了一塊20G的磁盤掛載到/home目錄做爲內核編譯目錄,可直接將目錄usr/src/linux-xxx/拷貝到/home/linux-xxx/不影響編譯。
更新內核步驟
一、執行uname –r確認當前運行內核的類型,拷貝/boot/目錄下對應內核類型的config文件到內核源碼目錄並重命名爲.config;大多數狀況下編譯內核後啓動失敗都是由於內核配置不當,所以最好在系統原有配置文件基礎上修改。
二、在內核源碼目錄執行make menuconfig進行內核配置;
進入Kernel hacking子選項,確認激活如下項目:
[*]Compile the kernel with debug info
[*]Compile the kernel with frame pointers
[*]KGDB: kernel debugging with remote gdb
清除 Write protect kernel read-only data structures選項;此項默認是激活的,會致使後續使用gdb調試時沒法加斷點;
在SLES11SP1上去掉Write protect kernel read-only data structures後編譯會出錯,緣由是函數mark_rodata_ro在init/main.c和cacheflush.h中重複定義了
解決辦法是注掉main.c中的定義:
三、執行make all編譯內核;(耗時約1小時,可以使用make –j x all加快編譯速度,x表示線程數)
四、安裝模塊,編譯完成後,新生成的模塊ko還在源碼目錄,並未更新到/lib/module/對應目錄:
注意,在安裝模塊前強烈建議備份原模塊目錄,以便調試完成後或新編譯模塊有問題時恢復環境,以下。
執行make modules_install(注意:不是make modules install)將拷貝ko到/lib/module/
五、建立啓動內核及initrd
注:依然強烈建議先備份/boot/目錄下的原vmlinuz和initrd文件,由於雖然內核install腳本會自動備份,但若是install執行兩次或以上,則以前的備份會被新備份覆蓋。
設置/etc/modprobe.d/unsupported-modules中allow_unsupported_modules爲1,不然新編譯生成的模塊ko可能沒法加載:
執行make install,將會拷貝源碼目錄下的vmlinux到/boot/目錄並壓縮爲vmlinuz,並建立initrd:
六、爲KGDB內核建立新的啓動項
注:繼續強烈建議先備份原始啓動項,將原始啓動項使用的內核和initrd文件指定爲以前備份的文件:
新增的KGDB啓動項,與原始啓動項相比只增長了一個參數:kgdboc=ttyS0,115200
若是須要目標機一啓動就斷住(好比要調試啓動階段的代碼),則再增長一個參數kgdbwait
七、重啓目標機,以KGDB選項啓動
2.2 開發機配置
開發機不須要和目標機硬件或內核相同,只要上面裝的gdb版本知足kgdb的要求就能夠。本文使用一個SLES10SP4的32位虛擬機做爲開發機。
2.2.1 配置串口
物理機串口根據實際環境要求配置,虛擬機按以下方式配置:
檢查參數,確認串口配置正確:
一、 在目標機執行cat /dev/ttyS0;
二、 在開發機執行echo test > /dev/ttyS0
三、 觀察目標機是否打印test字樣;
2.2.2 準備調試代碼和目標二進制文件
調試代碼
因爲gdb調試須要源碼文件,所以須要把內核源碼拷貝到開發機。建議在目標機編譯前把整個源碼目錄拷貝到開發機,不然編譯後整個源碼目錄體積太大。
目標二進制文件
目標二進制文件就是要調試的文件,如vmlinux或xxx.ko,直接把目標機上編譯好的文件拷貝到開發機,建議放在內核源碼目錄下。
3.1調試內核vmlinux
以調試函數block層的函數get_request_wait爲例
一、 在目標機執行echo g > /proc/sysrq-trigger,會觸發目標機掛起以等待開發機輸入;
二、 在開發機啓動gdb:
三、 設置啓動遠程調試
在gdb界面輸入如下兩條命令,成功的話會顯示斷在kgdb_breakpoint函數:
set remotebaud 115200
target remote /dev/ttyS0
四、 輸入b get_request_wait爲咱們想調試的函數設置斷點(b表示breakpoint),而後執行c(continue)讓目標機繼續運行直到斷點;
五、 查看調用棧(bt)和單步調試(n)都是比較有用的手段;
查看函數get_request_wait的調用棧:
單步調試:
下圖例子中代碼執行到rq = get_request(q, rw_flags, bio, GFP_NOIO);這行前;
執行p rq打印指針變量rq的地址顯示value optimized out表示爲空;
執行p *rq打印指針變量rq的內容顯示沒法訪問0x0地址;
執行n讓rq = get_request(q, rw_flags, bio, GFP_NOIO);執行完;
再次執行p rq成功打印出指針變量rq的地址;
執行p *rq成功打印出指針變量rq的內容;
六、 調試完成後清除斷點讓目標機恢復正常運行;
執行info b查看當前斷點;
執行d breakpoint 1清除斷點1;
執行c讓目標機恢復運行;
目標機以前掛起後網絡就中斷了,此時恢復後又可從新登陸:
3.2 調試模塊KO
以調試模塊scsi_mod.ko爲例:
一、先在目標機上查看模塊在內核中的偏移地址,而後掛起目標機:
二、在開發機啓動gdb,並執行add-symbol-file [模塊ko] [內核地址]加載模塊ko文件:
以後的步驟同調試內核vmlinux同樣:啓動遠程調試、設置斷點…
使用KGDB,一方面能夠幫助閱讀內核代碼,實際觀察代碼執行的流程;另外一方面能夠幫助非自研模塊相關流程的問題定位,不須要反覆添加打印重編內核,提升問題定位效率。本文重點描述了KGDB環境搭建及啓動調試的步驟,更多gdb調試技巧請參考gdb手冊。
環境搭建重點在於更新內核,這塊也是整個過程當中最耗時和容易出錯的,項目組能夠組織分工進行各個版本、類型內核的KGDB更新(如SLES11 32位/64位、RHEL等等)並保存,後續使用時能夠直接拷貝。請搜索「ICT_Architect」加入微信公衆號「架構師技術聯盟」獲取更多精彩內容。