(转载)Linux 系统内核的调试

调试是软件开发过程中一个必不可少的环节,在 Linux 内核开发的过程中也不可避免地会面对如何调试内核的问题。但是,Linux 系统的开发者出于保证内核代码正确性的考虑,不愿意在 Linux 内核源代码树中加入一个调试器。他们认为内核中的调试器会误导开发者,从而引入不良的修正[1]。所以对 Linux 内核进行调试一直是个令内核程序员感到棘手的问题,调试工作的艰苦性是内核级的开发区别于用户级开发的一个显著特点。

尽管缺乏一种内置的调试内核的有效方法,但是 Linux 系统在内核发展的过程中也逐渐形成了一些监视内核代码和错误跟踪的技术。同时,许多的补丁程序应运而生,它们为标准内核附加了内核调试的支持。尽管这些补丁有些并不被 Linux 官方组织认可,但他们确实功能完善,十分强大。调试内核问题时,利用这些工具与方法跟踪内核执行情况,并查看其内存和数据结构将是非常有用的。
本文将首先介绍 Linux 内核上的一些内核代码监视和错误跟踪技术,这些调试和跟踪方法因所要求的使用环境和使用方法而各有不同,然后重点介绍三种 Linux 内核的源代码级的调试方法。
1. Linux 系统内核级软件的调试技术
printk() 是调试内核代码时最常用的一种技术。在内核代码中的特定位置加入 printk() 调试调用,可以直接把所关心的信息打打印到屏幕上,从而可以观察程序的执行路径和所关心的变量、指针等信息。 Linux 内核调试器( Linux kernel debugger kdb )是 Linux 内核的补丁,它提供了一种在系统能运行时对内核内存和数据结构进行检查的办法。 Oops KDB 在文章掌握 Linux 调试技术 有详细介绍,大家可以参考。 Kprobes 提供了一个强行进入任何内核例程,并从中断处理器无干扰地收集信息的接口。使用 Kprobes 可以轻松地收集处理器寄存器和全局数据结构等调试信息,而无需对 Linux 内核频繁编译和启动,具体使用方法,请参考 使用 Kprobes 调试内核
以上介绍了进行 Linux 内核调试和跟踪时的常用技术和方法。当然,内核调试与跟踪的方法还不止以上提到的这些。这些调试技术的一个共同的特点在于,他们都不能提供源代码级的有效的内核调试手段,有些只能称之为错误跟踪技术,因此这些方法都只能提供有限的调试能力。下面将介绍三种实用的源代码级的内核调试方法。
2. 使用 KGDB 构建 Linux 内核调试环境
kgdb 提供了一种使用 gdb 调试 Linux 内核的机制。使用 KGDB 可以象调试普通的应用程序那样,在内核中进行设置断点、检查变量值、单步跟踪程序运行等操作。使用 KGDB 调试时需要两台机器,一台作为开发机( Development Machine , 另一台作为目标机( Target Machine ),两台机器之间通过串口或者以太网口相连。串口连接线是一根 RS-232 接口的电缆,在其内部两端的第 2 脚( TXD )与第 3 脚( RXD )交叉相连,第 7 脚(接地脚)直接相连。调试过程中,被调试的内核运行在目标机上, gdb 调试器运行在开发机上。
目前, kgdb 发布支持 i386 x86_64 32-bit PPC SPARC 等几种体系结构的调试器。有关 kgdb 补丁的下载地址见参考资料 [4]
2 1 kgdb 的调试原理
安装 kgdb 调试环境需要为 Linux 内核应用 kgdb 补丁,补丁实现的 gdb 远程调试所需要的功能包括命令处理、陷阱处理及串口通讯 3 个主要的部分。 kgdb 补丁的主要作用是在 Linux 内核中添加了一个调试 Stub 。调试 Stub Linux 内核中的一小段代码,提供了运行 gdb 的开发机和所调试内核之间的一个媒介。 gdb 和调试 stub 之间通过 gdb 串行协议进行通讯。 gdb 串行协议是一种基于消息的 ASCII 码协议,包含了各种调试命令。当设置断点时, kgdb 负责在设置断点的指令前增加一条 trap 指令,当执行到断点时控制权就转移到调试 stub 中去。此时,调试 stub 的任务就是使用远程串行通信协议将当前环境传送给 gdb ,然后从 gdb 处接受命令。 gdb 命令告诉 stub 下一步该做什么,当 stub 收到继续执行的命令时,将恢复程序的运行环境,把对 CPU 的控制权重新交还给内核。


2 2 Kgdb 的安装与设置
下面我们将以 Linux 2.6.7 内核为例详细介绍 kgdb 调试环境的建立过程。
2.2.1 软硬件准备
以下软硬件配置取自笔者进行试验的系统配置情况:


kgdb 补丁的版本遵循如下命名模式: Linux-A-kgdb-B ,其中 A 表示 Linux 的内核版本号, B kgdb 的版本号。以试验使用的 kgdb 补丁为例, linux 内核的版本为 linux-2.6.7 ,补丁版本为 kgdb-2.2
物理连接好串口线后,使用以下命令来测试两台机器之间串口连接情况, stty 命令可以对串口参数进行设置:
development 机上执行:
 

stty ispeed 115200 ospeed 115200 -F /dev/ttyS0
 
target 机上执行:
 

stty ispeed 115200 ospeed 115200 -F /dev/ttyS0
 
developement 机上执行:
 

echo hello > /dev/ttyS0
 
target 机上执行:
 

cat /dev/ttyS0
 
如果串口连接没问题的话在将在 target 机的屏幕上显示 "hello"
2 2 2 安装与配置
下面我们需要应用 kgdb 补丁到 Linux 内核,设置内核选项并编译内核。这方面的资料相对较少,笔者这里给出详细的介绍。下面的工作在开发机( developement )上进行,以上面介绍的试验环境为例,某些具体步骤在实际的环境中可能要做适当的改动:
I 、内核的配置与编译
 

[root@lisl tmp]# tar -jxvf linux-2.6.7.tar.bz2
[root@lisl tmp]#tar -jxvf linux-2.6.7-kgdb-2.2.tar.tar
[root@lisl tmp]#cd inux-2.6.7
 
请参照目录补丁包中文件 README 给出的说明,执行对应体系结构的补丁程序。由于试验在 i386 体系结构上完成,所以只需要安装一下补丁: core-lite.patch i386-lite.patch 8250.patch eth.patch core.patch i386.patch 。应用补丁文件时,请遵循 kgdb 软件包内 series 文件所指定的顺序,否则可能会带来预想不到的问题。 eth.patch 文件是选择以太网口作为调试的连接端口时需要运用的补丁
应用补丁的命令如下所示:
 

[root@lisl tmp]#patch -p1 <../linux-2.6.7-kgdb-2.2/core-lite.patch
 
如果内核正确,那么应用补丁时应该不会出现任何问题(不会产生 *.rej 文件)。为 Linux 内核添加了补丁之后,需要进行内核的配置。内核的配置可以按照你的习惯选择配置 Linux 内核的任意一种方式。
 

[root@lisl tmp]#make menuconfig
 
在内核配置菜单的 Kernel hacking 选项中选择 kgdb 调试项,例如:
 

 [*] KGDB: kernel debugging with remote gdb                                                            
       Method for KGDB communication (KGDB: On generic serial port (8250)) ---> 
 [*] KGDB: Thread analysis                                                                            
 [*] KGDB: Console messages through gdb
[root@lisl tmp]#make
 
 
编译内核之前请注意 Linux 目录下 Makefile 中的优化选项,默认的 Linux 内核的编译都以 -O2 的优化级别进行。在这个优化级别之下,编译器要对内核中的某些代码的执行顺序进行改动,所以在调试时会出现程序运行与代码顺序不一致的情况。可以把 Makefile 中的 -O2 选项改为 -O, 但不可去掉 - O ,否则编译会出问题。为了使编译后的内核带有调试信息,注意在编译内核的时候需要加上 -g 选项。
不过,当选择 "Kernel debugging->Compile the kernel with debug info" 选项后配置系统将自动打开调试选项。另外,选择 "kernel debugging with remote gdb" 后,配置系统将自动打开 "Compile the kernel with debug info" 选项。
内核编译完成后,使用 scp 命令进行将相关文件拷贝到 target 机上 ( 当然也可以使用其它的网络工具,如 rcp)
 

[root@lisl tmp]#scp arch/i386/boot/bzImage root@192.168.6.13:/boot/vmlinuz-2.6.7-kgdb
[root@lisl tmp]#scp System.map root@192.168.6.13:/boot/System.map-2.6.7-kgdb
 
如果系统启动使所需要的某些设备驱动没有编译进内核的情况下,那么还需要执行如下操作:
 

[root@lisl tmp]#mkinitrd /boot/initrd-2.6.7-kgdb 2.6.7
[root@lisl tmp]#scp initrd-2.6.7-kgdb root@192.168.6.13:/boot/ initrd-2.6.7-kgdb
 
II kgdb 的启动
在将编译出的内核拷贝的到 target 机器之后,需要配置系统引导程序,加入内核的启动选项。以下是 kgdb 内核引导参数的说明:


如表中所述,在 kgdb 2.0 版本之后内核的引导参数已经与以前的版本有所不同。使用 grub 引导程序时,直接将 kgdb 参数作为内核 vmlinuz 的引导参数。下面给出引导器的配置示例。
 

title 2.6.7 kgdb
root (hd0,0)
kernel /boot/vmlinuz-2.6.7-kgdb ro root=/dev/hda1 kgdbwait kgdb8250=1,115200
 
在使用 lilo 作为引导程序时,需要把 kgdb 参放在由 append 修饰的语句中。下面给出使用 lilo 作为引导器时的配置示例。
 

image=/boot/vmlinuz-2.6.7-kgdb
label=kgdb
    read-only
    root=/dev/hda3
append="gdb gdbttyS=1 gdbbaud=115200"
 
保存好以上配置后重新启动计算机,选择启动带调试信息的内核,内核将在短暂的运行后在创建 init 内核线程之前停下来,打印出以下信息,并等待开发机的连接。
Waiting for connection from remote gdb...
在开发机上执行:
 

gdb
file vmlinux
set remotebaud 115200
target remote /dev/ttyS0
 
其中 vmlinux 是指向源代码目录下编译出来的 Linux 内核文件的链接,它是没有经过压缩的内核文件, gdb 程序从该文件中得到各种符号地址信息。
这样,就与目标机上的 kgdb 调试接口建立了联系。一旦建立联接之后,对 Linux 内的调试工作与对普通的运用程序的调试就没有什么区别了。任何时候都可以通过键入 ctrl+c 打断目标机的执行,进行具体的调试工作。
kgdb 2.0 之前的版本中,编译内核后在 arch/i386/kernel 目录下还会生成可执行文件 gdbstart 。将该文件拷贝到 target 机器的 /boot 目录下,此时无需更改内核的启动配置文件,直接使用命令:
 

[root@lisl boot]#gdbstart -s 115200 -t /dev/ttyS0
 
可以在 KGDB 内核引导启动完成后建立开发机与目标机之间的调试联系。
2 2 3 通过网络接口进行调试
kgdb 也支持使用以太网接口作为调试器的连接端口。在对 Linux 内核应用补丁包时,需应用 eth.patch 补丁文件。配置内核时在 Kernel hacking 中选择 kgdb 调试项,配置 kgdb 调试端口为以太网接口,例如:
 

[*]KGDB: kernel debugging with remote gdb
Method for KGDB communication (KGDB: On ethernet) --->
( ) KGDB: On generic serial port (8250)
(X) KGDB: On ethernet
 
另外使用 eth0 网口作为调试端口时, grub.list 的配置如下:
 

title 2.6.7 kgdb
root (hd0,0)
kernel /boot/vmlinuz-2.6.7-kgdb ro root=/dev/hda1 kgdbwait kgdboe=@192.168.
5.13/,@192.168. 6.13/
 
其他的过程与使用串口作为连接端口时的设置过程相同。
注意:尽管可以使用以太网口作为 kgdb 的调试端口,使用串口作为连接端口更加简单易行, kgdb 项目组推荐使用串口作为调试端口。
2 2 4 模块的调试方法
内核可加载模块的调试具有其特殊性。由于内核模块中各段的地址是在模块加载进内核的时候才最终确定的,所以 develop 机的 gdb 无法得到各种符号地址信息。所以,使用 kgdb 调试模块所需要解决的一个问题是,需要通过某种方法获得可加载模块的最终加载地址信息,并把这些信息加入到 gdb 环境中。
I 、在 Linux 2.4 内核中的内核模块调试方法
Linux2.4.x 内核中,可以使用 insmod -m 命令输出模块的加载信息,例如:
 

[root@lisl tmp]# insmod -m hello.ko >modaddr
 
查看模块加载信息文件 modaddr 如下:
 

.this           00000060 c88d8000 2**2
.text           00000035 c88d8060 2**2
.rodata         00000069 c88d80a0 2**5
……
.data           00000000 c88d833c 2**2
.bss            00000000 c88d833c 2**2
……
 
在这些信息中,我们关心的只有 4 个段的地址 :.text .rodata .data .bss 。在 development 机上将以上地址信息加入到 gdb , 这样就可以进行模块功能的测试了。
 

(gdb) Add-symbol-file hello.o 0xc88d8060 -s .data 0xc88d80a0 -s
.rodata 0xc88d80a0 -s .bss 0x c88d833c
 
这种方法也存在一定的不足,它不能调试模块初始化的代码,因为此时模块初始化代码已经执行过了。而如果不执行模块的加载又无法获得模块插入地址,更不可能在模块初始化之前设置断点了。对于这种调试要求可以采用以下替代方法。
target 机上用上述方法得到模块加载的地址信息,然后再用 rmmod 卸载模块。在 development 机上将得到的模块地址信息导入到 gdb 环境中,在内核代码的调用初始化代码之前设置断点。这样,在 target 机上再次插入模块时,代码将在执行模块初始化之前停下来,这样就可以使用 gdb 命令调试模块初始化代码了。
另外一种调试模块初始化函数的方法是:当插入内核模块时,内核模块机制将调用函数 sys_init_module(kernel/modle.c) 执行对内核模块的初始化,该函数将调用所插入模块的初始化函数。程序代码片断如下:
 

…… ……
   if (mod->init != NULL)
      ret = mod->init();
…… ……
 
在该语句上设置断点,也能在执行模块初始化之前停下来。
II 、在 Linux 2.6.x 内核中的内核模块调试方法
Linux 2.6 之后的内核中,由于 module-init-tools 工具的更改, insmod 命令不再支持 -m 参数,只有采取其他的方法来获取模块加载到内核的地址。通过分析 ELF 文件格式,我们知道程序中各段的意义如下:
.text (代码段):用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存种的镜像。
.data (数据段):数据段用来存放可执行文件中已初始化全局变量,也就是存放程序静态分配的变量和全局变量。
.bss BSS 段): BSS 段包含了程序中未初始化全局变量,在内存中 bss 段全部置零。
.rodata (只读段):该段保存着只读数据,在进程映象中构造不可写的段。
通过在模块初始化函数中放置一下代码,我们可以很容易地获得模块加载到内存中的地址。
 

……
int bss_var;
static int hello_init(void)
{
printk(KERN_ALERT "Text location .text(Code Segment):%p/n",hello_init);

static int data_var=0;
printk(KERN_ALERT "Data Location .data(Data Segment):%p/n",&data_var);

printk(KERN_ALERT "BSS Location: .bss(BSS Segment):%p/n",&bss_var);
……
}
Module_init(hello_init);
 
这里,通过在模块的初始化函数中添加一段简单的程序,使模块在加载时打印出在内核中的加载地址。 .rodata 段的地址可以通过执行命令 readelf -e hello.ko ,取得 .rodata 在文件中的偏移量并加上段的 align 值得出。
为了使读者能够更好地进行模块的调试, kgdb 项目还发布了一些脚本程序能够自动探测模块的插入并自动更新 gdb 中模块的符号信息。这些脚本程序的工作原理与前面解释的工作过程相似,更多的信息请阅读参考资料 [4]
2 2 5 硬件断点
kgdb 提供对硬件调试寄存器的支持。在 kgdb 中可以设置三种硬件断点:执行断点( Execution Breakpoint )、写断点( Write Breakpoint )、访问断点( Access Breakpoint )但不支持 I/O 访问的断点。目前, kgdb 对硬件断点的支持是通过宏来实现的,最多可以设置 4 个硬件断点,这些宏的用法如下:


在有些情况下,硬件断点的使用对于内核的调试是非常方便的。有关硬件断点的定义和具体的使用说明见参考资料 [4]
2 3 .在 VMware 中搭建调试环境
kgdb 调试环境需要使用两台微机分别充当 development 机和 target 机,使用 VMware 后我们只使用一台计算机就可以顺利完成 kgdb 调试环境的搭建。以 windows 下的环境为例,创建两台虚拟机,一台作为开发机,一台作为目标机。
2 3 1 虚拟机之间的串口连接
虚拟机中的串口连接可以采用两种方法。一种是指定虚拟机的串口连接到实际的 COM 上,例如开发机连接到 COM1 ,目标机连接到 COM2 ,然后把两个串口通过串口线相连接。另一种更为简便的方法是:在较高一些版本的 VMware 中都支持把串口映射到命名管道,把两个虚拟机的串口映射到同一个命名管道。例如,在两个虚拟机中都选定同一个命名管道 //./pipe/com_1, 指定 target 机的 COM 口为 server 端,并选择 "The other end is a virtual machine" 属性;指定 development 机的 COM 口端为 client 端,同样指定 COM 口的 "The other end is a virtual machine" 属性。对于 IO mode 属性,在 target 上选中 "Yield CPU on poll" 复选择框, development 机不选。这样,可以无需附加任何硬件,利用虚拟机就可以搭建 kgdb 调试环境。即降低了使用 kgdb 进行调试的硬件要求,也简化了建立调试环境的过程。


2 3 2 VMware 的使用技巧
VMware 虚拟机是比较占用资源的,尤其是象上面那样在 Windows 中使用两台虚拟机。因此,最好为系统配备 512M 以上的内存,每台虚拟机至少分配 128M 的内存。这样的硬件要求,对目前主流配置的 PC 而言并不是过高的要求。出于系统性能的考虑,在 VMware 中尽量使用字符界面进行调试工作。同时, Linux 系统默认情况下开启了 sshd 服务,建议使用 SecureCRT 登陆到 Linux 进行操作,这样可以有较好的用户使用界面。
2 3 3 Linux 下的虚拟机中使用 kgdb
对于在 Linux 下面使用 VMware 虚拟机的情况,笔者没有做过实际的探索。从原理上而言,只需要在 Linux 下只要创建一台虚拟机作为 target 机,开发机的工作可以在实际的 Linux 环境中进行,搭建调试环境的过程与上面所述的过程类似。由于只需要创建一台虚拟机,所以使用 Linux 下的虚拟机搭建 kgdb 调试环境对系统性能的要求较低。( vmware 已经推出了 Linux 下的版本)还可以在 development 机上配合使用一些其他的调试工具,例如功能更强大的 cgdb 、图形界面的 DDD 调试器等,以方便内核的调试工作。


2 4 kgdb 的一些特点和不足
使用 kgdb 作为内核调试环境最大的不足在于对 kgdb 硬件环境的要求较高,必须使用两台计算机分别作为 target development 机。尽管使用虚拟机的方法可以只用一台 PC 即能搭建调试环境,但是对系统其他方面的性能也提出了一定的要求,同时也增加了搭建调试环境时复杂程度。另外, kgdb 内核的编译、配置也比较复杂,需要一定的技巧,笔者当时做的时候也是费了很多周折。当调试过程结束后时,还需要重新制作所要发布的内核。使用 kgdb 并不能进行全程调试,也就是说 kgdb 并不能用于调试系统一开始的初始化引导过程。
不过, kgdb 是一个不错的内核调试工具,使用它可以进行对内核的全面调试,甚至可以调试内核的中断处理程序。如果在一些图形化的开发工具的帮助下,对内核的调试将更方便。
 
 
 
3. 使用 SkyEye 构建 Linux 内核调试环境
SkyEye 是一个开源软件项目( OPenSource Software ,SkyEye 项目的目标是在通用的 Linux Windows 平台上模拟常见的嵌入式计算机系统。 SkyEye 实现了一个指令级的硬件模拟平台,可以模拟多种嵌入式开发板,支持多种 CPU 指令集。 SkyEye 的核心是 GNU gdb 项目,它把 gdb ARM Simulator 很好地结合在了一起。加入 ARMulator 的功能之后,它就可以来仿真嵌入式开发板,在它上面不仅可以调试硬件驱动,还可以调试操作系统。 Skyeye 项目目前已经在嵌入式系统开发领域得到了很大的推广。
3 1 SkyEye 的安装和 μcLinux 内核编译
3 1.1 SkyEye 的安装
SkyEye 的安装不是本文要介绍的重点,目前已经有大量的资料对此进行了介绍。有关 SkyEye 的安装与使用的内容请查阅参考资料 [11] 。由于 skyeye 面目主要用于嵌入式系统领域,所以在 skyeye 上经常使用的是 μcLinux 系统,当然使用 Linux 作为 skyeye 上运行的系统也是可以的。由于介绍 μcLinux 2.6 skyeye 上编译的相关资料并不多,所以下面进行详细介绍。
3 1.2 μcLinux 2.6.x 的编译
要在 SkyEye 中调试操作系统内核,首先必须使被调试内核能在 SkyEye 所模拟的开发板上正确运行。因此,正确编译待调试操作系统内核并配置 SkyEye 是进行内核调试的第一步。下面我们以 SkyEye 模拟基于 Atmel AT91X40 的开发板,并运行 μcLinux 2.6 为例介绍 SkyEye 的具体调试方法。
I 、安装交叉编译环境
先安装交叉编译器。尽管在一些资料中说明使用工具链 arm -elf-tools-20040427.sh , 但是由于 arm-elf-xxx arm-linux-xxx 对宏及链接处理的不同,经验证明使用 arm-elf-xxx 工具链在链接 vmlinux 的最后阶段将会出错。所以这里我们使用的交叉编译工具链是: arm-uclinux-tools-base-gcc3.4.0-20040713.sh ,关于该交叉编译工具链的下载地址请参见 [6] 。注意以下步骤最好用 root 用户来执行。
 

[root@lisl tmp]#chmod +x arm-uclinux-tools-base-gcc3.4.0-20040713.sh
[root@lisl tmp]#./arm-uclinux-tools-base-gcc3.4.0-20040713.sh
 
安装交叉编译工具链之后,请确保工具链安装路径存在于系统 PATH 变量中。
II 、制作 μcLinux 内核
得到 μcLinux 发布包的一个最容易的方法是直接访问 uClinux.org 站点 [7] 。该站点发布的内核版本可能不是最新的,但你能找到一个最新的 μcLinux 补丁以及找一个对应的 Linux 内核版本来制作一个最新的 μcLinux 内核。这里,将使用这种方法来制作最新的 μcLinux 内核。目前(笔者记录编写此文章时),所能得到的发布包的最新版本是 uClinux-dist.20041215.tar.gz
下载 uClinux-dist.20041215.tar.gz ,文件的下载地址请参见 [7]
下载 linux-2.6.9-hsc0.patch.gz ,文件的下载地址请参见 [8]
下载 linux-2.6.9.tar.bz2 ,文件的下载地址请参见 [9]
现在我们得到了整个的 linux-2.6.9 源代码,以及所需的内核补丁。请准备一个有 2GB 空间的目录里来完成以下制作 μcLinux 内核的过程。
 

[root@lisl tmp]# tar -jxvf uClinux-dist-20041215.tar.bz2
[root@lisl uClinux-dist]# tar -jxvf linux-2.6.9.tar.bz2
[root@lisl uClinux-dist]# gzip -dc linux-2.6.9-hsc0.patch.gz | patch -p0
 
或者使用:
 

[root@lisl uClinux-dist]# gunzip linux-2.6.9-hsc0.patch.gz
[root@lisl uClinux-dist]patch -p0 < linux-2.6.9-hsc0.patch
 
执行以上过程后,将在 linux-2.6.9/arch 目录下生成一个补丁目录- armnommu 。删除原来 μcLinux 目录里的 linux-2.6.x( 即那个 linux-2.6.9-uc0) ,并将我们打好补丁的 Linux 内核目录更名为 linux-2.6.x
 

[root@lisl uClinux-dist]# rm -rf linux-2.6.x/
[root@lisl uClinux-dist]# mv linux-2.6.9 linux-2.6.x
 
III 、配置和编译 μcLinux 内核
因为只是出于调试 μcLinux 内核的目的,这里没有生成 uClibc 库文件及 romfs.img 文件。在发布 μcLinux 时,已经预置了某些常用嵌入式开发板的配置文件,因此这里直接使用这些配置文件,过程如下:
 

[root@lisl uClinux-dist]# cd linux-2.6.x
[root@lisl linux-2.6.x]#make ARCH=armnommu CROSS_COMPILE=arm-uclinux- atmel_
deconfig
 
atmel_deconfig 文件是 μcLinux 发布时提供的一个配置文件,存放于目录 linux-2.6.x /arch/armnommu/configs/ 中。
 

[root@lisl linux-2.6.x]#make ARCH=armnommu CROSS_COMPILE=arm-uclinux-
oldconfig
 
下面编译配置好的内核:
 

[root@lisl linux-2.6.x]# make ARCH=armnommu CROSS_COMPILE=arm-uclinux- v=1
 
一般情况下,编译将顺利结束并在 Linux-2.6.x/ 目录下生成未经压缩的 μcLinux 内核文件 vmlinux 。需要注意的是为了调试 μcLinux 内核,需要打开内核编译的调试选项 -g ,使编译后的内核带有调试信息。打开编译选项的方法可以选择:
"Kernel debugging->Compile the kernel with debug info" 后将自动打开调试选项。也可以直接修改 linux-2.6.x 目录下的 Makefile 文件,为其打开调试开关。方法如下:。
 

CFLAGS += -g
 
最容易出现的问题是找不到 arm-uclinux-gcc 命令的错误,主要原因是 PATH 变量中没有包含 arm-uclinux-gcc 命令所在目录。在 arm-linux-gcc 的缺省安装情况下,它的安装目录是 /root/bin/arm-linux-tool/ ,使用以下命令将路径加到 PATH 环境变量中。
 

Export PATH
=$PATH:/root/bin/arm-linux-tool/bin
 
IV 、根文件系统的制作
Linux 内核在启动的时的最后操作之一是加载根文件系统。根文件系统中存放了嵌入式系统使用的所有应用程序、库文件及其他一些需要用到的服务。出于文章篇幅的考虑,这里不打算介绍根文件系统的制作方法,读者可以查阅一些其他的相关资料。值得注意的是,由配置文件 skyeye.conf 指定了装载到内核中的根文件系统。
3 2 使用 SkyEye 调试
编译完 μcLinux 内核后,就可以在 SkyEye 中调试该 ELF 执行文件格式的内核了。前面已经说过利用 SkyEye 调试内核与使用 gdb 调试运用程序的方法相同。
需要提醒读者的是, SkyEye 的配置文件- skyeye.conf 记录了模拟的硬件配置和模拟执行行为。该配置文件是 SkyEye 系统中一个及其重要的文件,很多错误和异常情况的发生都和该文件有关。在安装配置 SkyEye 出错时,请首先检查该配置文件然后再进行其他的工作。此时,所有的准备工作已经完成,就可以进行内核的调试工作了。
3 3 使用 SkyEye 调试内核的特点和不足
SkyEye 中可以进行对 Linux 系统内核的全程调试。由于 SkyEye 目前主要支持基于 ARM 内核的 CPU ,因此一般而言需要使用交叉编译工具编译待调试的 Linux 系统内核。另外,制作 SkyEye 中使用的内核编译、配置过程比较复杂、繁琐。不过,当调试过程结束后无需重新制作所要发布的内核。
SkyEye 只是对系统硬件进行了一定程度上的模拟,所以在 SkyEye 与真实硬件环境相比较而言还是有一定的差距,这对一些与硬件紧密相关的调试可能会有一定的影响,例如驱动程序的调试。不过对于大部分软件的调试, SkyEye 已经提供了精度足够的模拟了。
SkyEye 的下一个目标是和 eclipse 结合,有了图形界面,能为调试和查看源码提供一些方便。
 
 
 
4. 使用 UML 调试 Linux 内核
User-mode Linux UML )简单说来就是在 Linux 内运行的 Linux 。该项目是使 Linux 内核成为一个运行在 Linux 系统之上单独的、用户空间的进程。 UML 并不是运行在某种新的硬件体系结构之上,而是运行在基于 Linux 系统调用接口所实现的虚拟机。正是由于 UML 是一个将 Linux 作为用户空间进程运行的特性,可以使用 UML 来进行操作系统内核的调试。有关 UML 的介绍请查阅参考资料 [10] [12]
4 1 UML 的安装与调试
UML 的安装需要一台运行 Linux 2.2.15 以上,或者 2.3.22 以上的 I386 机器。对于 2.6.8 及其以前版本的 UML ,采用两种形式发布:一种是以 RPM 包的形式发布,一种是以源代码的形式提供 UML 的安装。按照 UML 的说明,以 RPM 形式提供的安装包比较陈旧且会有许多问题。以二进制形式发布的 UML 包并不包含所需要的调试信息,这些代码在发布时已经做了程度不同的优化。所以,要想利用 UML 调试 Linux 系统内核,需要使用最新的 UML patch 代码和对应版本的 Linux 内核编译、安装 UML 。完成 UML 的补丁之后,会在 arch 目录下产生一个 um 目录,主要的 UML 代码都放在该目录下。
2.6.9 版本之后(包含 2.6.9 版本的 Linux ), User-Mode Linux 已经随 Linux 内核源代码树一起发布,它存放于 arch/um 目录下。
编译好 UML 的内核之后,直接使用 gdb 运行已经编译好的内核即可进行调试。
4 2 使用 UML 调试系统内核的特点和不足
目前,用户模式 Linux 虚拟机也存在一定的局限性。由于 UML 虚拟机是基于 Linux 系统调用接口的方式实现的虚拟机,所以用户模式内核不能访问主机系统上的硬件设备。因此, UML 并不适合于调试那些处理实际硬件的驱动程序。不过,如果所编写的内核程序不是硬件驱动,例如 Linux 文件系统、协议栈等情况,使用 UML 作为调试工具还是一个不错的选择。
 
 
 
5. 内核调试配置选项
为了方便调试和测试代码,内核提供了许多与内核调试相关的配置选项。这些选项大部分都在内核配置编辑器的内核开发 (kernel hacking) 菜单项中。在内核配置目录树菜单的其他地方也还有一些可配置的调试选项,下面将对他们作一定的介绍。
Page alloc debugging CONFIG_DEBUG_PAGEALLOC:
不使用该选项时,释放的内存页将从内核地址空间中移出。使用该选项后,内核推迟移出内存页的过程,因此能够发现内存泄漏的错误。
Debug memory allocations CONFIG_DEBUG_SLAB:
该打开该选项时,在内核执行内存分配之前将执行多种类型检查,通过这些类型检查可以发现诸如内核过量分配或者未初始化等错误。内核将会在每次分配内存前后时设置一些警戒值,如果这些值发生了变化那么内核就会知道内存已经被操作过并给出明确的提示,从而使各种隐晦的错误变得容易被跟踪。
Spinlock debugging CONFIG_DEBUG_SPINLOCK:
打开此选项时,内核将能够发现 spinlock 未初始化及各种其他的错误 , 能用于排除一些死锁引起的错误。
Sleep-inside-spinlock checking CONFIG_DEBUG_SPINLOCK_SLEEP:
打开该选项时,当 spinlock 的持有者要睡眠时会执行相应的检查。实际上即使调用者目前没有睡眠,而只是存在睡眠的可能性时也会给出提示。
Compile the kernel with debug info CONFIG_DEBUG_INFO:
打开该选项时,编译出的内核将会包含全部的调试信息,使用 gdb 时需要这些调试信息。
Stack utilization instrumentation CONFIG_DEBUG_STACK_USAGE:
该选项用于跟踪内核栈的溢出错误,一个内核栈溢出错误的明显的现象是产生 oops 错误却没有列出系统的调用栈信息。该选项将使内核进行栈溢出检查,并使内核进行栈使用的统计。
Driver Core verbose debug messages CONFIG_DEBUG_DRIVER:
该选项位于 "Device drivers-> Generic Driver Options" 下,打开该选项使得内核驱动核心产生大量的调试信息,并将他们记录到系统日志中。
Verbose SCSI error reporting (kernel size +=12K) CONFIG_SCSI_CONSTANTS:
该选项位于 "Device drivers/SCSI device support" 下。当 SCSI 设备出错时内核将给出详细的出错信息。
Event debugging CONFIG_INPUT_EVBUG:
打开该选项时,会将输入子系统的错误及所有事件都输出到系统日志中。该选项在产生了详细的输入报告的同时,也会导致一定的安全问题。
以上内核编译选项需要读者根据自己所进行的内核编程的实际情况,灵活选取。在使用以上介绍的三种源代码级的内核调试工具时,一般需要选取 CONFIG_DEBUG_INFO 选项,以使编译的内核包含调试信息。
 
 
 
6. 总结
上面介绍了一些调试 Linux 内核的方法,特别是详细介绍了三种源代码级的内核调试工具,以及搭建这些内核调试环境的方法,读者可以根据自己的情况从中作出选择。
调试工具(例如 gdb )的运行都需要操作系统的支持,而此时内核由于一些错误的代码而不能正确执行对系统的管理功能,所以对内核的调试必须采取一些特殊的方法进行。以上介绍的三种源代码级的调试方法,可以归纳为以下两种策略:
I 、为内核增加调试 Stub ,利用调试 Stub 进行远程调试,这种调试策略需要 target development 机器才能完成调试任务。
II 、将虚拟机技术与调试工具相结合,使 Linux 内核在虚拟机中运行从而利用调试器对内核进行调试。这种策略需要制作适合在虚拟机中运行的系统内核。
由不同的调试策略决定了进行调试时不同的工作原理,同时也形成了各种调试方法不同的软硬件需求和各自的特点。
另外,需要说明的是内核调试能力的掌握很大程度上取决于经验和对整个操作系统的深入理解。对系统内核的全面深入的理解,将能在很大程度上加快对 Linux 系统内核的开发和调试。
对系统内核的调试技术和方法绝不止上面介绍所涉及的内容,这里只是介绍了一些经常看到和听到方法。在 Linux 内核向前发展的同时,内核的调试技术也在不断的进步。希望以上介绍的一些方法能对读者开发和学习 Linux 有所帮助。
 
 
 
 
 
 
[1]Robert Love Linux kernel development 机械工业出版社
[2] 陈渝 源代码开发的嵌入式系统软件分析与实践 北京航空航天大学出版社
[3]Alessandro Rubini Linux device driver 2se Edition O'Reilly
[4]Jonathan Corbet Linux device driver 3rd Edition O'Reilly
[5] 李善平 Linux 内核源代码分析大全 机械工业出版社
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值