内核程序调试手段 >>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位内核的跟踪

猜你喜欢

转载自blog.csdn.net/god1992/article/details/85089375