Linux 內核的測試和調試


https://blog.csdn.net/gong0791/article/details/48528295

Linux 內核的測試和調試

http://x-slam.com/linux-kernel-testing-and-debugging

 

Linux 內核測試哲學

 

不管是開源還是閉源,所有軟件的開發流程中,測試是一個重要的、不可或缺的環節,Linux 內核也不例外。開發人員自測、系統測試、回歸測試、壓力測試,都有各自不同的目的,但是從更高一個層次上看,這些測試的最終目的又是一樣的:保證軟件能一直運行下去,當有新功能加進去時,要保證新功能可以正常工作。

在軟件釋出 release 版之前,不用回歸測試就能保證穩定性,並且盡量避免在軟件發布后被用戶發現 bug。調試被用戶發現的 bug 是一項非常浪費時間和精力的工作。因此測試是一項非常重要的工作。不像閉源和專有的操作系統,Linux 內核的開發過程是完全開放的。這種處理方式即是它的優點,也是它的缺點。多個開發者持續增加新功能、修 bug、不斷集成與測試 —— 當環境有新的硬件或功能時,這種開發方式能夠保證內核能持續工作。在開源項目中,開發者與用戶共享測試的結果,這也是開源項目與閉源項目之間的一個很重要的差別。

幾乎所有 Linux 內核開發者都是活躍的 Linux 用戶。內核測試人員不一定非得是內核開發者,相反,用戶和開發者如果對新增的代碼不是很熟悉,他們的測試效果會比代碼開發人員自己測試的效果要好很多。也就是說,開發者的單元自測能驗證軟件的功能,但並不能保證在其他代碼、其他功能、其他軟件、硬件環境下面運行時會出現什么問題。開發者無法預料、也沒有機會和資源來測試所有環境。因此,用戶在 Linux 內核開發過程中起到非常重要的角色。

現在我們已經了解了持續集成測試的重要性,接下來我們會詳細介紹測試的知識。但在此之前,我還是向你介紹一下開發的過程,以便讓大家了解它是怎么工作的,以及如何把補丁打進內核主線。

全世界共有3000多個內核開發者為 Linux 內核貢獻代碼,每天都有新代碼添加到內核,結果是大概2個月就能產生一個release ,包括幾個穩定版和擴展穩定版。新功能的開發與已發布的穩定版集成測試流程在同時進行。

關於開發流程的詳細描述,請參考Greg Kroah-Hartman 的 Linux 內核開發的介紹

這份教程適合與初學者以及有經驗的內核開發者,如果你想加入到內核開發者行列,那么它也適合你。有經驗的開發人員可以跳過那些介紹基礎測試和調試的章節。

這份教程介紹如何測試和調試 Linux 內核、工具、腳本以及在回歸測試和集成測試中使用的調試機制。另外,本文還會介紹如何使用 git 把針對一個 bug 的補丁分離出來,再介紹把你的補丁提交到內核的郵件列表之前需要做些什么。我將會使用 Linux PM 作為測試它調試的對象。盡管本文討論的是 Linux 內核,但是介紹的方法也適用於任何其他軟件開發項目。

配置開發與測試的系統

第一步,找一個滿足你需求的開發環境,x86-64 是一個比較理想的選擇,除非你必須用特別的架構。

第二步,安裝 Linux 發行版,我推薦 Ubuntu,所以本教程會介紹基於 Ubuntu 的配置過程。你可以參考如何使用 Ubuntu 來安裝一個 Ubuntu 系統。

在開發和測試環境,最好要保證你的 boot 分區有足夠的空間來存放內核文件。你可以為 boot 分區留下 3GB 空間,或把 boot 分區直接放到根目錄下,這樣 boot 分區可以使用整個磁盤的空間。

安裝好操作系統后,確保 root 用戶可用,確保你的用戶身份可以使用 sudo 命令。你的系統也許已經安裝了 build-essential,它是編譯內核必備的軟件包,如果沒安裝,運行下面的命令:

    sudo apt-get install build-essential

然后運行下面的命令,保證你的系統能夠交叉編譯內核。下面的 ncurses-dev 安裝包是運行 make menuconfig 命令必須用到的。

  1. sudo apt-get install binutils-multiarch
  2. sudo apt-get install ncurses-dev
  3. sudo apt-get install alien

然后安裝一些每個內核開發者都會用到的工具包:

  1. sudo apt-get install git
  2. sudo apt-get install cscope
  3. sudo apt-get install meld
  4. sudo apt-get install gitk

如果你喜歡把內核通過交叉編譯以支持非 x86_64 架構的環境,請參考在 x86_64 上交叉編譯 Linux 內核

穩定的內核

使用 git 克隆一個穩定的內核,然后編譯安裝。你可以參考Linux 內核結構來找到最新的穩定版和開發主線。

    git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git

上面的步驟將會創建一個新的目錄,名為 linux-stable,並把源碼下載到里面。

你也可以直接下載壓縮包並解壓出源碼,無需使用 git:

    tar xvf linux-3.x.y.tar.xz

編譯安裝穩定版內核

如果你用 git 下載源碼,就執行以下命令:

  1. cd linux-stable
  2. git checkout linux-3.x.y

如果是直接下載壓縮文件,用以下命令進入源碼目錄:

    cd linux-3.x.y

如果你想把內核安裝到自己的系統上,最安全的方法是使用你安裝好的發行版擁有的配置文件。你可以在 /boot 目錄找到當前發行版的內核配置文件:

    cp /boot/config-3.x.y-z-generic .config

運行下面的命令,可以在當前內核配置的基礎上修改一些小地方,然后產生新的內核配置文件。比如說新的內核比你的 Ubuntu 發行版自帶的內核多了些新功能,而你正好需要用到它們,這個時候你就要修改配置了。

    make oldconfig

完成配置后,就可以編譯了:

    make all

完成編譯后,安裝這個新的內核:

    sudo “make modules_install install”

上面的命令安裝新內核,並把新內核作為啟動項添加到 grub 文件(LCTT:就是你下次開機時會多出一個開機選項)。好了你可以重啟電腦,然后選擇新的內核啟動系統。等等!先別沖動,在重啟電腦之前,我們保存下編譯內核產生的日志,用於比較和查找錯誤(如果有錯誤發生的話):

  1. dmesg -t > dmesg_current
  2. dmesg -t -k > dmesg_kernel
  3. dmesg -t -l emerg > dmesg_current_emerg
  4. dmesg -t -l alert > dmesg_current_alert
  5. dmesg -t -l crit > dmesg_current_alert
  6. dmesg -t -l err > dmesg_current_err
  7. dmesg -t -l warn > dmesg_current_warn

正常的話,dmesg 不會輸出 emerg, alert, crit 和 err 級別的信息。如果你不幸看到這些輸出了,說明內核或者你的硬件環境有問題。

再介紹一些重啟前的需要執行的操作。誰也不能保證新內核能夠正常啟動,所以請不要瀟灑地把老內核刪除,至少保留一個穩定可用的內核在系統上。修改一下 /etc/default/grub 文件:

使用 earlyprink=vga 作為內核啟動選項,把系統早期啟動的信息打印到顯示屏上:

    GRUB_CMDLINE_LINUX=”earlyprink=vga”

將 GRUB_TIMEOUT 的值設置成10秒到15秒之間的值,保證在開機啟動的時候你有足夠的時間來選擇啟動哪個內核:

取消對 GRUB_TIMEOUT 的注釋,並把它設置為10:GRUB_TIMEOUT=10

注釋掉 GRUB_HIDDEN_TIMEOUT 和 GRUB_HIDDEN_TIMEOUT_QUIET

運行 update-grub 命令,更新 /boot 目錄下的 grub 配置文件:

    sudo update-grub

現在可以重啟系統了。新內核起來后,比較新老內核的 dmesg 信息,看看新的內核有沒有編譯錯誤。如果新內核啟動失敗,你需要通過老內核啟動系統,然后分析下為什么失敗。

跟上節奏,永不落后(編譯最新版內核)

如果你想開上內核快車道,追求與時俱進,那就去下載 mainline 狀態的內核或 linux-next 狀態的內核(LCTT:讀者可進入 kernel.org 獲取代碼,linux 代碼被分為4種狀態:mainline, stable, longterm, linux-next)。安裝測試 mainline 狀態或 linux-next 狀態的內核,你就可以在正式發布之前幫助內核找到並修復里面的 bug。

mainline 狀態的內核源碼:

    git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git

linux-next 狀態的內核源碼:

    git clone git://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git

編譯安裝這兩種內核的步驟與編譯安裝穩定版內核一樣。按之前講過的步驟來就行了。

打補丁

Linux 內核的補丁是一個文本文件,包含新源碼與老源碼之間的差異。每個補丁只包含自己所依賴的源碼的改動,除非它被特意包含進一系列補丁之中。打補丁方法如下:

  1. patch -p1 < file.patch
  2. git apply --index file.patch

兩種方法都可以打補丁。但是,如果你要打的補丁包含一個新文件,git 命令不能識別這個新增的文件,也就是說這個新文件在 git 里面屬於 untracked 文件(LCTT:玩 git 的人對這個會比較熟悉,就是文件處於未被跟蹤的狀態,你需要使用 git add 命令將文件放入暫存區)。git diff 命令不會將這個文件的增量顯示出來,並且 git status 命令會顯示這個文件處於 untracked 狀態。

大多數情況下,有個沒被跟蹤的文件,對於編譯安裝內核來說沒什么問題,但是 git 操作就會出現一些問題了: git reset –hard 命令不會刪除這個新加的文件,並且接下來的 git pull 操作也會失敗。你有多種選擇來避免上面所說的狀況:

選項1,不跟蹤這個新文件:

如果打補丁后新添加了文件,在 git reset –hard 前使用 git clean 命令來刪除沒有被跟蹤的文件。舉個例子,git clean -dfx 命令會強制刪除未被跟蹤的目錄和文件,忽略在 .gitigniore 文件內規定的文件。如果你不在乎哪些文件會被刪除,你可以使用 -q 選項讓 git clean 命令進入安靜模式,不輸出任何處理過程。

選項2,跟蹤新文件:

你可以在使用 git apply –index file.patch 命令后讓 git 跟蹤打完補丁后新產生的文件(LCTT:使用 git add 命令),就是讓 git 把文件放入 index 區域。做完這個后,git diff 命令會將新文件的增量打印出來,git status 也會顯示者這是一個正常的新增文件。

基本測試

安裝好內核后,試試能不能啟動它。能啟動的話,檢查 dmesg 看看有沒有隱藏的錯誤。試試下面的功能:

  • 網絡(Wifi 或者網線)是否可用?
  • ssh 是否可用?
  • 使用 ssh 遠程傳輸文件。
  • 使用 git clone 和 git pull 命令。
  • 用用網絡瀏覽器。
  • 查看 email。
  • 使用 ftp, wget 等軟件下載文件。
  • 播放音頻視頻文件。
  • 連上 USB 鼠標等設備。
檢查內核日志

使用 dmesg 查看隱藏的問題,對於定位新代碼帶來的 bug 是一個好方法。一般來說,dmesg 不會輸出新的 crit, alert, emerg 級別的錯誤信息,也不應該出現新的 err 級別的信息。你要注意的是那些 warn 級別的日志信息。請注意 warn 這個級別的信息並不是壞消息,新代碼帶來新的警告信息,不會給內核帶去嚴重的影響。

  • dmesg -t -l emerg
  • dmesg -t -l crit
  • dmesg -t -l alert
  • dmesg -t -l err
  • dmesg -t -l warn
  • dmesg -t -k
  • dmesg -t

下面的腳本運行了上面的命令,並且將輸出保存起來,以便與老的內核的 dmesg 輸出作比較(LCTT:老內核的 dmesg 輸出在本系列的第二篇文章中有介紹)。然后運行 diff 命令,查看新老內核 dmesg 日志之間的不同。這個腳本需要輸入老內核版本號,如果不輸入參數,它只會生成新內核的 dmesg 日志文件后直接退出,不再作比較(LCTT:話是這么說沒錯,但點開腳本一看,沒輸參數的話,這貨會直接退出,連新內核的 dmesg 日志也不會保存的)。如果 dmesg 日志有新的警告信息,表示新發布的內核有漏網之“蟲”,這些 bug 逃過了自測和系統測試。你要看看,那些警告信息后面有沒有棧跟蹤信息?也許這里有很多問題需要你進一步調查分析。

壓力測試

執行壓力測試的一個好辦法是同時跑三四個內核編譯任務。下載各種版本的內核,同時編譯它們,並記錄時間。比較新內核跑壓力測試和老內核跑壓力測試所花的時間,然后可以定位新內核的性能。如果新內核跑壓力測試的時間比老內核的更長,說明新內核的部分模塊性能退步了。性能問題很難調試出來。第一步是找出哪里導致的性能退步。同時跑多個內核編譯任務對檢測內核整體性能來說是個好方法,但是這種方法涵蓋了多個內核模塊,比如內存管理、文件系統、DMA、驅動等(LCTT:也就是說,這種壓力測試沒辦法定位到是哪個模塊造成了性能的下降)。

    time make all

內核測試工具

我們可以在 Linux 內核本身找到多種測試方法。下面介紹一個很好用的功能測試工具集: ktest 套件

ktest 是一個自動測試套件,它可以提供編譯安裝啟動內核一條龍測試服務,也可以跑交叉編譯測試,前提是你的系統有安裝交叉編譯所需要的軟件。ktest 依賴於 flex 和 bison。詳細信息請參考放在 tools/testing/ktest 目錄下的文檔,你可以自學成材。另外還有一些參考資料教你怎么使用 ktest:

tools/testing/selftests 套件

我們來玩玩自測吧。內核源碼的多個子系統都有自己的自測工具,到目前為止,斷點、cpu熱插拔、efivarfs、IPC、KCMP、內存熱插拔、mqueue、網絡、powerpc、ptrace、rcutorture、定時器和虛擬機子系統都有自測工具。另外,用戶態內存的自測工具可以利用 testusercopy 模塊來測試用戶態內存到內核態的拷貝過程。下面的命令演示了如何使用這些測試工具:

編譯測試:

    make -C tools/testing/selftests

測試全部:(有些測試需要 root 權限,你需要以 root 用戶登入系統然后運行命令)

    make -C tools/testing/selftests run_tests

只測試單個子系統:

    make -C tools/testing/selftests TARGETS=vm run_tests

tools/testing/fault-injection 套件

在 tools/testing 目錄下的另一個測試套件是 fault-injection。failcmd.sh 腳本用於檢測 slab 和內存頁分配器的錯誤。這些工具可以測試內核能否很好地從錯誤狀態中恢復回來。這些測試需要用到 root 權限。下面簡單介紹了一些當前能提供的錯誤檢測方法。隨着錯誤檢測方法的增加,這份名單也會不斷增長。最新的名單請參考 Documentation/fault-injection/fault-injection.txt 文檔。

failslab (默認選項)

產生 slab 分配錯誤。作用於 kmalloc(), kmemcachealloc() 等函數(LCTT:產生的結果是調用這些函數就會返回失敗,可以模擬程序分不到內存時是否還能穩定運行下去)。

fail_page_alloc

產生內存頁分配的錯誤。作用於 allocpages(), getfree_pages() 等函數(LCTT:同上,調用這些函數,返回錯誤)。

fail_make_request

對滿足條件(可以設置 /sys/block//make-it-fail 或 /sys/block///make-it-fail 文件)的磁盤產生 IO 錯誤,作用於 generic_make_request() 函數(LCTT:所有針對這塊磁盤的讀或寫請求都會出錯)。

fail_mmc_request

對滿足條件(可以設置 /sys/kernel/debug/mmc0/fail_mmc_request 這個 debugfs 屬性)的磁盤產生 MMC 數據錯誤。

你可以自己配置 fault-injection 套件的功能。fault-inject-debugfs 內核模塊在系統運行時會在 debugfs 文件系統下面提供一些屬性文件。你可以指定出錯的概率,指定兩個錯誤之間的時間間隔,當然本套件還能提供更多其他功能,具體請查看 Documentation/fault-injection/fault-injection.txt。 Boot 選項可以讓你的系統在 debugfs 文件系統起來之前就可以產生錯誤,下面列出幾個 boot 選項:

  • failslab=
  • fail_page_alloc=
  • fail_make_request=
  • mmc_core.fail_request=[interval],[probability],[space],[times]

fault-injection 套件提供接口,以便增加新的功能。下面簡單介紹下增加新功能的步驟,詳細信息請參考上面提到過的文檔:

使用 DECLARE_FAULT_INJECTION(name) 定義默認屬性;

詳細信息可查看 fault-inject.h 中定義的 struct fault_attr 結構體。

配置 fault 屬性,新建一個 boot 選項;

這步可以使用 setup_fault_attr(attr, str) 函數完成,為了能在系統啟動的早期產生錯誤,添加一個 boot 選項這一步是必須要有的。

添加 debugfs 屬性;

使用 fault_create_debugfs_attr(name, parent, attr) 函數,為新功能添加新的 debugfs 屬性。

為模塊設置參數;

為模塊添加一些參數,對於配置錯誤屬性來說是一個好主意,特別是當新功能的應用范圍受限於單個內核模塊的時候(LCTT:不同內核,你的新功能可能需要不同的測試參數,通過設置參數,你的功能可以不必為了迎合不同內核而每次都重新編譯一遍)。

添加一個鈎子函數到錯誤測試的代碼中。

should_fail(attr, size) —— 當這個鈎子函數返回 true 時,用戶的代碼就應該產生一個錯誤。

應用程序使用這個 fault-injection 套件可以指定某個具體的內核模塊產生 slab 和內存頁分配的錯誤,這樣就可以縮小性能測試的范圍。

自動測試工具

這里列出一些能滿足不同需求的測試工具供你選擇。本小節只是簡單介紹個大概,並不提供詳細操作指南。

AuToTest

AuToTest 是一個全自動測試框架,存在的主要目的就是測試 Linux 內核,當然也可以用來測試其他東西,比如測試一塊新硬件是否能穩定工作。AuToTest 是開源軟件,以 GPL 方式授權,運行於 server-client 架構(即 C/S 架構)。你可以通過配置 server 端來對運行了 client 端的系統執行初始化、運行與監測工作,也可以自己在目標系統上讓 client 運行起來。另外你可以為這個測試框架添加測試用例,詳情請參考AuToTest 白皮書

Linaro Automated Validation Architecture

LAVA 自動測試框架用於自動安裝於運行測試。舉個例子:你在 LAVA 里面只需運行幾個命令就可以跑 LTP(LCTT:Linux Test Project,中文是 Linux 測試計划,SGI發起並由IBM負責維護,目的是為開源社區提供測試套件來驗證Linux的可靠性、健壯性和穩定性)。通過 LAVA 命令可以自動為你安裝 LTP 所需要的所有依賴包,下載源碼、編譯編碼、將 LTP 安裝到某個獨立的地方,方便卸載 LTP 時能移除所有二進制文件。安裝好 LTP 后,運行 LAVA 命令時添加 ‘ltp’ 選項就可以運行 LTP 測試任務了,它會將測試結果以文件方式保存下來,文件名包含測試名稱、時間戳。這些測試結果可以留着供以后參考。這是個發現軟件退化(如果軟件退化了的話)的好方法。下面列出 LAVA 配合 LTP 使用的一些命令:

顯示 LAVA 支持的測試列表:

    lava-test list-tests

安裝測試套件:

    lava-test install ltp

運行測試:

    lava-test run ltp

查看結果:

    lava-test results show ltp-timestamp.0

卸載測試套件:

    lava-test uninstall ltp

內核調試功能

Linux 內核本身包含很多調試功能,比如 kmemcheck 和 kmemleak。

kmemcheck

kmemcheck 是一個動態檢查工具,可以檢測出一些未被初始化的內存(LCTT:內核態使用這些內存可能會造成系統崩潰)並發出警告。它的功能與 Valgrind 類似,只是 Valgrind 運行在用戶態,而 kmemchecke 運行在內核態。編譯內核時加上 CONFIG_KMEMCHECK 選項打開 kmemcheck 調試功能。你可以閱讀 Documentation/kmemcheck.txt 來學習如何配置使用這個功能,以及如何看懂調試結果。

kmemleak

kmemleak 通過類似於垃圾收集器的功能來檢測內核是否有內存泄漏問題。而 kmemleak 與垃圾收集器的不同之處在於前者不會釋放孤兒目標(LCTT:不會再被使用的、應該被釋放而沒被釋放的內存區域),而是將它們打印到 /sys/kernel/debug/kmemleak 文件中。用戶態的 Valgrind 也有一個類似的功能,使用 –leak-check 選項可以檢測並報錯內存泄漏問題,但並不釋放這個孤兒內存。編譯內核時使用 CONFIG_DEBUG_KMEMLEAK 選項打開 kmemcleak 調試功能。閱讀 Documentation/kmemleak.txt 來學習怎么使用這個工具並讀懂調試結果。

內核調試接口

Linux 內核通過配置選項、調試用的 API、接口和框架來支持動態或靜態的調試。我們現在就好好學習學習這些牛逼的功能,從靜態編譯選項開始講。

調試配置選項:靜態編譯

大部分 Linux 內核以及內核模塊都包含調試選項,你只要在編譯內核或內核模塊的時候添加這個靜態調試選項,程序運行時后就會產生調試信息,並記錄在 dmesg 緩存中。

調試的 API

調試 API 的一個很好的例子是 DMA-debug,用來調試驅動是否錯誤使用了 DMA 提供的 API。它會跟蹤每個設備的映射關系,檢測程序有沒有試圖為一些根本不存在的映射執行“取消映射”操作,檢測代碼建立 DMA 映射后可能產生的“映射丟失”的錯誤。內核配置選項 CONFIG_HAVE_DMA_APT_DEBUG 和 CONFIG_DMA_API_DEBUG 可以為內核提供這個功能。其中,CONFIG_DMA_API_DEBUG 選項啟用后,內核調用 DMA 的 API 的同時也會調用 Debug-dma 接口。舉例來說,當一個驅動調用 dma_map_page() 函數來映射一個 DMA 緩存時,dma_map_page() 會調用debug_dma_map_page() 函數來跟蹤這個緩存,直到驅動調用 dma_unmap_page() 來取消映射。詳細內容請參考使用 DMA 調試 API 檢測潛在的數據污染和內存泄漏問題

動態調試

動態調試功能就是你可以決定在程序運行過程中是否要 pr_debug(), dev_dbg(), print_hex_dump_debug(), print_hex_dump_bytes() 這些函數正常運行起來。什么意思?當程序運行過程中出現錯誤時,你可以指定程序打印有針對性的、詳細的調試信息。這功能牛逼極了,我們不再需要為了添加調試代碼定位一個問題,而重新編譯安裝內核。你可以指定 CONDIF_DYNAMIC_DEBUG 選項打開動態調試功能,然后通過 /sys/kernel/debug/dynamic_debug/control 接口指定要打印哪些調試日志。下面分別列出代碼級別和模塊級別打印日志的操作方法:

讓 kernel/power/suspend.c 源碼第340行的 pr_debug() 函數打印日志:

    echo ‘file suspend.c line 340 +p’ > /sys/kernel/debug/dynamic_debug/control

讓內核模塊在加載過程中打開動態調試功能:

使用 modprobe 命令加在模塊時加上 dyndbg=’plmft’ 選項。

讓內核模塊的動態調試功能在重啟后依然有效:

編輯 /etc/modprobe.d/modname.conf 文件(沒有這個文件就創建一個),添加 dyndbg=’plmft’ 選項。然而對於哪些通過 initramfs 加載的驅動來說,這個配置基本無效(LCTT:免費奉送點比較高級的知識哈。系統啟動時,需要先讓 initramfs 掛載一個虛擬的文件系統,然后再掛載啟動盤上的真實文件系統。這個虛擬文件系統里面的文件是 initramfs 自己提供的,也就是說你在真實的文件系統下面配置了 /etc/modprobe.d/modname.conf 這個文件,initramfs 是壓根不去理會的。站在內核驅動的角度看:如果內核驅動在 initramfs 過程中被加載到內核,這個驅動讀取到的 /etc/modprobe.d/modname.conf 是 initramfs 提供的,而不是你編輯的那個。所以會有上述“寫了配置文件后重啟依然無效”的結論)。對於這種刁民,呃,刁驅動,我們需要修改 grub 配置文件,在 kernel 那一行添加 module.dyndbg=’plmft’ 參數,這樣你的驅動就可以開機啟動動態調試功能了。

想打印更詳細的調試信息,可以使用 dynamic_debug.verbose=1 選項。參考 Documentation/dynamic-debug-howto.txt 文件獲取更多信息。

設置追蹤點

到目前為止,我們介紹了多種動態和靜態調試方法。靜態調試選項和靜態調試鈎子函數(比如 DMA Debug API)需要的編譯過程打開或關閉,導致了一個難過的事實:需要重新編譯安裝內核。而動態編譯功能省去了“重新編譯”這件麻煩事,但是也有不足的地方,就是調試代碼引入了條件變量,用於判斷是否打印調試信息。這種方法可以讓你在程序運行時決定是否打印日志,但需要執行額外的判斷過程。“追蹤點”代碼只會在程序運行過程中使用“追蹤點”功能才會被觸發。也就是說,“追蹤點”代碼與上述說的兩種方法都不一樣。當用不到它時,它不會運行(LCTT:動態調試的話,代碼每次都需要查看下變量,然后判斷是否需要打印日志;而“追蹤點”貌似利用某種觸發機制,不需要每次都去查看變量)。當你需要用到它時,程序的代碼會把“追蹤點”代碼包含進去。它不會添加任何條件變量來增加系統的運行負擔。

詳細信息請參考布置追蹤代碼的小技巧

“追蹤點”的原理

追蹤點使用“跳躍標簽”,這是一種使用分支跳轉的編碼修正(code modification)技術。

當關閉追蹤點的時候,其偽代碼看起來時這樣的:

  1. [ code1 ]
  2. nop
  3. back:
  4. [ code2 ]
  5. return;
  6. tracepoint:
  7. [ tracepoint code ]
  8. jmp back;

當打開追蹤點的時候,其偽代碼看起來時這樣的:(注意追蹤點代碼出現的位置)

  1. [ code1 ]
  2. jmp tracepoint
  3. back:
  4. [ code2 ]
  5. return;
  6. tracepoint:
  7. [ tracepoint code ]
  8. jmp back;

(LCTT:咳咳,解釋解釋上面兩段偽代碼吧,能看懂的大神請忽略這段注釋。不使用追蹤點時,代碼運行過程是:code1->code2->return結束;使用追蹤點時,代碼運行過程是:code1->跳到tracepoint code執行調試代碼->跳回code2->return結束。兩段代碼的唯一區別就是第二行,前者為 nop(不做任何操作),后者為 jmp tracepoint (跳到調試代碼)。)

Linux 電源管理子系統的測試

使用靜態調試、動態調試和追蹤調試技術,我們來跑一下磁盤的電源管理測試。當系統被掛起時,內核會為磁盤創建一個休眠鏡像,使磁盤進入休眠模式,當系統重新被喚醒時,內核又利用這個休眠鏡像重新喚醒磁盤。

設置掛起設備與喚醒設備需要的時間:

    echo 1 > /sys/power/pm_print_times

以 reboot 模式掛起磁盤:

  1. echo reboot > /sys/power/disk
  2. echo disk > /sys/power/state

以 shutdown 模式掛起磁盤 —— 與 reboot 模式一樣,只是重新喚醒磁盤的話還需要電源提供。

  1. echo shutdown > /sys/power/disk
  2. echo disk > /sys/power/state

以 platform 模式掛起磁盤 —— 能測試更多內容,比如 BIOS 掛起和喚醒,會涉及到 ACPI 功能。我們推薦你使用這種方式,把 BIOS 也拉下水陪你玩掛起和喚醒游戲。

  1. echo platform > /sys/power/disk
  2. echo disk > /sys/power/state
仿真環境下進行 Linux 電源管理子系統測試

Linux 電源管理子系統在仿真環境下提供5種測試方式。這些方式僅僅在內核各層之間運行休眠的代碼而不是真正的讓系統進入休眠狀態。有些平台不能掛起系統,比如說我們需要模擬飛機的飛行環境,這時候使用這種仿真環境就非常有用處了。

freezer – 測試停掉處理器:

  1. echo freezer > /sys/power/pm_test
  2. echo platform > /sys/power/disk
  3. echo disk > /sys/power/state

devices – 測試停掉處理器以及掛起設備:

  1. echo devices > /sys/power/pm_test
  2. echo platform > /sys/power/disk
  3. echo disk > /sys/power/state

platform – 測試停掉處理器、掛起設備以及平台全局控制方法(*)

  1. echo platform > /sys/power/pm_test
  2. echo platform > /sys/power/disk
  3. echo disk > /sys/power/state

processors – 測試停掉處理器、掛起設備和平台全局控制方法(*),以及關閉未啟動的 CPU。

  1. echo processors > /sys/power/pm_test
  2. echo platform > /sys/power/disk
  3. echo disk > /sys/power/state

core – 測試停掉處理器、掛起設備和平台全局控制方法(*),關閉未啟動的 CPU,以及掛起平台或系統的設備。注意:這個測試模式運行在 ACPI 系統。

  1. echo core > /sys/power/pm_test
  2. echo platform > /sys/power/disk
  3. echo disk > /sys/power/state
Linux 電源管理子系統追蹤事件

電源管理子系統在運行過程中支持多種追蹤點和追蹤事件。我將對如何使用這些追蹤時間以及如何找到追蹤信息作一個簡單的介紹:

在運行時開啟電源管理事件:

  1. cd /sys/kernel/debug/tracing/events/power
  2. echo 1 > cpu_frequency/enable
  3. cat /sys/kernel/debug/tracing/set_event
  4. less /sys/kernel/debug/tracing/trace

為內核啟動的命令添加一個參數:

    trace_event=cpu_frequency

更多信息查看 Documentation/power/basic-pm-debugging.txt 以及同目錄下其他的文檔。

git bisect 命令

git bisect 是一個非常有用非常強大的工具,用於將 git 上的一個 commit 分離出來。我簡單過一遍它的用法。

下面是 git bisect 的用法:

  1. git bisect start
  2. git bisect bad # 當前版本是壞的
  3. git bisect good v3.14-rc6 # 上個版本是好的

一旦指定好好的版本和壞的版本,git bisect 就會開始把好壞兩個版本之間的所有 commit 對半分,並將其中的一半提交 pull 下來。然后重新編譯安裝測試內核,並標記這個內核是好是壞。重復這個過程,知道某個你選好的 commit 被標記被好或者壞。我們可能需要測試多個內核版本,測到最后一個版本時,git bisect 會將一個 commit 標記為壞。下面的命令可以在 git bisect 分析過程中起到幫助作用:

查看 bisect 操作的過程:

    git bisect log

重置 git bisect,標記錯誤時可以用到,保存 git log 的輸出,重新操作上一次 bisect 的步驟:

    git bisect reset

重放 git bisect 操作過程:

    git bisect replay git_log_output

如果一個問題很清楚是在某個區域內,git bisect 命令可以定位到一個具體的內核源碼樹枝干上。舉個例子,在調試一個鐳龍顯卡驅動的問題時,為 git bisect 指定 drivers/drm/radeon 參數,可以讓 git bisect 只檢索對 drivers/drm/radeon 里面的文件有修改的 commit。

讓 git bisect 只檢索內核樹的某個枝干:

    git bisect start drivers/drm/radeon

Linux 內核補丁測試

你試過自己寫內核補丁嗎?本節介紹在把你的補丁包提交到 Linux 郵箱列表之前,需要做哪些操作。另外我們還會介紹如何把它發送出去。

寫好代碼后,編譯它。把 make 過程產生的輸出保存到文檔中,查看新代碼有沒有警告信息。找到所有的警告信息,處理掉。當你的代碼編譯過程沒有任何不正常的輸出,安裝這個內核,然后啟動測試。如果啟動正常,查看 dmesg 里面有沒於錯誤,與老內核生成的 dmesg 日志做個比較。運行一些壓力測試,請參考我們以前講過的測試內容。如果這個補丁用於修復某個 bug,請確保真的已經修復了。如果真的修復了,請確保能通過系統測試。找出打你補丁的模塊下面的回歸測試工具,運行一下。如果補丁涉及到其他架構,你需要交叉編譯然后測試一下。請通過下面的目錄查找測試工具:

如果你對你的補丁測試結果感到很滿意,你就可以提交補丁了。請確保提交 commit 的信息要描述得非常清楚。要讓內核維護者和其他開發者看懂補丁所修改的內容,這一點非常重要。生成補丁后,執行 scripts/checkpatch.pl 腳本,找到 checkpatch 是產生的錯誤或警告(如果有的話),修復它們。重新生成補丁,直到補丁通過這個腳本的測試。重新測試這個補丁。將本補丁用於其他的內核源碼上,保證不會有沖突產生。

現在你做好提交補丁的准備了。先運行 scriptst/get_maintainer.pl 來確認你應該把補丁發給哪個內核維護者。注意不要以附件形式發送補丁,而是以純文本形式粘貼在郵件里面。確保你的郵件客戶端可以發送純文本信息,你可以試試給自己發送一份補丁郵件來測試你的郵件客戶端的功能。收到自己的郵件后,運行 checkpatch 命令並給自己的內核源碼打上你的補丁。如果這兩部都能通過,你就可以給 Linux 郵箱列表發送補丁了。使用 git send-email 命令是提交補丁最安全的方式,可以避免你的郵箱的兼容性問題。你的 .gitconfig 文件里面需要配置好有效的 smtp 服務器,詳細操作參考 git 的幫助文檔。

更多提交補丁的規矩,請參考下面的資料:

  • linux_git/Documentation/applying-patches.txt
  • linux_git/Documentation/SubmitChecklist
  • linux_git/Documentation/SubmittingDrivers
  • linux_git/Documentation/SubmittingPatches
  • linuxgit/Documentation/stablekernel_rules.txt
  • linuxgit/Documentation/stableapi_nonsense.txt

下面是一些內核測試教程的資料:

內核測試套件和項目

除我們討論過的測試資源之外,這里還有很多測試項目值得介紹,包括開源的和廠家自己提供的。這些項目每一個都是針對特定領域的,比如嵌入式或者企業自己使用。我們簡單過一下。

Linux 測試項目(LTP)測試套件是一系列工具的集合,用於測試內核的可靠性、健壯性和穩定性。你可以為這個項目添加自己的測試代碼,並且 LTP 項目歡迎你貢獻自己的代碼。runltp 腳本默認情況下會測試下面的子系統:

  • 文件系統壓力測試
  • 磁盤 IO 測試
  • 內存管理壓力測試
  • IPC(進程間通信)測試
  • 調度器測試
  • 命令的功能性驗證測試
  • 系統調用功能驗證測試

LTP-DDT 是一個基於 LTP 的測試應用(LCTT:就是 LTP 的閹割版么),專注於測試嵌入式設備驅動。

Linux Driver Verification 這個項目的目標是提高 Linux 設備驅動的質量,它為設備驅動驗證開發了集成環境平台,並且利用與時俱進的研究來增強驗證工具的質量。

一致性測試

如果你有將某個 Unix 平台下的應用一直到另一個平台的經驗,你就能理解 Linux Standard Base (LSB) 和 LSB 一致性測試套件的重要性了。LSB 是 Linux Foundation 工作組創建的用於降低支持不同 Linux 平台所需要的開銷,方法就是通過降低不同 Linux 發行版之間的差別,保證應用在不同發行版之間的可移植性。前事不忘后事之師,Unix 世界的分歧在 Linux 世界一定要避免。這就是為什么你可以把一個 rpm 包轉化成 deb 包后還能安裝並正常運行的秘密。

靜態分析工具

靜態分析之所以會被稱為“靜態分析”,是因為這些工具只分析代碼,並不執行它們。分析 Linux 內核代碼的靜態分析工具有很多,Sparse 是 Linus Torvalds 寫的專門用於檢查內核靜態類型的工具。它是一個語義檢查器,會為 C 語言的語義建立語義檢析樹,執行惰性類型評估。內核編譯系統支持 sparse,並且為編譯內核的命令提供開啟 sparse 的選項。

為內核所有需要重新編譯的 C 文件執行 sparse 語義檢查:

    make C=1 allmodconfig

為內核所有 C 文件(即使不需要重新編譯)執行 sparse 語義檢查:

    make C=2 allmodconfig

Sparse 的資源:

Smatch 分析程序代碼的邏輯錯誤。它可以檢測到諸如“為一個沒鎖上的 spinlock 執行解鎖”的邏輯錯誤。內核源碼支持 smatch:

在 Linux 內核中運行 smatch:

    make CHECK=”~/path/to/smatch/smatch -p=kernel” C=1 bzImage modules | tee warns.txt

請參考下面的資料來獲取和編譯 smatch。需要注意的是 smatch 是個正在發展的項目,架構會不斷變化。

那么我們該怎么處理 Sparse 和 Smatch 所發現的語義和邏輯上的錯誤呢?一些錯誤可以被分離為日常問題或模塊問題,可以輕易被解決。但是有些語義錯誤涉及到全局,因為剪切粘貼了一些代碼。在一些環境中,當一些接口函數被廢棄不再使用,或者僅僅做了寫微小的修改,你就需要大規模更新源碼。這時候你需要 Coccinelle 來幫忙。,Coccinelle 使用 SmPL 語言(語義包語言)來為 C 代碼提供匹配和轉換代碼的功能。Coccinelle 的從一開始就作為 Linux 的附屬產品持續發展的。

舉個例子:foo(int) 函數突然變成 foo(int, char *) 函數,多出了一個輸入參數(可以把第二個參數置為 null)。所有調用 foo() 函數的代碼都需要更新了,這可能是個悲摧的體力活。但是使用 Coccinelle 的話,這項工作瞬間變得輕松,腳本會幫你找到調用 foo(parameter1) 的代碼,然后替換成 foo(parameter1, NULL)。做完這些后,所有調用這個函數的代碼都可以運行一遍,驗證下第二個參數為 NULL 是否能正常工作。關於 Coccinelle 的更多信息,以及在不同項目中(當然,也包括 Linux 內核這個項目)的使用方法,請參考項目主頁:Cocinelle

參考文獻

本文涵蓋了很多方面,這里列出一些參考文檔供讀者做進一步研究。


via:

http://www.linuxjournal.com/content/linux-kernel-testing-and-debugging?page=0,0

http://www.linuxjournal.com/content/linux-kernel-testing-and-debugging?page=0,1

http://www.linuxjournal.com/content/linux-kernel-testing-and-debugging?page=0,2

http://www.linuxjournal.com/content/linux-kernel-testing-and-debugging?page=0,3

http://www.linuxjournal.com/content/linux-kernel-testing-and-debugging?page=0,4

http://www.linuxjournal.com/content/linux-kernel-testing-and-debugging?page=0,5

轉載請注明:

轉載自X-SLAM

本文鏈接地址: Linux 內核的測試和調試


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM