?? ch04s06.html
字號:
<html xmlns:cf="http://docbook.sourceforge.net/xmlns/chunkfast/1.0"><head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>4.6. 調(diào)試器和相關工具</title><link rel="stylesheet" href="docbook.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.69.0"><link rel="start" href="index.html" title="Linux 設備驅(qū)動 Edition 3"><link rel="up" href="ch04.html" title="第 4 章 調(diào)試技術"><link rel="prev" href="ch04s05.html" title="4.5. 調(diào)試系統(tǒng)故障"><link rel="next" href="ch05.html" title="第 5 章 并發(fā)和競爭情況"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">4.6. 調(diào)試器和相關工具</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch04s05.html">上一頁</a> </td><th width="60%" align="center">第 4 章 調(diào)試技術</th><td width="20%" align="right"> <a accesskey="n" href="ch05.html">下一頁</a></td></tr></table><hr></div><div class="sect1" lang="zh-cn"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="DebuggerandRelatedTools.sect"></a>4.6. 調(diào)試器和相關工具</h2></div></div></div><p>調(diào)試模塊的最后手段是使用調(diào)試器來單步調(diào)試代碼, 查看變量值和機器寄存器. 這個方法費時, 應當盡量避免. 但是, 通過調(diào)試器獲得的代碼的細粒度視角有時是很有價值的.</p><p>在內(nèi)核上使用一個交互式調(diào)試器是一個挑戰(zhàn). 內(nèi)核代表系統(tǒng)中的所有進程運行在自己的地址空間. 結果, 用戶空間調(diào)試器所提供的一些普通功能, 例如斷點和單步, 在內(nèi)核中更難得到. 本節(jié)中, 我們看一下幾個調(diào)試內(nèi)核的方法; 每個都有缺點和優(yōu)點.</p><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="Usinggdb.sect"></a>4.6.1. 使用 gdb</h3></div></div></div><p>gdb 對于看系統(tǒng)內(nèi)部是非常有用. 在這個級別精通調(diào)試器的使用要求對 gdb 命令有信心, 需要理解目標平臺的匯編代碼, 以及對應源碼和優(yōu)化的匯編碼的能力.</p><p>調(diào)試器必須把內(nèi)核作為一個應用程序來調(diào)用. 除了指定內(nèi)核映象的文件名之外, 你需要在命令行提供一個核心文件的名子. 對于一個運行的內(nèi)核, 核心文件是內(nèi)核核心映象, /proc/kcore. 一個典型的 gdb 調(diào)用看來如下:</p><pre class="screen">gdb /usr/src/linux/vmlinux /proc/kcore </pre><p>第一個參數(shù)是非壓縮的 ELF 內(nèi)核可執(zhí)行文件的名子, 不是 zImage 或者 bzImage 或者給啟動環(huán)境特別編譯的任何東東.</p><p>gdb 命令行的第二個參數(shù)是核心文件的名子. 如同任何 /proc 中的文件, /proc/kcore 是在被讀的時候產(chǎn)生的. 當 read 系統(tǒng)調(diào)用在 /proc 文件系統(tǒng)中執(zhí)行時, 它映射到一個數(shù)據(jù)產(chǎn)生函數(shù),而不是一個數(shù)據(jù)獲取函數(shù); 我們已經(jīng)在本章"使用 /proc 文件系統(tǒng)"一節(jié)中利用了這個特點. kcore 用來代表內(nèi)核"可執(zhí)行文件", 以一個核心文件的形式; 它是一個巨大的文件, 因為他代表整個的內(nèi)核地址空間, 對應于所有的物理內(nèi)存. 從 gdb 中, 你可查看內(nèi)核變量,通過發(fā)出標準 gdb 命令. 例如, p jiffies 打印時鐘的從啟動到當前時間的嘀噠數(shù).</p><p>當你從gdb打印數(shù)據(jù), 內(nèi)核仍然在運行, 各種數(shù)據(jù)項在不同時間有不同的值; 然而, gdb 通過緩存已經(jīng)讀取的數(shù)據(jù)來優(yōu)化對核心文件的存取. 如果你試圖再次查看 jiffies 變量, 你會得到和以前相同的答案. 緩存值來避免額外的磁盤存取對傳統(tǒng)核心文件是正確的做法, 但是在使用一個"動態(tài)"核心映象時就不方便. 解決方法是任何時候你需要刷新 gdb 緩存時發(fā)出命令 core-file /proc/kcore; 調(diào)試器準備好使用新的核心文件并且丟棄任何舊信息. 然而, 你不會一直需要發(fā)出 core-file 在讀取一個新數(shù)據(jù)時; gdb 讀取核心以多個幾KB的塊的方式, 并且只緩存它已經(jīng)引用的塊.</p><p>gdb 通常提供的不少功能在你使用內(nèi)核時不可用. 例如, gdb 不能修改內(nèi)核數(shù)據(jù); 它希望在操作內(nèi)存前在它自己的控制下運行一個被調(diào)試的程序. 也不可能設置斷點或觀察點, 或者單步過內(nèi)核函數(shù).</p><p>注意, 為了給 gdb 符號信息, 你必須設置 CONFIG_DEBUG_INFO 來編譯你的內(nèi)核. 結果是一個很大的內(nèi)核映象在磁盤上, 但是, 沒有這個信息, 深入內(nèi)核變量幾乎不可能.</p><p>有了調(diào)試信息, 你可以知道很多內(nèi)核內(nèi)部的事情. gdb 愉快地打印出結構, 跟隨指針, 等等. 而有一個事情比較難, 然而, 是檢查 modules. 因為模塊不是傳遞給gdb 的 vmlinux 映象, 調(diào)試器對它們一無所知. 幸運的是, 作為 2.6.7 內(nèi)核, 有可能教給 gdb 需要如何檢查可加載模塊.</p><p>Linux 可加載模塊是 ELF 格式的可執(zhí)行映象; 這樣, 它們被分成幾個節(jié). 一個典型的模塊可能包含一打或更多節(jié), 但是有 3 個典型的與一次調(diào)試會話相關:</p><div class="variablelist"><dl><dt><span class="term">.text<span></span></span></dt><dd><p>這個節(jié)包含有模塊的可執(zhí)行代碼. 調(diào)試器必須知道在哪里以便能夠給出回溯或者設置斷點.( 這些操作都不相關, 當運行一個調(diào)試器在 /proc/kcore 上, 但是它們在使用 kgdb 時可能有用, 下面描述).</p></dd><dt><span class="term"><span>.bss</span></span></dt><dd></dd><dt><span class="term">.data <span></span></span></dt><dd><p>這 2 個節(jié)持有模塊的變量. 在編譯時不初始化的任何變量在 .bss 中, 而那些要初始化的在 .data 里.</p></dd></dl></div><p>使 gdb 能夠處理可加載模塊需要通知調(diào)試器一個給定模塊的節(jié)加載在哪里. 這個信息在 sysfs 中, 在 /sys/module 下. 例如, 在加載 scull 模塊后, 目錄 /sys/module/scull/sections 包含名子為 .text 的文件; 每個文件的內(nèi)容是那個節(jié)的基地址.</p><p>我們現(xiàn)在該發(fā)出一個 gdb 命令來告訴它關于我們的模塊. 我們需要的命令是 add-symble-flile; 這個命令使用模塊目標文件名, .text 基地址作為參數(shù), 以及一系列描述任何其他感興趣的節(jié)安放在哪里的參數(shù). 在深入位于 sysfs 的模塊節(jié)數(shù)據(jù)后, 我們可以構建這樣一個命令:</p><pre class="screen">(gdb) add-symbol-file .../scull.ko 0xd0832000 \-s .bss 0xd0837100 \ -s .data 0xd0836be0</pre><p>我們已經(jīng)包含了一個小腳本在例子代碼里( gdbline ), 它為給定的模塊可以創(chuàng)建這個命令.</p><p>我們現(xiàn)在使用 gdb 檢查我們的可加載模塊中的變量. 這是一個取自 scull 調(diào)試會話的快速例子:</p><pre class="screen">(gdb) add-symbol-file scull.ko 0xd0832000 \-s .bss 0xd0837100 \ -s .data 0xd0836be0add symbol table from file "scull.ko" at .text_addr = 0xd0832000 .bss_addr = 0xd0837100 .data_addr = 0xd0836be0(y or n) yReading symbols from scull.ko...done.(gdb) p scull_devices[0]$1 = {data = 0xcfd66c50, quantum = 4000, qset = 1000, size = 20881, access_key = 0, ...}</pre><p>這里我們看到第一個 scull 設備當前持有 20881 字節(jié). 如果我們想, 我們可以跟隨數(shù)據(jù)鏈, 或者查看其他任何感興趣的模塊中的東東.</p><p>這是另一個值得知道的有用技巧:</p><pre class="screen">(gdb) print *(address)</pre><p>這里, 填充 address 指向的一個 16 進制地址; 輸出是對應那個地址的代碼的文件和行號. 這個技術可能有用, 例如, 來找出一個函數(shù)指針真正指向哪里.</p><p>我們?nèi)匀徊荒苓M行典型的調(diào)試任務, 如設置斷點或者修改數(shù)據(jù); 為進行這些操作, 我們需要使用象 kdb( 下面描述 ) 或者 kgdb ( 我們馬上就到 )這樣的工具.</p></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="ThekdbKernelDebugger.sect"></a>4.6.2. kdb 內(nèi)核調(diào)試器</h3></div></div></div><p>許多讀者可能奇怪為什么內(nèi)核沒有建立更多高級的調(diào)試特性在里面.答案, 非常簡單, 是 Linus 不相信交互式的調(diào)試器. 他擔心它們會導致不好的修改, 這些修改給問題打了補丁而不是找到問題的真正原因. 因此, 沒有內(nèi)嵌的調(diào)試器.</p><p>其他內(nèi)核開發(fā)者, 但是, 見到了交互式調(diào)試工具的一個臨時使用. 一個這樣的工具是 kdb 內(nèi)嵌式內(nèi)核調(diào)試器, 作為來自 oss.sgi.com 的一個非官方補丁. 要使用 kdb, 你必須獲得這個補丁(確認獲得一個匹配你的內(nèi)核版本的版本), 應用它, 重建并重安裝內(nèi)核. 注意, 直到本書編寫時, kdb 只在IA-32(x86)系統(tǒng)中運行(盡管一個給 IA-64 的版本在主線內(nèi)核版本存在了一陣子, 在被去除之前.)</p><p>一旦你運行一個使能了kdb的內(nèi)核, 有幾個方法進入調(diào)試器. 在控制臺上按下 Pause(或者 Break) 鍵啟動調(diào)試器. kdb 在一個內(nèi)核 oops 發(fā)生時或者命中一個斷點時也啟動, 在任何一種情況下, 你看到象這樣的一個消息:</p><pre class="screen">Entering kdb (0xc0347b80) on processor 0 due to Keyboard Entry[0]kdb></pre><p>注意, 在kdb運行時內(nèi)核停止任何東西. 在你調(diào)用 kdb 的系統(tǒng)中不應當運行其他東西; 特別, 你不應當打開網(wǎng)絡 -- 除非, 當然, 你在調(diào)試一個網(wǎng)絡驅(qū)動. 一般地以單用戶模式啟動系統(tǒng)是一個好主意, 如果你將使用 kdb.</p><p>作為一個例子, 考慮一個快速 scull 調(diào)試會話. 假設驅(qū)動已經(jīng)加載, 我們可以這樣告訴 kdb 在 sucll_read 中設置一個斷點:</p><pre class="screen">[0]kdb> bp scull_readInstruction(i) BP #0 at 0xcd087c5dc (scull_read) is enabled globally adjust 1
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -