📄 (ldd) ch04-调试技术(转载).txt
字号:
页边界(Alpha上的页面大小是8KB,缓冲区正好在页面的起始位置附近)。如果在你的
系统上读取faulty没有产生oops,试试wc,或者给dd显式地指定块大小。
使用ksymoops
oops消息的最大问题就是十六进制数值对于程序员来说没什么意义;需要将它们解析为
符号。
内核源码通过其所包含的ksymoops工具帮助开发人员――但是注意,版本1.2的源码中没
有这个程序。该工具将oops消息中的数值地址解析为内核符号,但只限于PC机产生的oop
s消息。由于消息本身就是处理器相关的,每一体系结构都有其自身的消息格式。
ksymoops从标准输入获得oops消息,并从命令行内核符号表的名字。符号表通常就是/us
ksymoops从标准输入获得oops消息,并从命令行内核符号表的名字。符号表通常就是/us
r/src/linux/System.map。程序以更可读的方式打印调用轨迹和程序代码,而不是最原
始的oops消息。下面的片断就是用上一节的oops消息得出的结果:
(代码)
由ksymoops反汇编出的代码给出了失效的指令和其后的指令。很明显――对于那些知道
一点汇编的人――repz movsl指令(REPeat till cx is Zero, MOVe a String of
Longs)用源索引(esi,是0x202e000)访问了一个未映射页面。用来获得模块信息的ks
ymoops -m命令给出,模块映射到一个在0x0202dxxx的页面上,这也确认乐esi确实超出
了范围。
由于faulty模块所占用的内存不在系统表中,被解码的调用轨迹还给出了两个数值地址
。这些值可以手动补充,或是通过ksyms命令的输出,或是在/proc/ksyms中查询模块的
名字。
然而对于这个失效,这两个地址并不对应与代码地址。如果你看了arch/i386/kernel/tr
aps.c,你就发现,调用轨迹是从整个堆栈并利用一些启发式方法区分数据值(本地变量
和函数参数)和返回地址获得的。调用轨迹中只给出了引用内核代码的地址和引用模块
的地址。由于模块所占页面既有代码也有数据,错综复杂的栈可能会漏掉启发式信息,
这就是上面两个0x202xxxx地址的情况。
如果你不愿手动查看模块地址,下面这组管道可以用来创建一个既有内核又有模块符号
如果你不愿手动查看模块地址,下面这组管道可以用来创建一个既有内核又有模块符号
的符号表。无论何时你加载模块,你都必须重新创建这个符号表。
(代码)
这个管道将完整的系统表与/proc/ksyms中的公开内核符号混合在一起,后者除了内核符
号外,还包括了当前内核里的模块符号。这些地址在insmod重定位代码后就出现在/proc
/ksyms中。由于这两个文件的格式不同,使用了sed和awk将所有的文本行转换为一种合
适的格式。然后对这张表排序,去除重复部分,这样ksymoops就可以用了。
如果我们重新运行ksymoops,它从新的符号表中截取出如下信息:
(代码)
正如你所见到的,当跟踪与模块有关的oops消息时,创建一个修订的系统表是很有助益
的:现在ksymoops能够对指令指针解码并完成整个调用轨迹了。还要注意,显式反汇编
码的格式和objdump所使用的格式一样。objdump也是一个功能强大的工具;如果你需要
查看失败前的指令,你调用命令objdump –d faulty.o。
在文件的汇编列表中,字串faulty_read+45/60标记为失效行。有关objdump的更多的信
息和它的命令行选项可以参见该命令的手册。
即便你构建了你自己的修订版符号表,上面提到的有关调用轨迹的问题仍然存在:虽然0
即便你构建了你自己的修订版符号表,上面提到的有关调用轨迹的问题仍然存在:虽然0
x202xxxx指针被解码了,但仍然是假的。
学会解码oops消息需要一定的经验,但是确实值得一做。用来学习的时间很快就会有所
回报。不过由于机器指令的Unix语法与Intel语法不同,唯一的问题在于从哪获得有关汇
编语言的文档;尽管你了解PC汇编语言,但你的经验都是用Intel语法的编程获得的。在
参考书目中,我给一些有所补益的书籍。
使用oops
使用ksymoops有些繁琐。你需要C++编译器编译它,你还要构建你自己的符号表来充分发
挥程序的能力,你还要将原始消息和ksymoops输出合在一起组成可用的信息。
如果你不想找这么多麻烦,你可以使用oops程序。oops在本书的O’Reilly FTP站点给出
的源码中。它源自最初的ksymoops工具,现在它的作者已经不维护这个工具了。oops是
用C语言写成的,而且直接查看/proc/ksyms而无需用户每次加载模块后构建新的符号表
。
该程序试图解码所有的处理器寄存器并 颜 轨迹解析为符号值。它的缺点是,它要比ksy
moops罗嗦些,但通常你所有的信息越多,你发现错误也就越快。oops的另一个优点是,
它可以解析x86,Alpha和Sparc的oops消息。与内核源码相同,这个程序也按GPL发行。
oops产生的输出与ksymoops的类似,但是更完全。这里给出前一个oops输出的开始部分
—由于在这个oops消息中堆栈没保存什么有用的东西,我不认为应该显示整个 颜 轨迹
—由于在这个oops消息中堆栈没保存什么有用的东西,我不认为应该显示整个 颜 轨迹
:
(代码)
当你调试“真正的”模块(faulty太短了,没有什么意义)时,将寄存器和堆栈解码是
非常有益的,而且如果被调试的所有模块符号都开放出来时更有帮助。在失效时,处理
器寄存器一般不会指向模块的符号,只有当符号表开放给/proc/ksyms时,你才能输出中
标别它们。
我们可以用一下步骤制作一张更完整的符号表。首先,我们不应在模块中声明静态变量
,否则我们就无法用insmod开放它们了。第二,如下面的截取自scull的init_module函
数的代码所示,我们可以用#ifdef SCULL_DEBUG或类似的宏屏蔽register_symtab调用。
(代码)
我们在第2章“编写和运行模块”的“注册符号表”一节中已经看到了类似内容,那里说
,如果模块不注册符号表,所有的全局符号就都开放。尽管这一功能仅在SCULL_DEBUG被
激活时才有效,为了避免内核中的名字空间污染,所有的全局符号有合适的前缀(参见
第2章的“模块与应用程序”一节)。
使用klogd
使用klogd
klogd守护进程的近期版本可以在oops存放到记录文件前对oops消息解码。解码过程只由
版本1.3或更新版本的守护进程完成,而且只有将-k /usr/src/linux/System.map做为参
数传递给守护进程时才解码。(你可以用其他符号表文件代替System.map)
有新的klogd给出的faulty的oops如下所示,它写到了系统记录中:
(代码)
我想能解码的klogd对于调试一般的Linux安装的核心来说是很好的工具。由klogd解码的
消息包括大部分ksymoops的功能,而且也要求用户编译额外的工具,或是,当系统出现
故障时,为了给出完整的错误报告而合并两个输出。当oops发生在内核时,守护进程还
会正确地解码指令指针。它并不反汇编代码,但这不是问题,当错误报告给出消息时,
二进制数据仍然存在,可以离线反汇编代码。
守护进程的另一个功能就是,如果符号表版本与当前内核不匹配,它会拒绝解析符号。
如果在系统记录中解析出了符号,你可以确信它是正确的解码。
然而,尽管它对Linux用户很有帮助,这个工具在调试模块时没有什么帮助。我个人没有
在开放软件的电脑里使用解码选项。klogd的问题是它不解析模块中的符号;因为守护进
程在程序员加载模块前就已经运行了,即使读了/proc/ksyms也不会有什么帮助。记录文
件中存在解析后的符号会使oops和ksymoops混淆,造成进一步解析的困难。
如果你需要使用klogd调试你的模块,最新版本的守护进程需要加入一些新的特殊支持,
我期待它的完成,只要给内核打一个小补丁就可以了。
系统挂起
尽管内核代码中的大多数错误仅会导致一个oops消息,有时它们困难完全将系统挂起。
如果系统挂起了,没有消息能够打印出来。例如,如果代码遇到一个死循环,内核停止
了调度过程,系统不会再响应任何动作,包括魔法键Ctrl-Alt-Del组合。
处理系统挂起有两个选择――一个是防范与未然,另一个就是亡羊补牢,在发生挂起后
调试代码。
通过在策略点上插入schedule调用可以防止死循环。schedule调用(正如你所猜想到的
)调用调度器,因此允许其他进程偷取当然进程的CPU时间。如果进程因你的驱动程序中
的错误而在内核空间循环,你可以在跟踪到这种情况后杀掉这个进程。
在驱动程序代码中插入schedule调用会给程序员带来新的“问题”:函数,,以及调用轨
迹中的所有函数,必须是可重入的。在正常环境下,由于不同的进程可能并发地访问设
备,驱动程序做为整体是可重入的,但由于Linux内核是不可抢占的,不必每个函数都是
可重入的。但如果驱动程序函数允许调度器中断当前进程,另一个不同的进程可能会进
入同一个函数。如果schedule调用仅在调试期间打开,如果你不允许,你可以避免两个
并发进程访问驱动程序,所以并发性倒不是什么非常重要的问题。在介绍阻塞型操作时
(第5章的“写可重入代码”)我们再详细介绍并发性问题。
(第5章的“写可重入代码”)我们再详细介绍并发性问题。
如果要调试死循环,你可以利用Linux键盘的特殊键。默认情况下,如果和修饰键一起按
了PrScr键(键码是70),系统会向当前控制台打印有关机器状态的有用信息。这一功能
在x86和Alpha系统都有。Linux的Sparc移植也有同样的功能,但它使用了标记为“Break
/Scroll Lock”的键(键码是30)。
每一个特殊函数都有一个名字,并如下面所示都有一个按键事件与之对应。组合键之后
的括号里是函数名。
Shift-PrScr(Show_Memory)
打印若干行关于内存使用的信息,尤其是有关缓冲区高速缓存的使用情况。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -