290 likes | 388 Views
CHAP 4. 訊息除錯法 (1). printk() 函式 通用的除錯技巧 , 對應用程式而言使用 printf(); 對於核心程式而言則使用 printk(). printk() 能讓你指定訊息的 loglevel( 等級 ), 共分為八纇 . 定義在 <linux/kernel.h> 裡 . • KERN_EMERG : 緊急訊息 , 出現在系統崩潰前 . • KERN_ALERT : 危險通知 , 發生在需要立即採 取行動的事件 .
E N D
訊息除錯法(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_toffset,intcount,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需宣告如下: /proc入口點名稱 static void scull_create_proc() { create_proc_read_entry(“scullmem”, 0 /* 預設模式(0x444) */, NULL /* 上層目錄(*proc_dir_entry)*/, scull_read_procmem, NULL /* 提供給read_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檔案系統) •ex:在/proc子目錄下( /proc/driver)建立新入口點 static void scull_create_proc() { create_proc_read_entry(“scullmem”, 0 /* 預設模式(0x444) */, proc_root_driver/* 代表/proc/driver子目錄*/, scull_read_procmem, NULL /* 提供給read_proc使用的資料 */); }
查詢除錯法(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程式所發出的所有system 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 00 Code; df8a90ed <[faulty]faulty_write+d/14> a: 31 c0 xor %eax,%eax Code; df8a90ef <[faulty]faulty_write+f/14> c: c9 leave Code; df8a90f0 <[faulty]faulty_write+10/14> d: c3 ret Code; df8a90f1 <[faulty]faulty_write+11/14> e: 8d 76 00 lea 0x0(%esi),%esi Code; df8a90f4 <[faulty]faulty_init+0/0> 11: 55 push %ebp Code; 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/cgi-bin/info2html?(gdb)Top
0xc8002060: push %ebp 0xc8002061: mov %esp,%ebp 0xc8002063: sub $0x8,%esp 0xc8002066: sub $0xc,%esp 0xc8002069: push $0xc8002098 0xc800206e: call 0xc0114e50 <printk> 0xc8002073: add $0x10,%esp
其他核心除錯器: • kdb • kgdb