Upload
nevina
View
18
Download
2
Embed Size (px)
DESCRIPTION
CHAP 4. 訊息除錯法 (1). printk() 函式 通用的除錯技巧 , 對應用程式而言使用 printf(); 對於核心程式而言則使用 printk(). printk() 能讓你指定訊息的 loglevel( 等級 ), 共分為八纇 . 定義在 裡 . • KERN_EMERG : 緊急訊息 , 出現在系統崩潰前 . • KERN_ALERT : 危險通知 , 發生在需要立即採 取行動的事件 . - PowerPoint PPT Presentation
Citation preview
CHAP 4
訊息除錯法 (1)
printk() 函式 通用的除錯技巧 ,對應用程式而言使用 printf();對於核心程式而言則使用 printk().
printk() 能讓你指定訊息的 loglevel( 等級 ),共分為八纇 .定義在 <linux/kernel.h> 裡 .
•KERN_EMERG: 緊急訊息 ,出現在系統崩潰前 . •KERN_ALERT: 危險通知 ,發生在需要立即採 取行動的事件 . •KERN_CRIT: 嚴重狀況 ,涉及硬體或軟體故障
訊息除錯法 (2)
•KERN_ERR: 錯誤狀況回報 ,通常回報硬體上的困難 .
•KERN_WARNING: 緊急訊息 ,程度上不影響系統 .•NERN_NOTICE: 通知 ,意料中會發生且值得注意的 狀況 .•KERN_INFO: 資訊性訊息 ,driver 在啟動階段所印出
的硬體資訊 .•KERN_DEBUG: 供除錯用途的訊息 .
訊息除錯法 (3) 上述代號展開後分別成為 :<0>,<1>,<2>…<7> 之類的字串 ,括弧內數字越低表示等級越高 .
•ex:printk(KERN_DEBUG “HI”); 不同等級的訊息會被輸出到不同的地點 ,有可能是目前的操控台 (console) 或者是某種文字終端機 (Xterm 視窗 ,Telnet 或 SSH 連線 ).
任何等值低於 console_loglevel 的變數訊息都會被顯示在操控台上 (console).
不過若系統上同時執行 klogd 與 syslogd 不管 console_loglevel 的值為何 ,所有核心訊息都會被加入/var/log/messages.
訊息除錯法 (4)
如果沒跑 klogd, 則訊息不會流入 user-space, 除非主動讀取 /proc/kmsg.
修改 console_loglevel 的值 •使用 sys_syslog() 利用 klogd 的 – c 選項 . •本書範例 :misc-progs/setlevel.c, 2.1.31 版本開始 ,可以直接透 /proc/sys/kernel/
printk 檔案來修改或檢視 console_levle 的值 . • # echo 8 > /proc/sys/kernel/printk
核心訊息的輸出流程 (1)
printk() 會將訊息寫入一個環型 queue,長度為 LOG_BUF_LEN( 定義在 kernel/printk.c), 且喚醒
•正在等待訊息的行程 (使用 syslog()). •正在讀取 /proc/kmesg 的行程 . 在環型 queue 被填滿時 ,printk() 將會繞回原點 ,將新訊息覆蓋在最就訊息上 ,其優點 :
•固定記憶體 ,既使沒跑日誌紀錄引擎 (klogd,syslogd)也不至於消耗所有的記憶體 . •核心內部到處都可以直接呼叫 printk().
核心訊息的輸出流程 (2)
klogd 所取得的核心訊息會被轉交給 syslogd,由他依據 /etc/syslog.conf 來決定如何處裡收到的訊息 .
若系統沒跑 klogd, 核心訊息將會一值留在環型佇列 , 直到有人讀取他或者是被新訊息蓋掉 .
若不希望 driver 所發出得訊息擾亂的系統的日誌檔 ,可以用 -f選項來從新啟動 klogd並將其訊息寫入特定檔案 .
kernel /proc/kmsg [klogd] /dev/log [syslogd] syslog.conf
查詢除錯法 (1)
大量使用 printk() 的結果將導致系統效能變慢 ,因為 syslogd每次必須隨時將取得的核心訊息寫入日誌檔 .
一般而言 ,取得相關資訊的最佳辦法 , 是再必要時才向系統查詢 ,Unix提供許多 ps,netstat,vmstat 等取得系統資訊的工具 .
對 driver設計時而言 ,查詢系統資訊的只要管道有 :
•將資訊輸出到 /proc 檔案系統 . •適用 ioctl作業方式 .
查詢除錯法 (2)( 使用 /proc 檔案系統 )
/proc 是靠軟體模擬出來的特殊檔案系統 ,並不實際存在於硬碟上 , 而是核心提供給 user-space 的資訊窗口 .
在 proc/下的每一個檔案 , 接聯繫到核心內的專屬函式 ,這些函式在使用者讀取檔案時 , 及時產生檔案的內容 .
•ex: 以 /proc/modules 為例 ,當你讀取它時它會顯示目前載入哪些模組 ,但此檔案系統的長度都為 0.
查詢除錯法 (3)( 使用 /proc 檔案系統 )
Linux許多系統工具 , 如 ps,top,uptime 等都是從 /proc 取的它們所需要的資訊 . 建立 /proc 檔案 :driver必須製作一備查程式 , 讓它在檔案被存取的時 , 及時供應資料 , 核心也會配置一記憶頁給它
備查程式介面 :int (*read_proc)(char *page,char **start,off_t offset,int count,int *eof ,void *data);
read() /proc 備查函式 記憶頁 User-space
查詢除錯法 (4)( 使用 /proc 檔案系統 )
page 指標 : 指向核心預先配置的記憶頁 *start 與 offset: 若檔案超過一記憶頁大小 , 可利用分批傳輸的方式 ,先將 *start 指向 page再利用 offset 指向下一各位元組 .
skcull 程式中有對 read_proc 的實作 :
int scull_read_procmem(char *buf, char **start, off_t offset, int count, int *eof, void *data){ int i, j, len = 0; int limit = count - 80; /* Don't print more than this */
for (i = 0; i < scull_nr_devs && len <= limit; i++) { Scull_Dev *d = &scull_devices[i]; if (down_interruptible(&d->sem)) return -ERESTARTSYS; len += sprintf(buf+len,"\nDevice %i: qset %i, q %i, sz %li\n", i, d->qset, d->quantum, d->size); for (; d && len <= limit; d = d->next) { /* scan the list */ len += sprintf(buf+len, " item at %p, qset at %p\n", d, d->data); if (d->data && !d->next) /* dump only the last item - save space */ for (j = 0; j < d->qset; j++) { if (d->data[j]) len += sprintf(buf+len," % 4i: %8p\n",j,d->data[j]); } } up(&scull_devices[i].sem); } *eof = 1; return len;}
查詢除錯法 (5)( 使用 /proc 檔案系統 )
定義好 read_proc作業方式後 ,必須為它在 /proc下設置一個入口點 ,使用 create_proc_read_entry().
若希望使用者能透過 /proc/scullmem 取得該函式提供的資料 ,則 driver 需宣告如下 :static void scull_create_proc()
{ create_proc_read_entry(“scullmem”, 0 /* 預設模式 (0x444) */, NULL /* 上層目錄 (*proc_dir_entry)*/, scull_read_procmem, NULL /* 提供給 read_proc 使用的資料 */);}
/proc 入口點名稱檔案權限
入口點上層目錄
read_proc 作業方法的指標
傳給 read_proc 的資料的指標
查詢除錯法 (6)( 使用 /proc 檔案系統 )
第三參數的說明 : •在 /proc檔案系統下的每一個子目錄 ,都有各自 專屬的proc_dir_entry結構來描述. •ex:描述/proc/driver子目錄的結構為 proc_root_driver,描述/proc/bus子目錄的結 構為proc_bus. •若此引數設定為NULL代表入口點設置在/proc 目錄下 .
查詢除錯法 (7)( 使用 /proc 檔案系統 )
static void scull_create_proc()
{
create_proc_read_entry(“scullmem”,
0 /* 預設模式 (0x444) */,
proc_root_driver/* 代表 /proc/driver 子目錄*/,
scull_read_procmem,
NULL /* 提供給 read_proc 使用的資料 */);
}
•ex: 在 /proc 子目錄下 ( /proc/driver) 建立新入口點
查詢除錯法 (6)( 使用 /proc 檔案系統 )
建立新的 /proc 子目錄 , 利用 proc_mkdir()
struct proc_dir_entry *scull_procdir=NULL;static void scull_create_proc(){ // 建立 /proc/scull 子目錄
scull_procdir=proc_mkdir(“scull”,NULL);create_proc_read_entry(“scullmem”,0,scull_procdir,scull_read_proc,NULL);
}
查詢除錯法 (7)( 使用 /proc 檔案系統 )
在模組被載卸之前必須先移除相關的 /proc 入口點 ,使用 remove_proc_entry()
// 移除 /proc/scullmem; remove_pcor_entry(“scullmem”,NULL);
// 移除 /proc/driver/scullmem; remove_proc_entry(“scullmem”,proc_root_driver);
// 移除 /proc/scull/scullmem; remove_proc_entry(“scullmem”,scull_procdir);
查詢除錯法 (8)( 使用 ioctl)
Ioctl() 是作用在”檔案描述單元 (file descriptor)”的一種 system call.
•比讀取 /proc 的速度快 .
•沒有記憶頁的限制 .
•不同於任何人都可見的 /proc 檔案 ,ioctl 能將擷取除錯資訊的功能留在驅動程式裡面 .
•詳情請期待第五章 .
觀測除錯法
strace, 檢驗位於 kernel-space 的程式碼 . 能顯示出由 user-space 程式所發出的所有 syste
m call. #strace ls /dev > /dev/scull0
排除重大系統錯誤 (1)
除了監視 , 除錯技術 ,驅動程式可能還是意料之外的 bug存在 , 嚴重可能造成 system fault.
fault(失誤 ) 不等於 panic(死當 ),失誤通常會摧毀目前的行程 , 系統本身能正常 .
若 fault 發生在 process context 之外 , 或是破壞系統的關鍵部分 , 即有可能早成 panic.
oops 訊息 :往往發生在不當的操作指標所引起 ,如 dereference(提領 ) 或者是誤用指標的值 .
排除重大系統錯誤 (2)
page fault: 在 protect mode(保護模式 )下 ,所使用的是 virtual address(虛擬記憶體 ),藉由 page table換算出 physical address(實體位置 ), 若程式提領一個無效指標 , 分頁機制則沒有辦法算出實體位置 ,因而發生 page fault.
若在 user-space 發生提領無效 , 後果頂多是無法” page in “該位址 .
若發生在 kernel 則迫使核心發出 oops 訊息 .
排除重大系統錯誤 (2)
範例 :misc-modules/faulty.c
ssize_t faulty_write (struct file *filp, const char *buf, size_t count, loff_t *pos){ /* 提領一個 NULL 指標 , 刻意製造簡單的 fault 狀況 */ *(int *)0 = 0; return 0;} 0 不是合理指標值
排除重大系統錯誤 (3)
oops 訊息包括 :fault 當時 CPU 的狀態 , 包括各站存器的值等…
ksymoops 用法 : systme.map 檔 (-v) /usr/src/linux/system.map 模組清單 (-l): /proc/modules 核心符號表 (-k): /proc/ksyms 映像檔 (-v): 模組 object 檔存放位置 (-o):
指令 :#ksymoops -v /usr/src/linux-2.4.20-8/vmlinux -m /usr/src/linux-2.4.20-8/Systme.map oops.txt
>>EIP; df8a90e3 <[faulty]faulty_write+3/14> <=====Trace; c0145de3 <sys_write+a3/9f0>Trace; c0109537 <__up_wakeup+1097/1470>Code; df8a90e3 <[faulty]faulty_write+3/14>00000000 <_EIP>:Code; df8a90e3 <[faulty]faulty_write+3/14> <===== 0: c7 05 00 00 00 00 00 movl $0x0,0x0 <=====Code; df8a90ea <[faulty]faulty_write+a/14> 7: 00 00 00Code; df8a90ed <[faulty]faulty_write+d/14> a: 31 c0 xor %eax,%eaxCode; df8a90ef <[faulty]faulty_write+f/14> c: c9 leaveCode; df8a90f0 <[faulty]faulty_write+10/14> d: c3 retCode; df8a90f1 <[faulty]faulty_write+11/14> e: 8d 76 00 lea 0x0(%esi),%esiCode; df8a90f4 <[faulty]faulty_init+0/0> 11: 55 push %ebpCode; df8a90f5 <[faulty]init_module+1/44> 12: 89 e5 mov %esp,%ebp
gdb 使用 : 指令 :gdb /usr/src/linux/vmlinux /proc/kcore (gdb) x/i <addr> Ex: (gdb) x/20i 0xc8002060 參考網址 :http://es-sun2.fernuni-hagen.de/cg
i-bin/info2html?(gdb)Top
0xc8002060: push %ebp0xc8002061: mov %esp,%ebp0xc8002063: sub $0x8,%esp0xc8002066: sub $0xc,%esp0xc8002069: push $0xc80020980xc800206e: call 0xc0114e50 <printk>0xc8002073: add $0x10,%esp
其他核心除錯器 : kdb kgdb