《Linux设备驱动程序》——调试技术

本文介绍Linux内核调试的各种方法和技术,包括使用 printk 进行消息输出、通过 /proc 文件系统和 ioctl 实现查询调试、利用 strace 工具进行系统调用跟踪、处理 oops 错误信息以及介绍多种调试工具的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、内核中的调试支持

 1、内核开发的配置选项。(P76-P78)

   1)这些选项都在内核配置工具的“kernel  hacking”菜单中。

   2)、并非所有的体系架构都支持其中的某些选项。



二、通过打印测试

 1、printk

   1)、printk与pritf的不同

      I、日志级别或消息优先级的不同——pritk可以根据消息优先级所表示的严重程度对消息进行分类。

        A、通常采用宏来指示日志级别。(P79)

               每个字符串表示一个尖括号中的整数。整数值范围为0-7,数值越小,优先级越高。

               未指定优先级的printk语句的默认级别为DEFAULT_MESSAGE_LOGLEVEL。

   2)、根据日志级别,内核可能会把消息打印到当前控制台上,这个控制台可以使一个字符模式的终端、一个串口打印机或是一个并口打印机。当优先级小于console_loglevel

             这个整数变量的值,消息才能显示出来,而且每次输出一行。

      I、如果系统同时运行了klogd和syslogd,则无论console_loglevel为何值,内核消息都将追加到/var/log/messages中(否则按照syslogd的配置处理)。

      II、如果klogd没有运行,这些消息就不会传递到用户空间,这种情况下,只能查看/proc/kmsg文件(或通过dmesg命令)。

      II、如果使用了klogd,则应该了解它不会保存连续相同的信息行;它只会八村连续相同的第一行,并在最后打印这一行的重复次数。

   3)、变量console_loglevel的初始值是DEFAULT_MESSAGE_LOGLEVEL,而且还可以通过sys_syslog系统调用修改。

       I、调用klogd时可以指定-c开关项来修改这个变量。

       II、注意,修改其当前值,必须先杀死klogd,然后再用新的-c选项重新启动它。

       III、还可以通过编写程序来改变控制台的日志级别。

   4)、通过文本文件/proc/sys/kernel/printk的访问来读取和修改控制台的日志级别。此文件包含了4个整数值:

       I、当前的日志级别。

       II、未明确指定日志级别时的默认消息级别。

       III、最小允许的日志级别。

       IV、引导时的默认日志级别。


 2、重定向控制台信息

   1)、对于控制台日志策略。Linux允许有某些灵活性:内核可以将消息发送到一个指定的虚拟控制台。默认情况下,“控制台”就是当前的虚拟控制端。

      I、可以在任何一个控制台设备上调用ioctl(TIOCLINUX)来指定接收消息的其他虚拟终端。

      II、setconsole程序。


 3、消息如何被记录

   1)、访问日志引擎接口

      I、syslog系统调用。

      II、/proc/kmsg。  

      III、对/proc/kmsg进行读写操作时,日志缓冲区中被读取的数据就不再保留,而syslog系统调用却能通过选项返回日志数据并保留这些数据,以便其他进程也能使用。

   2)、Linux消息处理方法的另一个特点是,可以在任何地方调用printk,甚至在中断处理函数里也可以调用,而且对数据量的大小没有限制。但缺点是可能丢失某些数据。

   3)、避免扰乱系统日志

      I、为klogd指定-f(file)选项,指示klogd将消息保存到某个特定文件,或者修改/etc/syslog.conf来满足自己的需求。

      II、杀掉klogd,而将消息详细地打印到空间的虚拟终端上,或者在一个为使永的xterm上执行命令cat  /proc/kmsg来显示信息。


 4、开启和关闭信息

   1)、调用printk的编码方法可以个别或全局地开关printk语句:此技巧是定义一个宏,在需要时,这个宏开为一个printk调用。

   2)、实现代码。

   3)、每个驱动程序都会有自身的功能和监视需求。良好的编程技术在于选择灵活性和效率的最佳折衷点。


 5、速度限制

   1)、通常,正式的代码不应该在正常的操作下打印任何信息,而打印出的信息应作为对需要引起注意的异常情形显示。

   2)、在许多情况下,最好的办法是设置一个标志,并在该标志被设置时不再打印任何信息。但在某些情况下,仍然有理由偶尔发出一条“该设备停止工作”这样的信息。内核提

             供了一个有用的函数:

             int  printk_ratelimit(void);


 6、打印编号

   1)、打印设备的主设备号和次设备号,内核提供了一对辅助宏:

       int  print_dev_t(char  *buffer,  dev_t  dev);

       char  *format_dev_t(char  *buffer,  dev_t  dev);



三、通过查询调试

 1、printk打印调试的缺点


 2、对系统进行查询的方法

   1)、在/proc文件系统中创建文件。

   2)、使用驱动程序的ioctl方法。

   3)、通过sysfs导出属性。


 3、使用/proc文件系统

   1)、/proc文件系统文件系统是一种特殊的、由软件创建的文件系统,内核使用它向外界导出信息。/proc下面的每一个文件都绑定于一个内核函数,用户读取其中的文件时,

             该函数动态地生成文件的“内容”。

   2)、/proc对Linux系统有着重要的作用。

   3)、具有完整特征的/proc入口项相当复杂:在特征之中,/proc不仅可以用于读数据,也可以用于写数据。

   4)、不建议在/proc下添加文件,建议新的代码通过sysfs来向外界导出信息。


 4、在/proc中实现文件

   1)、所有使用/proc的模块必须包含<linux/proc_fs.h>,并通过这个头文件来定义正确的函数。

   2)、在某个进程读取我们的/proc文件时,内核会分配一个内存页,驱动程序可以将数据通过这个内存页返回到用户空间。该缓冲区会传入我们定义的函数,该函数称为

             read_proc方法:

             int  (read_proc)(char  *page,  char  **start,  off_t  offset,  int  count,  int  *eof,  void  *data);

            I、page指针指向用来写入数据的缓冲区。

            II、函数应使用start返回实际的数据写到内存页的哪个位置。

            III、offset与count这两个参数与read方法相同。

            IV、eof参数指向一个整数值,当没有数据可返回时,驱动程序必须设置此参数。

            V、data参数是提供给驱动程序专用的数据指针,可用于内部记录。

   3)、scull设备read_proc函数的简单实现


 5、老的/proc接口


 6、创建自己的/proc文件

   1)、把read_proc函数与一个/proc入口项链接起来:

       struct  proc_dir_entry  *create_proc_entry(const  char  *name,  mode_t  mode,struct  proc_dir_entry  *base,

                                                                                    read_proc_t  *read_proc,  void  *data);

       I、name是创建的文件名称。

       II、mode是该文件的保护掩码(可传入0表示系统默认值)。

       III、base指定该文件所在的目录。

       IV、read_proc是实现该文件的read_proc函数。

       V、内核会忽略打他参数,但是会将该参数传递给read_proc。

       VI、目录项指针可用来在proc下创建完整的目录层次结构。

   2)、删除/proc中的入口项:remove_proc_entry()函数。

       I、如果删除入口项失败,将导致未预期的调用,如果模块已被卸载,内核会奔溃。


 7、/proc文件实现的不足

   1)、删除/proc项引发的问题。

   2)、使用同一名字注册两个入口项的问题。


 8、sep_file接口

   1)、sep_file接口假定我们正在创建的虚拟文件要顺序遍历一个项目序列,而这些项目正是必须要返回给用户空间的,为使用sep_file,我们必须创建一个简单的“迭代器”对

             象,该对象用来表示项目序列中的位置,每前进一步,该对象输出序列中的一个项目。

   2)、用sep_file接口创建/proc文件

      I、第一步包含<linux/seq_file.h>头文件,然后必须建立四个迭代器对象,分别是start、next、stop和show。

      II、start犯法始终会首先调用,该函数的原型如下:

          void  *start(struct  seq_file,  loff_t  *ops);

          这里的sfile参数几乎可在大多数情况下忽略。

          ops是一个整数的位置值,表明读取的位置。

      III、next函数应将迭代器移动到下一个位置,并在序列中没有其他项目时返回NULL。

          void  *next(struct  seq_file  *sfile,  void  *v,  loff_t  &pos);

          v是先前对start和next的调用所返回的迭代器。

          pos是文件的当前位置。

      IV、当内核使用迭代器之后,回调用stop方法通知我们进行处理:

          void  *stop(struct  seq_file  *sfile,  void  *v);

       V、在stop调用之间,内核会调用show方法来将实际的数据传输到用户空间:

          void  *show(struct  seq_file  *sfile,  void  *v);

         此方法应该为迭代器v所指向的项目建立输出。但是,他不能使用printk函数,而要使用针对seq_file输出的一组特殊函数。

         int  seq_printf(struct  seq_file  *file,  const  char  *fmt, .  .  .);

         int  seq_putc(struct  seq_file  *file, char  c);

         int  seq_puts(struct  seq_file  *file, const  char  c);

         int  seq_escape(struct  seq_file  *m, const  char  *s,  const  char  *esc);

         int  seq_paths(struct  seq_file  *file, struct  vfsmout  *m,  struct  dentry  *dentry,  char  *esc);

   3)、将完整的迭代器操作函数打包并和/proc中的某个文件链接起来

       I、首先要填充一个seq_operations结构。

       II、创建一个内核能理解的文件实现。

          首先创建一个open方法,该方法将文件连接到sep_file操作。

          定义seq_operations结构。

          建立实际的/proc文件。(struct  proc_dir_entry  *create_proc_entry(const  char  *name,  mode_t  mode,  struct  proc_dir_entry  *parent);)


 9、ioctl方法

   1)、ioctl方法接受一个“命令”号以及另一个参数,命令号用以标识将要执行的命令,而可选参数通常是个指针。

   2)、作为替代/proc文件系统的方法,可以专为调试设计若干ioctl命令。这些命令从驱动程序复制相关的数据到用户空间,然后可在用户空间中检验这些数据。

   3)、ioctl方法的优缺点。



三、通过监视测试

 1、监视用户空间程序的方法:

   1)、调试器一步步跟踪它的函数。

   2)、插入打印语句

   3)、在strace状态下运行程序。


 2、strace工具是一个功能非常强大的工具,它可以显示由用户空间程序发出的所有系统调用。它不仅可以显示调用,还可以显示调用参数以及用符号形式表示的返回值。

   1)、strace命令行选项

      I、-t:该选项用来显示调用发生的时间。

      II、-T:显示调用所花费的时间。

      III、-e:限定被跟踪的调用类型。

      IV、-o:将输出重定向到一个文件中。默认情况下,strace将跟踪信息打印到stderr上。


 3、strace从内核中接收信息。这意味着一个程序师傅支持调试的编译或者是否被去掉了符号信息,都可以被跟踪。


 4、跟踪信息通常用于生成错误报告,然后把它们发送给应用程序开发人员,但是它对内核编程人员来说也同样非常有用。



四、调试系统故障

 1、oops信息

   1)、大部分错误都是因为多NULL指针取值或因为使用了其他不正确的指针值。这些错误通常会导致一个oops信息。

   2)、oops显示发生错误时处理器的状态。这些信息由失效处理函数中的printk语句产生。

   3)、处理ooops

      I、当面对oops时,首先要观察的是发生的问题所在的位置。这通常可以通过调用栈信息得到。

      II、如需要更多的信息,调用栈可以告诉我们系统是如何到达故障点的。

      III、最后,在观察oops清单时还要记得观察“slab毒剂”值。

   4)、只有在构造内核时打开了CONFIG_KALLSYMS选项,我们才能看到符号化的调用栈;否则我们只能看到裸的、十六进制的清单。


 2、系统挂起

   1)、尽管内核代码中的大多数错误只会导致一个oops信息,但有时它们会将系统完全挂起,如果系统挂起了,任何信息都无法打印出来。

   2)、如果系统确实会挂起,而又不知道在什么位置插入schedule调用时,最好的方法是加入一些打印信息,并把它们写入控制台。

             如果系统看起来挂起了,但其实没有。这时,运行专为探明此种情况而设计的程序,通过查看它的输出情况,可以发现这些假的挂起。

   3)、对于上述情形,可利用“SysRq魔法键”

      I、大多数架构上都可以使用魔法键。SysRq魔法键可以通过键盘上的ALT和SysRq组合键来激活。

      II、根据与这两个键一起按下的第三个键的不同,内核会执行许多有用动作中的其中一个:

            r:关闭键盘的raw模式。

            k:激活”留意安全键“功能。

            s:对所有磁盘进行紧急同步。

            u:尝试以只读模式重新挂载所有磁盘。

            b:立即重启系统。

            p:打印当前处理器寄存器信息。

            t:打印当前任务列表。

            m:打印内存信息。

       III、可通过下面的命令启用SysRq功能:

              echo  0  >  /proc/sys/kernel/sysrq

              如果有未授权用户可以使用当前系统的键盘,则应该考虑禁止此功能,以避免出现意外或者蓄意的破环。可向/proc/sys文件写1来禁止。

       IV、SysRq功能也可以对无法访问控制台的系统管理员开放。

       V、复现系统的挂起故障时,另一个要采用的预防措施是。把所有的磁盘以只读的方式挂载在系统(或者干脆卸载它们)。另一个可行的办法是通过NFS挂载所有的文件系

              统。



五、调试器和相关工具

 1、使用gdb

   1)、gdb在探究系统内部行为时非常有用。

   2)、启动调试器时必须把内核看作一个应用程序。除了指定未压缩的内核映像文件名以外,还应该在命令行中提供”core文件“的名称。对于正在运行的内核,所谓的core文件

              就是这个内核在内存中的核心映像。

   3)、在gdb使用中,可使用标准的gdb命令查看内核变量。

   4)、对内核进行调试时,gdb的许多常用功能都不可用。为了让内核使用内核的符号信息,必须在打开CONFIG_DEBUG_INFO选项的情况下编译内核。

   5)、调试会话相关的代码段:

       I、.text。

       II、.bss。

       III、data。

   6)、为了gdb能够处理可装载模块,必须告诉调试器装载模块代码的具体位置。该信息可通过sysfs的/sysfs/module获得。


 2、kdb内核调试器

   1)、要使用kdb,首先必须获得这个补丁(取得的版本一定要和内核版本匹配),然后对当前内核源代码进行patch操作,在重新编译并安装这个内核。

   2)、一旦运行的是支持kdb的内核,则可以用下面几个方法进入kdb的调试状态:

      I、在控制台上按下Psuse(或Break)键将启动调试。

      II、当内核发生oops,或到达某个断点时,也会启动kdb。

   3)、当运行kdb时,内核所做的每一件事都会停下来。当激活kdb调试时,系统不应运行其他任何东西;尤其是,不要开启网络功能——除非是调试网络驱动程序。


 3、kgdb补丁

   1)、kgdb补丁将运行调试内核的系统和运行调试器的系统隔离开来而工作,而这两个系统之间通过串口线缆相连。


 4、用户模式的Linux虚拟机

   1)、用户模式Linux作为一个独立的、可移植的Linux内核而被创建,包含在子目录arch/um中。UML不运行于某种新硬件上,二十运行在基于Linux系统调用接口所实现的虚

             拟机上。

   2)、对内核开发人员来说,可以很容易利用gdb或其他调试器对用户模式Linux进行处理。

   3)、从驱动程序编写者看来,UML也有明显的缺点:用户模式的内核无法访问主机系统的硬件。


 5、Linux跟踪工具包

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值