`

【Linux 驱动】第四章 调试技术

 
阅读更多
一,内核中的调试支持
在内核配置菜单中有“kernel hacking”菜单选项,这些选项帮助用户检查很多错误,这里我列了一个表方便大家参考:
kernel hacking 在哪里?
~# cd /usr/src/linux-source.3.0.0
~#make menuconfig 则出现
查找USB驱动的方法

1)cd linux-source-3.0.0

2)lsusb /*查看所有连接到系统的USB设备*/

3)拔掉USB设备,然后再运行一遍lsusb命令,这样就可以确定以上哪条信息是针对你的新硬件的了。

Bus 002 Device 004: ID 1e3d:2093 /*我的硬件识别信息*/

其中ID 1e3d:2093这个信息对我们很有用处,我们需要用它来查找内核中与硬件匹配的信息。1e3d代表的是厂商ID,就是哪家厂商。2093是硬件ID。

下面开始用1e3d搜索内核源码树:

4)grep -i -R-l 0403 drivers

drivers/usb/serial/ftdi_sio.mod.o
drivers/usb/serial/ftdi_sio.ko
drivers/usb/serial/ftdi_sio.mod.c
drivers/watchdog/pcwd_pci.ko
drivers/watchdog/pcwd_pci.mod.c
drivers/watchdog/pcwd_pci.mod.o
drivers/bluetooth/btusb.ko
drivers/bluetooth/btusb.mod.c
drivers/bluetooth/btusb.mod.o
drivers/media/rc/keymaps/rc-hauppauge.c
drivers/media/rc/keymaps/rc-dib0700-rc5.c
drivers/media/dvb/dvb-usb/dvb-usb-vp702x.mod.c
drivers/media/dvb/dvb-usb/nova-t-usb2.c
drivers/media/dvb/dvb-usb/dvb-usb-vp702x.ko
drivers/media/dvb/dvb-usb/dvb-usb-vp702x.mod.o

该命令执行完后,会在屏幕上显示若干条以.data .c .h等为结尾的文件,比如drivers/usb/serial/ftdi_sio.ko不用看最后一部分,前三个目录名就可以确定这是个USB串口设备。同样的判断方法,我们就可以确定我们需要的内核文件了。以防万一,我们进入这个文件中,USB驱动告诉内核它们支持哪些谁被,以便内核可以把驱动绑定到设备上。一般在一个结构体变量中列出制造商ID和设备ID。如果我们设备的制造商ID和设备ID在里面的话,说明这个驱动支持我们的硬件设备。

cd linux-3.0.0 /*进入内核文件中*/

find –type f –name Makefile | xargs grep XXXXX/*会显示一个以CONFIG_为前缀的字段*/

找到这个字段后,返回内核Makefile文件中,使用内核配置工具menuconfig,搜索这个字段。最后在该程序菜单中相应位置启动这个驱动。


二,printk打印调试
在应用程序中,我们也经常使用这种经典的打印调试技术。在内核中,printk用来完成相同的工作。
printk与printf的一个不同就是,前者可以对消息进行分类,表示日志级别的宏会自动展成一个字符串,用到的级别有以下几种:
KERN_EMERG:紧急事件,系统崩溃前提示的消息。
KERN_ALERT: 立即采取动作的情况。
KERN_CRIT: 涉及到严重的硬件或者软件操作失败。
KERN_ERR:用于报告错误状态,驱动程序中用于报告来自硬件的问题。
KERN_WARNING: 不会造成系统严重问题的一般警告。
KERN_NOTICE: 进行提示的正常情形。
KERN_INFO: 提示性信息,驱动程序一般提示找到了硬件信息。
KERN_DEBUG: 用于debug。
一般我们需要将这些调试信息打印到控制台上,而控制台本身有一个日志级别(默认为DEFAULT_CONSOLE_LOGLEVEL),printk制定的级别必须数值上小于该默认值且以newline结束才能打印到控制台上,所以我们有时候需要更改控制台的日志级别,比如下面可以将所有的printk消息输出到控制台:
echo 8 > /proc/sys/kernel/printk

如何关闭打开调试信息?
技巧:通过ifdef定义宏来完成,需要编译debug信息在makefile中打开此宏,否则关闭。
速度限制?
为了防止大量产生log信息,导致在某些慢速设备上出现系统假死的情况,使用内核函数
int printk_ratelimit(void) 返回非0值,可以继续打印,如果输出速度超过一个值,返回0避免发送重复消息。
if(printk_ratelimit())
printk(KERN_NOTICE"The printer is still on fire\n");
打印设备编号
int print_dev_t(char *buffer,dev_t dev); //返回打印字符数
char *format_dev_t(char *buffer, dev_t dev); //返回缓冲区
三,使用proc文件系统进行调试
/proc 文件系统是一种特殊的,由软件创建的文件系统,内核使用它向外界导出信息。/proc 下面的每个文件都绑定于一个内核函数,用户读取其中的文件时,该函数动态完成文件内容。
所有proc模块需要包含#include<linux/fs_proc.h>
内核函数接口:
  1. struct proc_dir_entry*create_proc_read_entry(constchar*name,mode_t mode,
  2. struct proc_dir_entry*base,
  3. read_proc_t*read_proc,void*data);
  4. /*创建proc文件接口
  5. **name:要创建的文件名称
  6. **mode:该文件的保护码,0表示系统默认值
  7. **base:指定文件所在目录,base如果为NULL,表示在proc根目录下创建该文件
  8. **read_proc:该文件的read_proc函数,读取该文件时调用
  9. **data:传递给read_proc的参数
  10. */
    1. int(*read_proc)(char*page,char**start,off_t offset,intcount,int*eof,void*data);
    2. /*
    3. **page:指针指向用来写入数据的缓冲区
    4. **start:返回实际的数据写到内存页得哪个位置
    5. **offset,count:和read方法一样
    6. **eof:指向一个整形数,当没有数据可返回时,驱动程序必须设置这个参数
    7. **data:提供给驱动程序的专用数据指针,可用于内部记录
    8. */
    9. 返回值:必须返回存放到内存页缓冲区的字节数,*eof和*start也属于返回值
    //由于内核信任驱动程序,因此不会检查某个名称是否已经被注册,所以有可能导致注册同名入口项
    1. void remove_proc_entry(constchar*name,struct proc_dir_entry*base)//如果删除已卸载模块,内核会崩溃
    2. /*移除proc文件
    3. **name:文件名
    4. **base:目录,和创建前面一样
    5. */
    针对proc文件的不足而诞生了Seq_file
    Seq_file的实现基于proc文件。使用Seq_file,用户必须抽象出一个链接对象,然后可以依次遍历这个链接对象。这个链接对象可以是链表,数组,哈希表等等。
    编程接口
    Seq_file必须实现四个操作函数:start(), next(), show(), stop()。
    structseq_operations{
    void*(*start)(structseq_file*m,loff_t*pos);
    void(*stop)(structseq_file*m,void*v);
    void*(*next)(structseq_file*m,void*v,loff_t*pos);
    int (*show)(structseq_file*m,void*v);
    };
    start():
    主要实现初始化工作,在遍历一个链接对象开始时,调用。返回一个链接对象的偏移或者SEQ_START_TOKEN(表征这是所有循环的开始)。出错返回ERR_PTR。
    stop():
    当所有链接对象遍历结束时调用。主要完成一些清理工作。
    next():
    用来在遍历中寻找下一个链接对象。返回下一个链接对象或者NULL(遍历结束)。
    show():
    对遍历对象进行操作的函数。主要是调用seq_printf(), seq_puts()之类的函数,打印出这个对象节点的信息。
    下图描述了seq_file函数对一个链表的遍历。
    2、重要的数据结构
    除了struct seq_operations以外,另一个最重要的数据结构是structseq_file
    structseq_file{
    char *buf;
    size_t size;
    size_t from;
    size_t count;
    loff_t index;
    u64 version;
    struct mutex lock;
    const struct seq_operations *op;
    void *private;
    };
    该结构会在seq_open函数调用中分配,然后作为参数传递给每个seq_file的操作函数。Privat变量可以用来在各个操作函数之间传递参数。
    3、Seq_file使用示例:
    #include/* for use of init_net*/
    #include/* We're doing kernel work */
    #include/* Specifically, a module */
    #include/* Necessary because we use proc fs */
    #include/* forseq_file*/
    #definePROC_NAME"my_seq_proc"
    MODULE_LICENSE("GPL");
    staticvoid*my_seq_start(structseq_file*s,loff_t*pos)
    {
    staticunsignedlongcounter=0;
    printk(KERN_INFO"Invoke start\n");
    /* beginning a new sequence ? */
    if(*pos==0)
    {
    /* yes => return a non null value to begin the sequence */
    printk(KERN_INFO"pos == 0\n");
    return&counter;
    }
    else
    {
    /* no => it's the end of the sequence, return end to stop reading */
    *pos=0;
    printk(KERN_INFO"pos != 0\n");
    returnNULL;
    }
    }
    staticvoid*my_seq_next(structseq_file*s,void*v,loff_t*pos)
    {
    unsignedlong*tmp_v=(unsignedlong*)v;
    printk(KERN_INFO"Invoke next\n");
    (*tmp_v)++;
    (*pos)++;
    returnNULL;
    }
    staticvoidmy_seq_stop(structseq_file*s,void*v)
    {
    printk(KERN_INFO"Invoke stop\n");
    /* nothing to do, we use a static value in start() */
    }
    staticintmy_seq_show(structseq_file*s,void*v)
    {
    printk(KERN_INFO"Invoke show\n");
    loff_t*spos=(loff_t*)v;
    seq_printf(s,"%Ld\n",*spos);
    return0;
    }
    staticstructseq_operations my_seq_ops={
    .start=my_seq_start,
    .next=my_seq_next,
    .stop=my_seq_stop,
    .show=my_seq_show
    };
    staticintmy_open(structinode*inode,structfile*file)
    {
    returnseq_open(file,&my_seq_ops);
    };
    staticstructfile_operations my_file_ops={
    .owner=THIS_MODULE,
    .open=my_open,
    .read=seq_read,
    .llseek=seq_lseek,
    .release=seq_release
    };
    intinit_module(void)
    {
    structproc_dir_entry*entry;
    entry=create_proc_entry(PROC_NAME,0,init_net.proc_net);
    if(entry){
    entry->proc_fops=&my_file_ops;
    }
    printk(KERN_INFO"Initialze my_seq_proc success!\n");
    return0;
    }
    /**
    * This function is called when the module is unloaded.
    *
    */

    voidcleanup_module(void)
    {
    remove_proc_entry(PROC_NAME,init_net.proc_net);
    printk(KERN_INFO"Remove my_seq_proc success!\n");
    }
    该程序在/proc/net下注册一个my_seq_proc文件。
    四,通过监视调试
    有时候监视用户空间应用程序的运行情况,可以捕捉到一些小问题。
    strace工具,可以显示用户空间程序所发出的所有系统调用,并显示调用参数以及字符串形式的返回值。
    常用参数:
    -t 显示调用发生时间
    -T 显示调用所花费的时间
    -e 限定被跟踪的调用类型
    -o 将输出重定向到一个文件
    五,oops消息
    大部分错误是因为对NULL指针值取值或者因为使用了其他不正确的指针指,这些错误将导致oops消息。
    oops产生原因:

    1. 引用空指针

    Unable to handle kernel NULL pointer dereference at virtual address 00000000
    printing eip:
    d083a064
    Oops: 0002 [#1]
    SMP
    CPU: 0
    EIP: 0060:[<d083a064>] Not tainted
    EFLAGS: 00010246 (2.6.6)
    EIP is at faulty_write+0x4/0x10 [faulty]
    eax: 00000000 ebx: 00000000 ecx: 00000000 edx: 00000000
    esi: cf8b2460 edi: cf8b2480 ebp: 00000005 esp: c31c5f74
    ds: 007b es: 007b ss: 0068
    Process bash (pid: 2086, threadinfo=c31c4000 task=cfa0a6c0)
    Stack: c0150558 cf8b2460 080e9408 00000005 cf8b2480 00000000 cf8b2460 cf8b2460
    fffffff7 080e9408 c31c4000 c0150682 cf8b2460 080e9408 00000005 cf8b2480
    00000000 00000001 00000005 c0103f8f 00000001 080e9408 00000005 00000005
    Call Trace:
    [<c0150558>] vfs_write+0xb8/0x130
    [<c0150682>] sys_write+0x42/0x70
    [<c0103f8f>] syscall_call+0x7/0xb
    Code: 89 15 00 00 00 00 c3 90 8d 74 26 00 83 ec 0c b8 00 a6 83 d0

    这个错误消息比较明显的,指到了空指针,位置在faulty_write 后 四个字节。

    2. 堆栈被破坏

    EIP: 0010:[<00000000>]
    Unable to handle kernel paging request at virtual address ffffffff
    printing eip:
    ffffffff
    Oops: 0000 [#5]
    SMP
    CPU: 0
    EIP: 0060:[<ffffffff>] Not tainted
    EFLAGS: 00010296 (2.6.6)
    EIP is at 0xffffffff
    eax: 0000000c ebx: ffffffff ecx: 00000000 edx: bfffda7c
    esi: cf434f00 edi: ffffffff ebp: 00002000 esp: c27fff78
    ds: 007b es: 007b ss: 0068
    Process head (pid: 2331, threadinfo=c27fe000 task=c3226150)
    Stack: ffffffff bfffda70 00002000 cf434f20 00000001 00000286 cf434f00 fffffff7
    bfffda70 c27fe000 c0150612 cf434f00 bfffda70 00002000 cf434f20 00000000
    00000003 00002000 c0103f8f 00000003 bfffda70 00002000 00002000 bfffda70
    Call Trace:
    [<c0150612>] sys_read+0x42/0x70
    [<c0103f8f>] syscall_call+0x7/0xb
    Code:Bad EIP value.

    这个错误信息比较隐晦的。 说的是,找不到一个虚拟地址。 EIP一看就是个乱七八糟的值。

    call trace不完整,只指示到了 sys_read。

    造成错误的源代码是:

    ssize_t faulty_read(struct file *filp, char __user *buf,size_t count, loff_t *pos)
    {
    int ret;
    char stack_buf[4];
    /* Let's try a buffer overflow */
    memset(stack_buf, 0xff, 20);
    if (count > 4)
    count = 4; /* copy 4 bytes to the user */
    ret = copy_to_user(buf, stack_buf, count);
    if (!ret)
    return count;
    return ret;
    }

    处理器使用的几乎都是虚拟地址,这些地址通过mmu转换成物理地址,当引用一个非法指针时,分页机制无法将该地址映射到物理地址,此时处理器就会向os发出一个页面失效的信号,如果是非法地址,内核就无法换入缺失页面,如果这时处理器处于超级用户模式,系统就会产生一个oops。
    oops显示错误发生时处理器的状态
    EIP 指令指针
    六,调试器gdb
    跟踪代码调试是比较耗时的,所以不到万不得已感觉不要走这一步
    调试内核和应用程序很不一样,用gdb对内核进行调试许多常用功能不能使用,比如设置断点观察点,单步跟踪内核函数。
    一个典型的gdb调试内核的命令如下:
    gdb /usr/src/linux/vmlinux /proc/kcore
    第一个是内核ELF可执行文件,不是经过压缩的zImage
    第二个是core文件的名字
    linux下的可装载模块是ELF格式的可执行映像,对于调试来讲,关心下面三个段:.text .bass .data
    而这三个段的信息可以在/sys/modules/scull/sections中获得,然后gdb需要做的就是:
    add-symbol-file ./scull.ko 0xd0832000 -s .bss 0xd0837100 -s .data 0xd0836e0
    有用的技巧命令
    print *(address)为address传入一个十六进制的地址值,输出是该地址对应的文件以及代码行数。
    这样的话,我们可以找到某个函数指针所指的函数定义在什么地方~~~
    7.kdb补丁
    kdb是内核内置的调试器,要获得对应内核版本的补丁程序,进行patch后重新编译内核,可以在控制台按下pause或者break键进入调试状态,如果当内核发生oops或者到达摸个断点,也会启动kdb,进入下面的状态:
    Entering kdb(0xc0234580)on processor 0 due to keyboard Entry
    [0]kdb>
    当kdb运行时,内核做的每一件事都会停下,注意不要开启网络功能,除非是在调试网络驱动,一般进入单用户模式进行kdb调试内核。
    bp设置断点
    go表示继续执行
    bt查看backtrace
    mds对数据进行处理
    mm addr value 修改addr的数据为value

    分享到:
    评论

    相关推荐

      LINUX 设备驱动程序(第二版)

      第4章 调试技术 第5章 字符设备驱动程序的扩展操作 第6章 时间流 第7章 获取内存 第8章 硬件管理 第9章 中断处理 第10章 合理使用数据类型 第11章 kerneld和高级模块化 第12章 加载快设备驱动程序 第13章 MMAP和DMA ...

      精通LINUX设备驱动程序开发

       第4章 基本概念   第5章 字符设备驱动程序   第6章 串行设备驱动程序   第7章 输入设备驱动程序   第8章 I2C协议  第9章 PCMCIA和CF   第10章 PCI   第11章 USB  第12章 视频驱动程序   ...

      Linux设备驱动详解第二版

      Linux设备驱动详解【第二版】,作者宋宝华,此版PDF是经过本人整理的文字版PDF,带目录、高清无水印版。...第4篇 Linux设备驱动调试、移植 第22章 Linux设备驱动的调试 564 第23章 Linux设备驱动的移植 602

      精通Linux设备驱动程序开发

      第4章 基本概念 第5章 字符设备驱动程序 第6章 串行设备驱动程序 第7章 输入设备驱动程序 第8章 I2C协议 第9章 PCMCIA和CF 第10章 PCI 第11章 USB 第12章 视频驱动程序 第13章 音频驱动程序 第14章 块...

      嵌入式linux设备驱动(共17章)

      第4章 调试技术 第5章 字符设备驱动程序的扩展操作 第6章 时间流 第7章 获取内存 第8章 硬件管理 第9章 中断处理 第10章 合理使用数据类型 第11章 kerneld和高级模块化 第十二章 加载快设备驱动程序 第十三章 MMAP...

      国嵌培训课件Linux驱动程序设计

      第一天 1.Linux驱动简介 2.字符设备驱动程序设计 3.驱动调试技术 4. 并发与竞态 第二天 1.Ioctl型驱动 2.内核等待队列 3. 阻塞型驱动程序设计 4.Poll设备操作 第三天 1.Mmap设备操作 2. 硬件访问 3. 混杂...

      嵌入式Linux应用开发完全手册有目录2

       第18章 Linux内核调试技术 第4篇 嵌入式Linux设备驱动开发篇  第19章 字符设备驱动程序  第20章 Linux异常处理体系结构  第21章 扩展串口驱动程序移植  第22章 网卡驱动程序移植  第23章 IDE...

      linux驱动开发中文版

      第四章 调试技术.doc 第五章 字符设备驱动程序的扩展操作.doc 第六章 时间流.doc 第七章 获取内存.doc 第八章 硬件管理.doc ... ... ... 第十六章 核心源码的物理布局.doc 第十七章 最新进展.doc

      嵌入式设计及linux驱动开发指南——基于ARM9处理器.pdf

      第4章 调试嵌入式系统程序 4.1 嵌入式系统调试方法 4.1.1 实时在线仿真 4.1.2 模拟调试 4.1.3 软件调试 4.1.4 BDM/JTAG调试 4.2 ARM仿真器 4.2.1 techorICE ARM仿真器 4.2.2 ARM仿真器工作原理 4.2.3 ARM...

      《精通Linux 设备驱动程序开发》.(Sreekrishnan).pdf

      第4章 基本概念61 4.1 设备和驱动程序介绍61 4.2 中断处理63 4.2.1 中断上下文63 4.2.2 分配irq号64 4.2.3 设备实例:导航杆65 4.2.4 softirq和tasklet68 4.3 linux设备模型71 4.3.1 udev71 4.3.2...

      LINUX设备驱动第三版_588及代码.rar

      第四章 调试技术 内核中的调试支持 通过打印调试 通过查询调试 通过监视调试 调试系统故障 调试器和相关工具 第五章 并发和竞态 scull的缺陷 并发及其管理 信号量和互斥体 completion 自旋锁 锁陷阱 ...

      嵌入式Linux应用开发完全手册

      第4章 Windows、Linux环境下相关工具、命令的使用 第2篇 ARM9嵌入式系统基础实例篇 第5章 GPIO接口 第6章 存储器控制 第7章 内存管理单元MMU 第8章 NAND Flash控制器 第9章 中断体系结构 第10章 系统时钟和定时器 ...

      华清远见驱动教程

      -第4章、Linux内核模块 -第5章、Linux文件系统与设备文件系统 -第6章、字符设备驱动 -第7章、Linux设备驱动中的并发控制 -第8章、Linux设备驱动中的阻塞与非阻塞IO -第9章、Linux设备驱动中的异步通知与异步IO -第10...

      linux内核调试技术

      在我看linux设备驱动程序第4章调试技术的时候,一开始就遇到了问题,怎么在内核中开启调试选项,这本书帮我解决了。所以共享给同样遇到这样问题的朋友。也谢谢书的作者。

      嵌入式设计及linux驱动开发指南——基于ARM.

      第四章:创建嵌入式系统开发环境;第五章:Bootloader;第六章:linux系统在ARM平台的移植;第七章:linux设备驱动程序开发;第八章:网络设备驱动程序开发;第九章:USB驱动程序开发;第十章:图形用户接口;第十一...

      Linux设备驱动程序第三版2.6

      第 1 章 第一章 设备驱动简介 第 2 章 建立和运行模块 第 3 章 字符驱动 第 4 章 调试技术 第 5 章 并发和竞争情况 第 6 章 高级字符驱动操作 第 7 章 时间, 延时, 和延后工作

      嵌入式Linux应用开发完全手册有目录1(共2个)

       第4章 Windows、Linux环境下相关工具、命令的使用 第2篇 ARM9嵌入式系统基础实例篇  第5章 GPIO接口  第6章 存储器控制  第7章 内存管理单元MMU  第8章 NANDFlash控制器  第9章 中断体系结构 ...

    Global site tag (gtag.js) - Google Analytics