如何调试Linux内核

  如果想深入理解Linux内核,我们就不得不接触Linux内核的调试。常用的Linux内核调试器是KDB。

  KDB是一款开源的调试工具,由SGI公司开发,嵌入在Linux内核中为内核程序员提供调试支持。它适用于内核及驱动程序的调试,目前支持的CPU体系结构有:x86, IA64和MIPS等。

  对于内核感兴趣的人可以使用kdb来查看内核的数据结构和运行状态,从而加深对内核的理解。KDB的弱点在于:不支持源代码级的调试,需要调试人员有一定的

(1)如何安装KDB?

  官方发布的Linux内核源代码中并不包括KDB,它是一个补充。KDB通过修改内核源代码将自身嵌入到内核中来提供调试功能。要使用KDB就需要重新编译内核,编译后的内核中含有KDB的代码。

  kdb的源代码是由SGI提供的,网上也有许多站点提供其源代码包。在下载之前,需要知道所使用的Linux内核的版本。针对不同的Linux kernel,kdb有不同的源码包。这里假定我们使用的是2.4.7的内核。在如下地址http://oss.sgi.com/projects/kdb可以找到关于kdb的简短介绍。SGI提供ftp下载,地址为:ftp://oss.sgi.com/www/projects/kdb/download/ix86 ,在此目录下,找到相应版本的的kdb源码包。源码包有两种格式,一种是.gz格式压缩,一种是.bz2格式压缩,文件名后缀分别为.gz和.bz2,用户可以根据自己的情况选择相应格式的文件下载。源码包以如下格式进行命名:kdb-vX.X-Y.Y.Y.bz2(.gz),其中X.X为kdb的版本号,而Y.Y.Y为所对应的Linux内核的版本号。根据所使用的内核版本,选择相应的Y.Y.Y后缀的文件下载。注意:kdb仅仅提供对Linux官方发布的内核版本的支持,如果使用发行商修改的内核版本,譬如Redhat 7.2的内核版本为2.4.7-10,这是经过Red Hat修改的内核版本,kdb没有相应的版本,如果选择为2.4.7而写的kdb版本,在对内核重编译时将会失败。因此,如果要使用kdb,必须使用官方发布的内核版本。目前kdb提供从2.2.3到2.4.19之间的所有内核版本的相应源代码包。

  下载kdb源码包后,将源码包解压缩,拷贝到内核源代码目录下,然后执行如下命令:

  #cd /usr/src/Linux-2.4.7   

  #patch p1 < kdb-v1.8-2.4.7     

  注意:如果没有安装内核源代码,需要先安装内核源代码。

  执行上述步骤之后,运行内核配置程序:

  #make menuconfig

  进入配置菜单,进入Kernel Hacking,这时可以看到屏幕菜单中多了几项,选择Magic SysRq Key和Built-in Kernel Debugger Support。第三项kdb off by default意味着是否缺省启动kdb,如果选择它,那么用户需要在使用kdb之前显式地激活它。如果需要,选择Compile the kernel with frame pointer。选择最后一项,表示在编译时使帧指针寄存器不作为通用寄存器使用,而是专用于帧指针。配置完成后,保存配置文件,退出配置程序。

  依照以下步骤重新编译内核:    

  #make clean   

  #make dep   

  #make bzImage

  将新内核拷贝到boot目录下:    

  #cp /usr/src/Linux-2.4.7/arch/i386/boot/bzImage /boot/kdbkernel     

  修改lilo.conf,加入如下几行:    

  image=/boot/kdbkernel   

  label = kdbkernel   

  read-only   

  root = /dev/hda1     

  运行lilo,这时lilo应该已经可以引导新内核。至此,kdb安装就完成了。

(2) 如何使用KDB?

  在我们使用新内核重新启动机器后,内核已经有kdb支持了。那么如何进入调试环境呢?还有两种情况。一种是在配置内核时,没有选择kdb-off by default选项,那么在内核启动后,按下“pause”键即可进入kdb调试环境。另一种情况恰恰相反,就是在配置内核时选择了kdb-off by default选项。这种情况下有两种办法可以进入kdb的调试环境:一种是在启动时加入“kdb=on”,另一种方式是在proc文件系统加载后,输入如下命令:     

  #echo 1”>/proc/sys/kernel/kdb。    

然后就可以按“pause”键进入调试环境了。按“pause”键后,出现提示符kdb>,同时键盘上Caps和Scroll两指示灯不停闪烁,提示现在处于kdb调试环境中。    

  kdb提供丰富的命令实现运行控制、内存操纵、寄存器操纵、断点设置、堆栈跟踪等许多功能,总共有33条命令,我们分别进行介绍。

   断点类     

  kdb提供强大的断点功能,包括设置断点、清除断点、激活断点、使断点失效,kdb也可以设置硬件断点。断点指令包括bp、bl、bpa、bph、bpha、bc、be和bd。 

   

  bp:设置或者显示断点    

  格式:bp [<vaddr>]     

  该命令设置一个新的断点,其中vaddr是要设置的断点的地址。如果不带参数,运行bp将显示当前设置的所有断点。  

  

  bl:设置或者显示断点    

  格式:bl [<vaddr>]     

  该命令的操作与bp命令相同。  

  

  bpa:设置或者显示全局断点    

  格式:bpa [<vaddr>]     

  该命令设置一个全局断点,或者显示所有全局断点,用法同上。 

   

  bph:设置硬件断点或者显示所有断点    

  格式:bph [vaddr [datar|dataw|io [length]]]     

  如果不带参数,则显示所有断点。如果带参数,那么设置断点。其中vaddr为要设置硬件断点的地址,datar表示对该内存区进行读操作,dataw表示写操作,io表示对该内存区进行io输入输出操作。length指明读写io操作的数据长度。    

  bpha:设置硬件断点或者显示所有断点    

  格式和用法同bph。   

 

  bc:清除断点    

  格式:bc <bpnum>     

  清除标号为bpnum的断点。如果断点号为“*”,将清除所有断点。 

   

  bd:使断点无效    

  格式:bd <bpnum>     

  使标号为bpnum的断点无效,如果标号为“*”,表示使所有断点无效。   

 

  be:激活断点    

  格式:be <bpnum>     

  激活标号为bpnum的断点。如果标号为“*”,将激活所有无效的断点。

  运行控制类     

  包括go、ss和ssb三个命令,提供对程序执行的控制。具体用法如下:

    

  go:继续程序执行    

  格式:go     

  该命令使内核继续执行,直到遇到一个断点才停止。如果没有设置断点,该命令将离开kdb调试器,系统回到正常运行状态。Caps和Scroll指示灯恢复到原来的状态。  

  

  ss:单步执行程序    

  格式:ss     

  该命令仅仅执行下一条指令,执行完后停止。这在进行跟踪时是必不可少的。

    

  ssb:执行到分支或者函数调用时停止    

  格式:ssb     

  该命令与ss的区别是,ss只执行一条语句,而ssb执行一组语句,它使指令继续执行,在遇到一个分支语句,或者遇到一个函数调用语句时停止。

  内存操作类     

  内存操作类命令包括对内存进行显示和修改的md、mdr、mds、mm四条命令。   

 

  md:显示内存内容    

  格式1:md [vaddr [line-count [output-radix]] ]     

  显示地址为vaddr的内存的内容。line-count为要显示的内存的行数,output-radix指定以8进制、10进制或者16进制显示。如果省略line-count和output-radix,那么将以设置的环境变量MDCOUNT和RADIX方式显示。如果不带任何参数,md命令将接着上次md命令的后续地址显示内存内容。

  格式2:mdWcn     

  在缺省情况下,md以当前环境变量BYTESPERWORD的值读取数据,在读取硬件寄存器的时候,需要指定数据的宽度。这是可以使用mdWcn来进行读取,W是读取的宽度,单位是字节,cn为要读取的数目。

     

  mdr:显示原始内存的内容    

  格式:mdr <vaddr> <count>     

  从指定地址vaddr开始显示count长度的内存,它打印一连串的内存数据。这个命令是留给外部的调试器使用的,一般很少使用。    

  mds:以符号的方式显示内存的内容    

  格式:mds [vaddr [line-count [output-radix]]]     

  从指定地址vaddr开始显示内存的内容,与md的区别是每行仅显示一个字,并且它试图将该地址与符号表进行匹配,如果找到,那么它将显示相应的符号名以及偏移值。如果不带参数,它将从上次mds的末尾开始显示。

    

  mm:修改内存内容    

  格式1:mm <vaddr> <new content>     

  将指定地址vaddr开始的数据修改为新的数据。修改的数据的长度为一个机器字。    

  格式2:mmW <vaddr> <new content>     

  意义同上,区别在于它改变W字节的内容。

      

  堆栈跟踪类       

  该类指令实现对堆栈的跟踪,包括bt、btp和bta三条命令。

    

  bt:显示调用堆栈    

  格式:bt [<stack-frame addr>]     

  如果不指定参数,它根据当前寄存器的内容显示堆栈,提供当前活动线程的完整的堆栈跟踪。如果指定stack-frame addr参数,它将从该地址开始跟踪。

    

  btp:显示进程的堆栈    

  格式:btp <pid>     

  显示由pid指定的进程的堆栈。

    

  bta:显示所有进程的堆栈    

  格式:bta   

    

  寄存器类       

  寄存器类命令包括对寄存器内容进行显示和修改的rd和rm指令,以及异常帧显示指令ef。

    

  rd:显示寄存器内容    

  格式:rd [c|d|u]     

  如果不带任何参数,rd显示所有进入kdb调试器时该点所设置的所有通用寄存器的值。如果带c参数,它将显示控制寄存器cr0、cr1、cr2、cr4寄存器的内容。如果带d参数,它显示调试寄存器的内容。如果带u参数,它显示当进入kdb调试器时当前任务的所有寄存器。 

   

  rm:修改寄存器的内容    

  格式:rm <register-name> <register-content>     

  该命令修改register-name指定的寄存器的内容为register-content。其中register-name为%eax、%ebx、%ecx、%edx、%esi、%edi、%esp、%eip或%ebp。如果参数为%%,由rd u指定的寄存器将被修改。当前rm命令不允许修改控制寄存器,也不允许显示和修改Pentium和Pentium Pro系列的特定寄存器。

    

  ef:显示异常帧    

  格式: ef <vaddr>     

  显示vaddr地址处的异常帧。

      

  环境变量类       

  这类指令对kdb调试器环境变量进行显示和设置。包括set和env命令。

    

  set:设置环境变量    

  格式:set <env-var=value>     

  将环境变量env-var的值设置为value。最多有33个环境变量,每个环境变量最大512字节。kdb的主要环境变量有:    

  PROMPT:kdb调试器提示符,缺省为kdb>。    

  MOREPROMPT:在一屏显示不下的情况下,系统的提示符,缺省为more>。    

  RADIX:显示数据时所使用的数制,缺省为16进制。    

  LINES:kdb调试器显示行数。缺省为24行。    

  COLUMNS:kdb调试器显示的列数。缺省为80列。    

  MDCOUNT:执行md指令时显示的内存行数,缺省为8行。    

  BTARGS:执行bt跟踪时,指定任一函数在打印时所使用参数最大个数。    

  SSCOUNT:该环境变量规定在执行ssb命令时,如果显示超过此数,执行将停止。缺省为20。    

  IDMODE:反汇编时所使用的指令格式。缺省为x86。    

  BYTESPERWORD:指定字的长度,缺省为4个字节。    

  IDCOUNT:反汇编时,一次反汇编的指令长度,缺省为16条指令。

    

  env:显示环境变量    

  格式:env     

 显示所有环境变量的值。

   杂项     

  id:指令反汇编    

  格式:id <vaddr>     

  从vaddr开始的地址反汇编指令。    

  cpu:切换到另一个CPU     

  格式:cpu <cpunum>     

  这条命令仅仅在SMP结构下有用,它切换到由cpunum指定的CPU。    

  ps:显示所有活动的进程    

  格式:ps     

  显示当前的活动的进程。包括pid、父进程pid、CPU号、当前状态,以及对应的线程。    

  reboot:重新启动机器

  格式:reboot     

  在某些情况下,内核无法返回到正常工作状态,这时可以利用reboot重新启动机器。注意在重启机器前,它不进行任何状态保存的工作。    

  sections:列出内核中所有已知的段的信息    

  格式:sections     

  列出模块和内核的所有已知的段的信息。首先是模块信息,最后是内核信息。包括模块名和一个或者多个段的信息。段信息包括段名、段起始地址、段结束地址和段标识。本命令仅仅是为外部调试器而设立的。    

  sr:激活SysRq代码,也就是调用MAGIC_SYSRQ函数    

  格式:sr <sysrq key>     

  将sysrq key字符作为参数传递给SysRq函数进行处理,就像你已经键入了SysRq键和该字符一样。如果要使用这个命令,需要在配置内核时,选择Magic SysRq Key。然后在新内核启动后,使用如下命令激活SysRq功能。    

  #echo “1” > /proc/sys/kernel/sysrq     

  这是一个功能强大的命令,它使得在kdb中可以使用操作系统提供的SysRq处理函数。    

  lsmod:列出内核中加载的所有模块    

  格式:lsmod     

  显示所有模块的信息。包括模块名、模块大小、模块结构地址、引用计数,以及被哪个模块所引用。    

  rmmod:卸载一个模块    

  格式:rmmod <modname>     

  将由modname指定的模块从内核中卸载。    

  ll:对链表中的每个元素重复执行命令    

  格式:ll <addr> <link-offset> <cmd>     

  它对以地址addr开头的链表的头link-offset个元素,重复执行cmd命令。    

  help和?:显示帮助信息。    

  格式:help 或者?     

  显示kdb的命令以及简单的用法。

猜你喜欢

转载自blog.csdn.net/jintianyishiyeai/article/details/2594774