⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 linux调试技术介绍.txt

📁 Linux网络编程
💻 TXT
📖 第 1 页 / 共 4 页
字号:
注意,如果一个正在读你的/proc文件的进程发出了若干read调用,每一个都获取新数
据,尽管只有少量数据被读取,你的驱动程序每次都要重写整个缓冲区。这些额外
的工作会使系统性能下降,而且如果文件产生的数据与下一次的不同,以后的read
调用要重新装配不相关的部分,这一会造成数据错位。事实上,由于每个使用C库
的应用程序都大块地读取数据,性能并不是什么问题。然而,由于错位时有发生,
它倒是一个值得考虑的问题。在获取数据后,库调用至少要调用1次read――只有当read
返回0时才报告文件尾。如果驱动程序碰巧比前面产生了更多的数据,系统就返回
到用户空间额外的字节并且与前面的数据块是错位的。我们将在第6章“时间流”
的“任务队列”一节中涉及/proc/jiq*,那时我们还会遇到错位问题。

cleanup_module中应该使用下面的语句注销/proc节点:

(代码)

传递给函数的参数是包含要撤销文件的目录名和文件的i节点号。由于i节点号是自
动分配的,在编译时是无法知道的,必须从数据结构中读取。

ioctl方法

ioctl,下一章将详细讨论,是一个系统调用,它可以操做在文件描述符上;它接收
一个“命令”号和(可选的)一个参数,通常这是一个指针。

做为替代/proc文件系统的方法,你可以为调试实现若干ioctl命令。这些命令从驱动程
序空间复制相关数据到进程空间,在进程空间里检查这些数据。

只有使用ioctl获取信息比起/proc来要困难一些,因为你一个程序调用ioctl并显示结
果。必须编写这样的程序,还要编译,保持与你测试的模块间的一致性等。

不过有时候这是最好的获取信息的方法,因为它比起读/proc来要快得多。如果在数
据写到屏幕前必须完成某些处理工作,以二进制获取数据要比读取文本文件有效得
多。此外,ioctl不限制返回数据的大小。

ioctl方法的一个优点是,当调试关闭后调试命令仍然可以保留在驱动程序中。/proc文
件对任何查看这个目录的人都是可见的,然而与/proc文件不同,未公开的ioctl命令通
常都不会被注意到。此外,如果驱动程序有什么异常,它们仍然可以用来调试。唯
一的缺点就是模块会稍微大一些。

通过监视调试

有时你遇到的问题并不特别糟,通过在用户空间运行应用程序来查看驱动程序与系
统之间的交互过程可以帮助你捕捉到一些小问题,并可以验证驱动程序确实工作正
常。例如,看到scull的read实现如何处理不同数据量的read请求后,我对scull更有信
心。

有许多方法监视一个用户态程序的工作情况。你可以用调试器一步步跟踪它的函
数,插入打印语句,或者用strace运行程序。在实际目的是查看内核代码时,最后一
项技术非常有用。

strace命令是一个功能非常强大的工具,它可以现实程序所调用的所有系统调用。它
不仅可以显示调用,而且还能显示调用的参数,以符号方式显示返回值。当系统调
用失败时,错误的符号值(如,ENOMEM)和对应的字串(Out of memory)同时显
示。strace还有许多命令行选项;最常用的是-t,它用来显示调用发生的时间,-T,
显示调用所花费的时间,以及-o,将输出重定向到一个文件中。默认情况下,strace
将所有跟踪信息打印到stderr上。

strace从内核接收信息。这意味着一个程序无论是否按调试方式编译(用gcc的-g选
项)或是被去掉了符号信息都可以被跟踪。与调试器可以连接到一个运行进程并控
制它类似,你还可以跟踪一个已经运行的进程。

跟踪信息通常用来生成错误报告报告给应用开发人员,但是对内核编程人员来说也
一样非常有用。我们可以看到系统调用是如何执行驱动程序代码的;strace允许我们
检查每一次调用输入输出的一致性。

例如,下面的屏幕输出给出了命令ls /dev > /dev/scull0的最后几行:

(代码)

很明显,在ls完成目标目录的检索后首次对write的调用中,它试图写4KB。很奇怪,
只写了4000个字节,接着重试这一操作。然而,我们知道scull的write实现每次只写一
个量子,我在这里看到了部分写。经过若干步骤之后,所有的东西都清空了,程序
正常退出。

另一个例子,让我们来读scull设备:

(代码)

正如所料,read每次只能读到4000个字节,但是数据总量是不变的。注意本例中重试
工作是如何组织的,注意它与上面写跟踪的对比。wc专门为快速读数据进行了优
化,它绕过了标准库,以便每次用一个系统调用读取更多的数据。你可以从跟踪的read
行中看到wc每次要读16KB。

Unix专家可以在strace的输出中找到很多有用信息。如果你被这些符号搞得满头雾
水,我可以只看文件方法(open,read等等)是如何工作的。

个人认为,跟踪工具在查明系统调用的运行时错误过程中最有用。通常应用或演示
程序中的perror调用不足以用来调试,而且对于查明到底是什么样的参数触发了系统
调用的错误也很有帮助。

调试系统故障

即便你用了所有监视和调试技术,有时候驱动程序中依然有错误,当这样的驱动程
序执行会造成系统故障。当这种情况发生时,获取足够多的信息来解决问题是至关
重要的。

注意,“故障”不意味着“panic”。Linux代码非常鲁棒,可以很好地响应大部分错
误:故障通常会导致当前进程的终止,但系统继续运行。如果在进程上下文之外发
生故障,或是组成系统的重要部件发生故障时,系统可能panic。但问题出在驱动程
序时,通常只会导致产生故障的进程终止――即那个使用驱动程序的进程。唯一不
可恢复的损失就是当进程被终止时,进程上下文分配的内存丢失了;例如,由驱动
程序通过kmalloc分配的动态链表可能丢失。然而,由于内核会对尚是打开的设备调
用close,你的驱动程序可以释放任何有open方法分配的资源。

我们已经说过,当内核行为异常时会在控制台上显示一些有用的信息。下一节将解
释如何解码和使用这些消息。尽管它们对于初学者来说相当晦涩,处理器的给出数
据都是些很有意思的信息,通常无需额外测试就可以查明程序错误。

Oops消息

大部分错误都是NULL指针引用或使用其他不正确的指针数值。这些错误通常会导致
一个oops消息。

由处理器使用的地址都是“虚”地址,而且通过一个复杂的称为页表(见第13章“Mmap
和DMA”中的“页表”一节)的结构映射为物理地址。当引用一个非法指针时,页
面映射机制就不能将地址映射到物理地址,并且处理器向操作系统发出一个“页面
失效”。如果地址确实是非法的,内核就无法从失效地址上“换页”;如果此时处
理在超级用户太,系统于是就产生一个“oops”。值得注意的是,在版本2.1中内核
处理失效的方式有所变化,它可以处理在超级用户态的非法地址引用了。新实现将
在第17章“最近发展”的“处理内核空间失效”中介绍。

oops显示故障时的处理器状态,模块CPU寄存器内容,页描述符表的位置,以及其他
似乎不能理解的信息。这些是由失效处理函数(arch/*/kernel/traps.c)中的printk语句产
生的,而且象前面“Printk”一节介绍的那样进行分派。

让我们看看这样一个消息。这里给出的是传统个人电脑(x86平台),运行Linux 2.0或
更新版本的oops――版本1.2的输出稍有不同。

(代码)

上面的消息是在一个有意加入错误的失效模块上运行cat所至。fault.c崩溃如下代码:

(代码)

由于read从它的小缓冲区(faulty_buf)复制数据到用户空间,我们希望读一小块文件
能够工作。然而,每次读出多于1KB的数据会跨越页面边界,如果访问了非法页面read
就会失败。事实上,前面给出的oops是在请求一个4KB大小的read时发生的,这条消
息在/var/log/messages(syslogd默认存放内核消息的文件)的oops消息前给出了:

(代码)

同样的cat命令却不能在Alpha上产生oops,这是因为从faulty_buf读取4KB字节没有超出页
边界(Alpha上的页面大小是8KB,缓冲区正好在页面的起始位置附近)。如果在你
的系统上读取faulty没有产生oops,试试wc,或者给dd显式地指定块大小。

使用ksymoops

oops消息的最大问题就是十六进制数值对于程序员来说没什么意义;需要将它们解
析为符号。

内核源码通过其所包含的ksymoops工具帮助开发人员――但是注意,版本1.2的源码
中没有这个程序。该工具将oops消息中的数值地址解析为内核符号,但只限于PC机
产生的oops消息。由于消息本身就是处理器相关的,每一体系结构都有其自身的消
息格式。

ksymoops从标准输入获得oops消息,并从命令行内核符号表的名字。符号表通常就是/usr/src/linux/System.map。
程序以更可读的方式打印调用轨迹和程序代码,而不是最原始的oops消息。下面的
片断就是用上一节的oops消息得出的结果:

(代码)

由ksymoops反汇编出的代码给出了失效的指令和其后的指令。很明显――对于那些
知道一点汇编的人――repz movsl指令(REPeat till cx is Zero, MOVe a String of Longs)用源索
引(esi,是0x202e000)访问了一个未映射页面。用来获得模块信息的ksymoops -m命令
给出,模块映射到一个在0x0202dxxx的页面上,这也确认乐esi确实超出了范围。

由于faulty模块所占用的内存不在系统表中,被解码的调用轨迹还给出了两个数值地
址。这些值可以手动补充,或是通过ksyms命令的输出,或是在/proc/ksyms中查询模块
的名字。

然而对于这个失效,这两个地址并不对应与代码地址。如果你看了arch/i386/kernel/traps.c,
你就发现,调用轨迹是从整个堆栈并利用一些启发式方法区分数据值(本地变量和
函数参数)和返回地址获得的。调用轨迹中只给出了引用内核代码的地址和引用模
块的地址。由于模块所占页面既有代码也有数据,错综复杂的栈可能会漏掉启发式
信息,这就是上面两个0x202xxxx地址的情况。

如果你不愿手动查看模块地址,下面这组管道可以用来创建一个既有内核又有模块
符号的符号表。无论何时你加载模块,你都必须重新创建这个符号表。

(代码)

这个管道将完整的系统表与/proc/ksyms中的公开内核符号混合在一起,后者除了内核
符号外,还包括了当前内核里的模块符号。这些地址在insmod重定位代码后就出现

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -