1. 程式人生 > >核心程式除錯手段 >>Linux裝置驅動程式

核心程式除錯手段 >>Linux裝置驅動程式

文章目錄

好難啊!這些東西完全不知道該那些地方入手,只能硬著頭皮吸收一些自己可以看得懂的;
有些看得懂,有些看不懂,都不知道怎麼查,總算知道為啥底層工程師這麼少了;
可不能放棄,核心打我千百遍,我當核心是我爹,混熟了應該就好了吧!

[0x100]常用核心除錯方式

[0x110]核心的DEBUG選項

make menuconfig 或者kernel_root_dir/.config 編譯核心時,提供的DEBUG 選項及其作用
例項核心版本:kernel-3.4.39 (高亮為例項不支援)

  1. CONFIG_DEBUG_KERNEL :預設常用DEBUG方式;
  2. CONFIG_KALLSYMS :oops 輸出開啟核心標籤符號資訊,
  3. CONFIG_DEBUG_SPINLOCK_ :自旋鎖操作錯誤捕獲 [呼叫休眠\使用為初始化鎖\重解鎖等];
  4. CONFIG_DEBUG_DRIVER
  5. CONFIG_MAGIC_SYSRQ
  6. CONFIG_DEBUG_PAGEALLOC : 釋放時移除所有記憶體頁,會導致系統性能降低,僅測試開啟
  7. CONFIG_IKCONFIG_ : 核心配置資訊輸出到/PROC 目錄,僅測試開啟
  8. CONFIG_PROFILING : 跟蹤核心掛起,效能除錯,僅測試開啟
  9. CONFIG_INIT_DEBUG :檢測__init 執行完成後對初始化記憶體段的違規訪問;
  10. CONFIG_DEBUG_INFO + CONFIG_FRAME_POINTER : 使用gdb 除錯核心的選項;
  11. CONFIG_DEBUG_SLAB :新增記憶體界限防護值以檢查記憶體溢位錯誤;
  12. CONFIG_DEBUG_STACKOVERFLOW + CONFIG_STACK_USAGE

[0x120]核心列印函式 >>printk

[0x121]預設規則

  • 日誌級別:printk擁有8種日誌級別,定義於<linux/kernel.h>;
  • 輸出條件:必須以‘ \n’ 字元結尾,否則將輸出到是/var/log/messages;
  • 緩衝型別: __LOG_BUF_LEN 大小的迴圈緩衝區 儲存於/proc/kmsg;

[0x122]終端列印日誌級別配置

  1. 數值1 :標識優先順序高於該等級的日誌將被輸出到螢幕;
  2. 數值2 :標識預設printk 輸出日誌等級;
  3. 數值3 :最高日誌優先順序;
  4. 數值4 :最低日誌優先順序;
  5. 引數範圍 :1-8 對應 Level [0-7];
  6. 配置檔案:優先順序指定通常由==/proc/sys/kernel/printk== 檔案指定;

[0x123]輸出限制

  • 速度限制:/proc/sys/kernel/printk_ratelimit
  • 間隔限制:/proc/sys/kernel/printk_ratelimit_burst
  • 關聯函式:int printk_ratelimit(void)
  • 函式作用: 根據 printk_ratelimit 檔案中設定的閾值,列印次數低於閾值返回1,超過閾值返回0;

[0x130]DEBUG 開關

[0x131] 編譯於程式碼

#undef PDEBUG                /*取消該巨集名稱的定義,防止重複宣告*/
#ifdef device_debug          /*開啟debug標識*/
   #ifdef __kernel__         /*處於核心空間 執行的debug方式 基於printk*/
        #define PDEBUG(fmt,args...) printk(KERN_DEBUG "device_name:"fmt,##args)
      #else                  /*處於使用者空間 執行的debug方式 基於fprintf*/
         #define PDEBUG(fmt,args...) fprintf(stderr,fmt,##args)
   #endif
#else                       /*關閉debug標識,去掉巨集定義的內容不與輸出到偽終端*/
    #define PDEBUG(fmt,args...)
#endif                   

[0x132] 應用於MAKEFILE

	DEBUG := y
	ifeq($(DEBUG),y)
	DEBFLAGS = -O -g -Ddevice_debug 
	else
	DEBFLAGS = -O2
	endif
	CFLAGS +=  $(DEBFLAGS)

[0x200]虛擬檔案系統 /PROC

使用者空間向核心請求關於核心屬性與日常裝置需求的資料的只讀介面
建立呼叫:實現proc_read 函式–>建立proc檔案入口並關聯Proc_read
銷燬呼叫:呼叫巨集 remove_proc_entry(name, parent)

[0x210]原生proc檔案操作函式介面

[0x211]建立proc目錄檔案引數解析

建立核心資料入口:繫結Proc_Read函式後可以獲取核心資料;
args 1 :虛擬系統檔名,又稱入口名
args 2 :proc檔案的保護屬性,預設為0
args 3 :指定建立proc檔案的根目錄,如果base =NULL 則在 proc 根目錄建立;L;
return :返回為proc 目錄結構指標

#include <linux/proc_fs.h>
/*建立proc 檔案入口 需要繫結read函式*/
struct proc_dir_entry *create_proc_entry(const char *name, umode_t mode,struct proc_dir_entry *parent)
{
        struct proc_dir_entry *ent;
        nlink_t nlink;

        if (S_ISDIR(mode)) {
                if ((mode & S_IALLUGO) == 0)
                        mode |= S_IRUGO | S_IXUGO;
                nlink = 2;
        } else {
                if ((mode & S_IFMT) == 0)
                        mode |= S_IFREG;
                if ((mode & S_IALLUGO) == 0)
                        mode |= S_IRUGO;
                nlink = 1;
        }   

        ent = __proc_create(&parent, name, mode, nlink);
        if (ent) {
                if (proc_register(parent, ent) < 0) {
                        kfree(ent);
                        ent = NULL;
                }   
        }   
        return ent;
}

[0x212]關於Proc_Read函式引數解析

核心資料讀取:驅動程式呼叫此函將資料,傳回到核心分配的PAGE_SIZE使用者空間記憶體頁;
args 1 :開始放置虛擬檔案資料的記憶體頁起始位置
args 2 :向用戶空間寫入的資料,從記憶體頁的哪裡開始讀取;
args 3 :如果*start為非NULL,則page +offset =start,否則為0;
args 4 : 需要傳送的位元組數量;
args 5 :沒有資料返回時,驅動程式設定數值;
args 6 :使用者空間傳遞的引數;
return :返回成功成功放置到記憶體頁緩衝區的位元組數;

#include <linux/proc_fs.h>
/*Read函式宣告指標*/
int (*read_proc)
(char *page,char **start,off_t offset,int count,int *eof,void *data);

[0x213]解除安裝proc目錄檔案引數解析

解除安裝資料入口:通常在驅動被解除安裝時呼叫執行該函式定義;
args 1 : 虛擬系統檔名,又稱入口名
args 2 : 指定建立proc檔案的根目錄,如果parent =NULL 則從 proc 根目錄移除檔案;
return :空

#include <linux/proc_fs.h>
/*implement kernel_root_dir/fs/proc/generic.c:784*/
void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
{       
        struct proc_dir_entry **p;
        struct proc_dir_entry *de = NULL;
        const char *fn = name;
        unsigned int len;
        
        spin_lock(&proc_subdir_lock);
        if (__xlate_proc_name(name, &parent, &fn) != 0) {
                spin_unlock(&proc_subdir_lock);
                return;
        }
        len = strlen(fn);

        for (p = &parent->subdir; *p; p=&(*p)->next ) {
                if (proc_match(len, fn, *p)) {
                        de = *p;
                        *p = de->next;
                        de->next = NULL;
                        break;
                }
        }
        spin_unlock(&proc_subdir_lock);
        if (!de) {
                WARN(1, "name '%s'\n", name);
                return;
        }

        spin_lock(&de->pde_unload_lock);
        /*
         * Stop accepting new callers into module. If you're
         * dynamically allocating ->proc_fops, save a pointer somewhere.
         */
        de->proc_fops = NULL;
        /* Wait until all existing callers into module are done. */
        if (de->pde_users > 0) {
                DECLARE_COMPLETION_ONSTACK(c);

                if (!de->pde_unload_completion)
                        de->pde_unload_completion = &c;

                spin_unlock(&de->pde_unload_lock);

                wait_for_completion(de->pde_unload_completion);

                spin_lock(&de->pde_unload_lock);
        }

        while (!list_empty(&de->pde_openers)) {
                struct pde_opener *pdeo;

                pdeo = list_first_entry(&de->pde_openers, struct pde_opener, lh);
                list_del(&pdeo->lh);
                spin_unlock(&de->pde_unload_lock);
                pdeo->release(pdeo->inode, pdeo->file);
                kfree(pdeo);
                spin_lock(&de->pde_unload_lock);
        }
        spin_unlock(&de->pde_unload_lock);

        if (S_ISDIR(de->mode))
                parent->nlink--;
        de->nlink = 0;
        WARN(de->subdir, KERN_WARNING "%s: removing non-empty directory "
                        "'%s/%s', leaking at least '%s'\n", __func__,
                        de->parent->name, de->name, de->subdir->name);
        pde_put(de);
}

[0x220]對於大量輸出行裝置處理

  1. 定義於 <linux/seq_file.h>
  2. 填充 struct seq_operations 結構,構建專案迭代器的生命週期;
  3. 填充 struct file_operations 結構,構建proc 檔案的讀寫函式
  4. 呼叫 create_proc_entry(),建立proc 檔案

[0x221] seq_file 相關資料結構

#include <linux/seq_file.h> 
struct seq_operations {
        /*迭代器的入口,初始化函式指標實現,*/
        void * (*start) (struct seq_file *m, loff_t *pos);
        /*使用結束的清除工作*/
        void   (*stop) (struct seq_file *m, void *v);
        /*移動到下一個迭代器的位置,pos 為cdev的偏移量 如果pos 大於裝置數量,則返回NULL*/
        void * (*next) (struct seq_file *m, void *v, loff_t *pos);
        /*用於將實際的資料輸出到使用者空間*/
        int    (*show) (struct seq_file *m, void *v);
};
 #include <linux/fs.h>
 struct file_operations {
            loff_t  (*llseek)  (struct file *, loff_t, int);
            ssize_t (*read)    (struct file *, char __user *, size_t, loff_t *);
            int     (*open)    (struct inode *, struct file *);
            int     (*release) (struct inode *, struct file *);
 };           

[0x222]seq_file 相關函式

#include <linux/seq_file.h>
/*只有seq_*的函式可以在 seq_file 中使用,以下函式等同於對應的應用層函式。*/
int seq_putc(struct seq_file *sfile,char c);
int seq_puts(struct seq_file *sfile,const char *s);
int seq_escape(struct seq_file *m,const char *s,const char *esc);
int seq_printf(struct seq_file *sfile,const char * fmt,...);

[0x300]其他可選的方式

  1. strace 命令可以用來除錯正在執行應用程式,顯示處理引數返回值等等;
  2. oops 常用於發現核心一些 NULL指標位置與不正確的指標數值
  3. Sysrq 強大響應核心假死的魔法鍵
  4. gdb 32位核心的跟蹤