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

📄 linux调试技术介绍.txt

📁 Linux网络编程
💻 TXT
📖 第 1 页 / 共 4 页
字号:
变量值)。在重演挂起过程时,最好将所有的磁盘都以只读方式安装在系统上。如
果磁盘是只读的或没有安装,就不会存在破坏文件系统或使其进入不一致状态的危
险。至少你可以避免在复位系统后运行fsck。另一中方法就是使用NFS根计算机来测
试模块。在这种情况下,由于NFS服务器管理文件系统的一致性,而它又不会受你
的驱动程序的影响,你可以避免任何的文件系统崩溃。

使用调试器

最后一种调试模块的方法就是使用调试器来一步步地跟踪代码,查看变量和机器寄
存器的值。这种方法非常耗时,应该尽可能地避免。不过,某些情况下通过调试器
对代码进行细粒度的分析是非常有益的。在这里,我们所说的被调试的代码运行在
内核空间――除非你远程控制内核,否则不可能一步步跟踪内核,这会使很多事情
变得更加困难。由于远程控制很少用到,我们最后介绍这项技术。所幸的是,在当
前版本的内核中可以查看和修改变量。

在这一级上熟练地使用调试器需要精通gdb命令,对汇编码有一定了解,并且有能
够将源码与优化后的汇编码对应起来的能力。

不幸的是,gdb更适合与调试核心而不是模块,调试模块化的代码需要更多的技
术。这更多的技术就是kdebug包,它利用gdb的“远程调试”接口控制本地内核。我
将在介绍普通调试器后介绍kdebug。

使用gdb

gdb在探究系统内部行为时非常有用。启动调试器时必须假想内核就是一个应用程
序。除了指定内核文件名外,你还应该在命令行中提供内存镜象文件的名字。典型
的gdb调用如下所示:

(代码)

第一个参数是未经压缩的内核可执行文件(在你编译完内核后,这个文件在/usr/src/linux
目录中)的名字。只有x86体系结构有zImage文件(有时称为vmlinuz),它是一种解决Intel
处理器实模式下只有640KB限制的一种技巧;而无论在哪个平台上,vmlinux都是你所
编译的未经压缩的内核。

gdb命令行的第二个参数是是内存镜象文件的名字。与其他在/proc下的文件类似,/proc/kcore
也是在被读取时产生的。当read系统调用在/proc文件系统执行时,它映射到一个用于
数据生成而不是数据读取的函数上;我们已在“使用/proc文件系统”一节中介绍了
这个功能。系统用kcore来表示按内存镜象文件格式存储的内核“可执行文件”;由
于它要表示整个内核地址空间,它是一个非常巨大的文件,对应所有的物理内存。
利用gdb,你可以通过标准gdb命令查看内核标量。例如,p jiffies可以打印从系统启动
到当前时刻的时钟滴答数。

当你从gdb打印数据时,内核还在运行,不同数据项会在不同时刻有不同的数值;
然而,gdb为了优化对内存镜象文件的访问会将已经读到的数据缓存起来。如果你
再次查看jiffies变量,你会得到和以前相同的值。缓存变量值防止额外的磁盘操作对
普通内存镜象文件来说是对的,但对“动态”内存镜象文件来说就不是很方便了。
解决方法是在你想刷新gdb缓存的时候执行core-file /proc/kcore命令;调试器将使用新的
内存镜象文件并废弃旧信息。但是,读新数据时你并不总是需要执行core-file命令;gdb
以1KB的尺度读取内存镜象文件,仅仅缓存它所引用的若干块。

你不能用普通gdb做的是修改内核数据;由于调试器需要在访问内存镜象前运行被
调试程序,它是不会去修改内存镜象文件的。当调试内核镜象时,执行run命令会导
致在执行若干指令后导致段违例。出于这个原因,/proc/kcore都没有实现write方法。

如果你用调试选项(-g)编译了内核,结果产生的vmlinux比没有用-g选项的更适合于gdb。
不过要注意,用-g选项编译内核需要大量的磁盘空间――支持网络和很少几个设备
和文件系统的2.0内核在PC上需要11KB。不过不管怎样,你都可以生成zImage文件并用
它来其他系统:在生成可启动镜象时由于选项-g而加入的调试信息最终都被去掉
了。如果我有足够的磁盘空间,我会一致打开-g选项的。

在非PC计算机上则有不同的方法。在Alpha上,make boot会在生成可启动镜象前将调试
信息去掉,所以你最终会获得vmlinux和vmlinux.gz两个文件。gdb可以使用前者,但你只
能用后者启动。在Sparc上,默认情况下内核(至少是2.0内核)不会被去掉调试信
息,所以你需要在将其传递给silo(Sparc的内核加载器)前将调试信息去掉,这样才
能启动。由于尺寸的问题,无论milo(Alpha的内核加载器)还是silo都不能启动未去
掉调试信息的内核。

当你用-g选项编译内核并且用vmlinux和/proc/kcore一起使用调试器,gdb可以返回很多有
关内核内部结构的信息。例如,你可以使用类似于这样的命令,p *module_list,p
*module_list->next和p *chrdevs[4]->fops等显示这些结构的内容。如果你手头有内核映射表
和源码的话,这些探测命令是非常有用的。

另一个gdb可以在当前内核上执行的有用任务是,通过disassemble命令(它可以缩写)
或是“检查指令”(x/i)命令反汇编函数。disassemble命令的参数可以是函数名或是
内存区范围,而x/i则使用一个内存地址做为参数,也可以用符号名。例如,你可以
用x/20i反汇编20条指令。注意,你不能反汇编一个模块的函数,这是因为调试器处
理vmlinux,它并不知道你的模块的信息。如果你试图用模块的地址反汇编代码,gdb
很有可能会报告“不能访问xxxx处的内存(Cannot access memory at xxxx)”。基于同样
的原因,你不查看属于模块的数据项。如果你知道你的变量的地址,你可以从/dev/mem
中读出它的值,但很难弄明白从系统内存中分解出的数据是什么含义。

如果你需要反汇编模块函数,你最好对用objdump工具处理你的模块文件。很不幸,
该工具只能对磁盘上的文件进行处理,而不能对运行中的模块进行处理;因此,objdump
中给出的地址都是未经重定位的地址,与模块的运行环境无关。

如你所见,当你的目的是查看内核的运行情况时,gdb是一个非常有用的工具,但
它缺少某些功能,最重要的一些功能就是修改内核项和访问模块的功能。这些空白
将由kdebug包填补。

使用kdebug

你可用从一般的FTP站点下的pcmcia/extras目录下拿到kdebug,但是如果你想确保拿到的
是最新的版本,你最好到ftp://hyper.stanford.edu/pub/pcmcia/extras/去找。该工具与pcmcia没有
什么关系,但是这两个包是同一个作者写的。

kdebug是一个使用gdb“远程调试”接口与内核通信的小工具。使用时首先向内核加
载一个模块,调试器通过/dev/kdebug访问内核数据。gdb将该设备当成一个与被调试
“应用”通信的串口设备,但它仅仅是一个用于访问内核空间的通信通道。由于模
块本身运行在内核空间,它可以看到普通调试器无法访问的内核空间地址。正如你
所猜想到的,模块是一个字符设备驱动程序,并且使用了主设备号动态分配技术。

kdebug的优点在于,你无需打补丁或重新编译:无论是内核还是调试器都无需修
改。你所需要做的就是编译和安装软件包,然后调用kgdb,kgdb是一个完成某些配置
并调用gdb,通过新接口访问内核部件结构的脚本程序。

但是,即便是kdebug也没有提供单步跟踪内核代码和设置断点的功能。这几乎是不
可避免的,因为内核必须保持运行状态以保证系统的出于运行状态,跟踪内核代码
的唯一方法就是后面将要谈到的从另外一台计算机上通过串口控制系统。不过kgdb
的实现允许用户修改被调试应用(即当前内核)的数据项,可以传递给内核任意数
目的参数,并以读写方式访问模块所属的内存区。

最后一个功能就是通过gdb命令将模块符号表增加到调试器内部的符号表中。这个
工作是由kgdb完成的。然后当用户请求访问某个符号时,gdb就知道它的地址是哪
了。最终的访问是由模块里的内核代码完成的。不过要注意,kdebug的当前版本(1.6)
在映射模块化代码地址方面还有些问题。你最好通过打印一些符号并与/proc/ksyms中
的值进行比较来做些检查。如果地址没有匹配,你可以使用数值,但必须将它们强
行转换为正确的类型。下面就是一个强制类型转换的例子:

(代码)

kdebug的另一个强于gdb的优点是,它允许你在数据结构被修改后读取到最新的值,
而不必刷新调试器的缓存;gdb命令set remotecache 0可以用来关闭数据缓存。

由于kdebug与gdb使用起来很相似,这里我就不过多地罗列使用这个工具的例子了。
对于知道如何使用调试器的人来说,这种例子很简单,但对于那些对调试器一无所
知的人来说就很晦涩了。能够熟练地使用调试器需要时间和经验,我不准备在这里
承担老师的责任。

总而言之,kdebug是一个非常好的程序。在线修改数据结构对于开发人员来说是一
个非常大的进步(而且一种将系统挂起的最简单方法)。现在有许多工具可以使你
的开发工作更轻松――例如,在开发scull期间,当模块的使用计数器增长后*,我可
以使用kdebug来将其复位为0。这就不必每次都麻烦我重启机器,登录,再次启动我
的应用程序等等。

远程调试

调试内核镜象的最后一个方法是使用gdb的远程调试能力。

当执行远程调试的时候,你需要两台计算机:一台运行gdb;另一台运行你要调试
的内核。这两台计算机间用普通串口连接起来。如你所料,控制gdb必须能够理解
它所控制的内核的二进制格式。如果这两台计算机是不同的体系结构,必须将调试
器编译为可以支持目标平台的。

在2.0中,Linux内核的Intel版本不支持远程调试,但是Alpha和Sparc版本都支持。在Alpha
版本中,你必须在编译时包含对远程调试的支持,并在启动时通过传递给内核命令
行参数kgdb=1或只有kgdb打开这个功能。在Sparc上,始终包含了对远程调试的支持。
启动选项kgdb=ttyx可以用来选择在哪个串口上控制内核,x可以是a或b。如果没有使
用kgdb=选项,内核就按正常方式启动。

如果在内核中打开了远程调试功能,系统在启动时就会调用一个特殊的初始化函
数,配置被调试内核处理它自己的断点,并且跳转到一个编译自程序中的断点。这
会暂停内核的正常执行,并将控制转移给断点服务例程。这一处理函数在串口线上
等待来自于gdb的命令,当它获得gdb的命令后,就执行相应的功能。通过这一配
置,程序员可以单步跟踪内核代码,设置断点,并且完成gdb所允许的其他任务。

在控制端,需要一个目标镜象的副本(我们假设它是linux.img),还需要一个你要调
试的模块副本。如下命令必须传递给gdb:

file linux.img

file命令告诉gdb哪个二进制文件需要调试。另一种方法是在命令行中传递镜象文件
名。这个文件本身必须和运行在另一端的内核一模一样。

target remote /dev/ttyS1

这条命令通知gdb使用远程计算机做为调试过程的目标。/dev/ttyS1是用来通信的本地
串口,你可以指定任一设备。例如,前面介绍的kdebug软件包中的kgdb脚本使用target
remote /dev/kdebug。

add-symbol-file module.o address

如果你要调试已经加载到被控内核的模块的话,在控制系统上你需要一个模块目标
文件的副本。add-symbol-file通知gdb处理模块文件,假定模块代码被定位在地址address
上了。

尽管远程调试可以用于调试模块,但你还是要加载模块,并且在模块上插入断点前
还需要触发另一个断点,调试模块还是需要很多技巧的。我个人不会使用远程调试
去跟踪模块,除非异步运行的代码,如中断处理函数,出了问题。

⌨️ 快捷键说明

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