您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 管理学资料 > Linux设备驱动程序调试方法
Linux设备驱动程序调试方法国防科技大学计算机学院张一鸣由于设备驱动程序运行于内核空间,因此有着与用户空间程序不同的调试方法。1.1简介设备驱动程序的调试需要内核的支持,因此通常应该根据需要对内核进行重编译。下面以2.6.11版的Linux内核为例进行介绍,其他版本的内核编译方法与此类似。1.进入编译选项配置界面。(1)如果不清楚当前正在运行的内核源代码的目录,可以通过如下方法查看。(2)进入内核源代码所在目录,通过makemenuconfig命令进入编译选项配置环境,如图1.1所示。图1.1编译选项配置环境调试内核在选项配置环境中每个选项有“*”(编译进内核)、“M”(以模块方式编译)和“”(不编译)三种状态,可以分别使用“Y”、“M”和“N”键来设置。2.配置编译选项如图1.2所示,在Kernelhacking下有很多选项,分别对应不同的内核调试功能。图1.2内核调试选项其中需要注意的有如下几点。(1)Kerneldebugging选项(对应于CONFIG_DEBUG_KERNEL)应设为“*”,只有把该选项选为“*”,才能够使其他内核调试选项可见。(2)使用gdb调试内核需要在编译内核的时候应选中Compilethekernelwithdebuginfo选项(CONFIG_DEBUG_INFO),否则gdb将无法对内核符号进行解析。(?是这样吗)(2)Spinlockdebugging选项(对应于CONFIG_DEBUG_SPINLOCK)应设为“*”,这样系统将检查到例如对没有初始化的spinlock的操作、对同一个锁解锁多次等类似错误。(3)Sleep-inside-spinlockchecking选项(对应于CONFIG_DEBUG_SPINLOC_SLEEP)通常可不选。选中该选项将使系统检查到占有spinlock的进程进入sleep状态的错误,但是系统在进程只是有可能sleep的情况下也会发出警告,从而可能在正常情况下产生大量无用信息。在使用VMware虚拟机的时候这种现象尤为明显。(4)Kprobes选项(对应于CONFIG_KPROBES)通常可不选。该选项的作用是使人们能够通过register_kprobe()函数在内核中适当的位置设置自定义的回调函数。但是选中该选项将会定义一个和kdb中同名的函数(用于产生int3中断),造成冲突。如果希望使用kdb,则不应选中Kprobes选项。(5)如果打了kdb的patch,那么在最后将会有Built-inKernelDebuggersupport选项,我们将在1.8节“使用kdb”中进行介绍。(6)在顶层选项的Loadablemodulesupport中只有选中Moduleversioningsupport选项,才能够为模块添加版本支持机制。(通常可以不选)。设置完成后保存并退出编译选项配置环境,通过makeinstall命令编译内核。(或者使用makebzImage,makemodules,makemodules_install,makeinstall)。编译完成后即可对内核进行调试了。1.2使用printk()1.2.1简介通常认为真正的高手和初学者都只使用printk()来调试内核。从某种程度上说,printk()就是内核里的printf()。如果klogd和syslogd都已经运行,那么printk()将把消息打印到console或者/var/log/messages文件中。例如消息通常会打印到console上或者/var/log/messages文件中。printk()能够通过为消息指定不同的重要性(loglevel)来对消息的重要性进行分类,从0到7表示重要性逐步降低。如果在printk()中不指定消息的重要性,系统将为消息使用默认的DEFAULT_MESSAGE_LOGLEVEL值,通常该值为KERN_WARNING(即4)。1.2.2消息显示控制1.console_loglevel好像在GNOME图形界面下console_loglevel没有什么用?可以使用console_loglevel变量来控制消息在console上的显示。当消息的重要性(loglevel)小于console_loglevel时才在console上打印出消息内容。需要注意的是,消息是否打印到messages文件中与console_loglevel变量无关。通过/proc/sys/kernel/printk文件可以读写console_loglevel变量。该文件有4个整数值,分别表示当前loglevel值(即console_loglevel变量)DEFAULT_MESSAGE_LOGLEVEL值(即消息的默认loglevel值)loglevel的最小值系统启动时的loglevel值使用echo5/proc/sys/kernel/printk,将把当前loglevel值(即console_loglevel变量)设为5,此时只有重要性小于5的消息才会被打印到console。2.例子:消息显示控制首先设置console_loglevel为5。(1)如果消息重要性设置为KERN_DEBUG(7),那么在messages文件和console都不会打印出“Helloprovider”消息。(2)如果消息重要性设置为KERN_INFO(6),那么在messages文件中将打印出“Helloprovider”消息,但是在console中不会打印。(3)如果消息重要性设置为KERN_ALERT(1),那么在messages文件中将打印出“Helloprovider”消息,但是在console中不会打印。(4)如果消息重要性设置为KERN_EMERG(0),那么在messages文件和console都将打印出“Helloprovider”消息。console为什么会打印出来?和内核版本有关,还是和内核调试选项有关?还是只要消息重要性为0就打印到console上?实验表明(1)是否打印到messages文件和console_loglevel无关,消息重要性为KERN_DEBUG(7)时不打印到messages文件,否则就打印。(2)不管消息重要性是否小于console_loglevel,只要重要性不为KERN_EMERG,就不会打印到console,说明在GNOME图形界面下消息只会发到messages文件,一般是不会到console上的。3.syslog.conf文件消息的默认输出文件是/var/log/messages,可以在/etc/syslog.conf文件中设置输出文件的位置和名称。如上所示是syslog.conf文件的部分内容,被注释掉的一行表示消息将打印到messages文件,而下面的一行表示消息将打印到messages_zym文件。完成修改后重启系统,所有消息将不再打印到messages文件,而是打印到messages_zym文件了。4.控制是否打印消息通过定义恰当的宏,可以很方便的控制是否打印调试消息。(1)首先在.h文件中定义ZDEBUG的两种版本。(2)在.c文件中可以很方便的调用前面定义的宏。(3)在Makefile中可以对使用哪个版本的宏进行控制。:表示打印出消息:表示不打印1.3使用/proc文件系统1.4使用seq_file接口1.5使用strace命令1.6查看oops消息1.7使用gdb使用gdb调试内核需要在编译内核的时候选中CONFIG_DEBUG_INFO,否则gdb将无法对内核符号进行解析。是这样吗?1.7.1调试内核gdb在内核调试中的主要作用是能够查看部分变量和结构,但是无法修改内核数据、设置断点和单步跟踪。使用gdb调试内核的时候需要把内核看作一个程序来加载。(1)启动gdb。在第一行调用gdb时所传入的参数中:vmlinux:未压缩的ELF内核可执行文件kcore:corefile的名称(2)查看系统变量。此时可以通过p命令查看系统变量,例如需要注意的是,从上图中可以看出,虽然jiffies是不停变换的,但是gdb每次读取同一个变量时将得到相同的值,这是因为gdb对读到的值进行了缓存。如果希望去掉缓存的影响,可以使用core-file命令。可以看到,jiffies的值得到了更新。1.7.2调试模块由于模块并没有作为vmlinux的一部分传给gdb,因此必须通过某种方法把模块信息告知gdb。1.模块文件的组成Linux的模块是ELF格式的可执行映像,分成了很多个section。与调试关系较为密切的在没有选中CONFIG_DEBUG_INFO时也可查看jiffies的三个section如下:.text:包含了模块的可执行代码。.bss和.data:包括了模块的变量(在模块编译阶段被初始化的变量在.data,其他的在.bss)2.加载模块信息可以通过add-symbol-file命令把模块信息告知gdb。这里的模块信息主要是指前面介绍的3个section的基址,分别保存在/sys/module/scull/sections目录下的与section同名的文件中。(1)得到几个section的基址。(2)加载模块信息。模块名称和.text基址是add-symbol-file命令的必要参数,.bss和.data的基址可使用-s选项传给add-symbol-file命令。3.使用(1)把模块信息传递给gdb后,可以通过p命令查看模块的变量和结构。(2)使用print*(address)命令查看address地址处代码所在的文件和行号,可用于查看一个函数指针到底指向什么函数。(有待实验)1.8使用kdb1.8.1安装与设置kdb是一款功能强大的内核调试软件,使用kdb,可以像调试用户空间进程那样设置断点、查看变量值等。1.打补丁kdb通过打补丁的方式对内核进行了修改,可以在下载2.6.10、2.6.11等版本内核所对应的kdb补丁。下载完毕后,在内核目录下可以通过如下命令安装补丁(以2.6.10为例):bzip2-dkdb-v4.4-2.6.10-common-1.bz2bzip2-dkdb-v4.4-2.6.10-i386-1.bz2patch-p1kdb-v4.4-2.6.10-common-1patch-p1kdb-v4.4-2.6.10-i386-12.设置安装好补丁后,在内核源代码目录下通过makemenuconfig命令进入内核编译选项设置界面,进入Kernelhacking,在选中了Kerneldebugging的前提下可以看到在最后有一项Built-inKernelDebuggersupport,如图1.3所示。图1.3Built-inKernelDebuggersupport选项选中该选项后,将可以看到三个子选项:KDBmodules:选中该选项将使用户能够以增加模块的方式为kdb增加新的功能KDBoffbydefault:选中该选项,那么系统在启动时的默认状态将是不打开kdbKDBcontinuesaftercatastrophicerrors:设置KDB遇到严重错误还继续运行的次数1.8.2进入kdb若选中KDBoffbydefault,那么有两种方法激活KDB:(1)在引导期间将kdb=on标志传递给内核。(2)执行命令:echo1/proc/sys/kernel/kdb。安装好kdb后,可以通过Pause(Break)键来进入kdb,在系统发生错误或者执行到断点时也将进入kdb。需要注意的是,有些系统只有在文本界面下才能使用kdb,而在图形化界面下无法进入kdb。如果当前默认登陆为图形界面,可以通过把/etc/inittab文件中的id设置为3,即可以文本方式登录。使用文本方式登录后,可以使用startx命令进入图形界面,如果需要再次进入文本界面,可以使用Ctrl+Alt+Backspace组合键。在
本文标题:Linux设备驱动程序调试方法
链接地址:https://www.777doc.com/doc-1307023 .html