linux内核调试

此文章属于在前辈们的基础上加入了些自己的理解,非原创

http://blog.csdn.net/hens007/article/details/7563341

驱动程序开发的一个重大难点就是不易调试。本文目的就是介绍驱动开发中常用的几种直接和间接的调试手段,它们是:

  • 利用printk
  • 查看OOP消息
  • 利用strace
  • 利用内核内置的hacking选项
  • 利用ioctl方法
  • 利用/proc 文件系统
  • 使用kgdb

一、利用printk

这是驱动开发中最朴实无华,同时也是最常用和有效的手段。scull驱动的main.c第338行如下,就是使用printk进行调试的例子,这样的例子相信大家在阅读驱动源码时随处可见。

338 //              printk(KERN_ALERT "wakeup by signal in process %d\n", current->pid);

printk的功能与我们经常在应用程序中使用的printf是一样的,不同之处在于printk可以在打印字符串前面加上内核定义的宏,例如上面例子中的KERN_ALERT(注意:宏与字符串之间没有逗号)。

  • #define KERN_EMERG "<0>"
  • #define KERN_ALERT "<1>"
  • #define KERN_CRIT "<2>"
  • #define KERN_ERR "<3>"
  • #define KERN_WARNING "<4>"
  • #define KERN_NOTICE "<5>"
  • #define KERN_INFO "<6>"
  • #define KERN_DEBUG "<7>"
  • #define DEFAULT_CONSOLE_LOGLEVEL 7

这个宏是用来定义需要打印的字符串的级别。值越小,级别越高。内核中有个参数用来控制是否将printk打印的字符串输出到控制台(屏幕或者/sys/log/syslog日志文件)

# cat /proc/sys/kernel/printk
6       4       1       7

第一个6表示级别高于(小于)6的消息才会被输出到控制台,第二个4表示如果调用printk时没有指定消息级别(宏)则消息的级别为4,第三个1表示接受的最高(最小)级别是1,第四个7表示系统启动时第一个6原来的初值是7。

因此,如果你发现在控制台上看不到你程序中某些printk的输出,请使用echo 8 > /proc/sys/kernel/printk来解决。


二:重点看使用kgdb,http://www.cnblogs.com/fbwang2011/p/3944018.html


VMware+Fedora+KGDB环境搭建及调试

一、准备环境

1. 下载Fedora,版本Fedora-Live-Desktop-x86_64-20-1.iso

2. 安装VMware10.0.0 build-1295980

3. VMware 虚拟两台Fedora, 第一台名为Fedora-Client-host:

step 1:

 

step2:

 

step3:

step4:

step5:

step6:

step7:

step 8:

4. Fedora安装之后,要接着安装必要的软件:

      [root@localhost xxxxx]# yum  install gcc  

      [root@localhost xxxxx]# yum  install perl

      [root@localhost xxxxx]# yum  install ncurses-devel

      5. 现在的内核是没有开启KGDB调试的,所以需要重新编译内核。Fedora-Live-Desktop-x86_64-20-1kernel版本为3.11.10-301.fc20.x86_64,从网站 https://www.kernel.org/ 下载源代码linux-3.12.1版本,解压缩并将解压后的内容拷贝到/usr/work/目录下, 注意用Root账号,因为内核编译是要Root的权限。

      6. 进入源码目录linux-3.12.1,执行make menuconfig进行KGDB的内核选项配置(这些选项default就有,不需要修改)。 

         (1)CONFIG_DEBUG_INFO = y 该选项可以使得编译的内核包含一些调试信息,使得调试更容易。 

     位置:---->Kernel hacking
        ---->Compile-time checks and compiler options
        ---->Compile the kernel with debug info
     (2)CONFIG_FRAME_POINTER = y 该选项将使得内核使用帧指针寄存器来维护堆栈,从而就可以正确地执行堆栈回溯,即函数调用栈信息。 
     位置:---->Kernel hacking
        ---->Compile-time checks and compiler options
        ---->Compile the kernel with frame pointers
     (3)CONFIG_MAGIC_SYSRQ = y (如果你选择了KGDB_SERIAL_CONSOLE,这个选项将自动被选上) 激活"魔术 SysRq"键. 该选项对kgdboc调试非常有用,kgdb向其注册了‘g’魔术键来激活kgdb 。   
   位置:---->Kernel hacking
        ---->Magic SysRq key
         //关闭的选项

     (4)CONFIG_DEBUG_RODATA = n 该选项是将内核的一些内存区域空间设置为只读,这样可能导致kgdb的设置软断点功能失效。所以推荐将该选项关闭。   
     位置:---->Kernel hacking
        ---->Write protect kernel read-only data structures
     (5)CONFIG_DEBUG_SET_MODULE_RONX =n,该选项会将内核模块空间设置为只读,这样会导致调试内核模块时设置断点功能失效,设置断点时出现以下错误,(调试内核模块时吃过亏又重新编译的内核)所以将其关闭。
Cannot insert breakpoint 1.
Error accessing memory address 0xf90f6000: Unknown error -1.
      位置:---->Kernel hacking
         ---->Set loadable kernel module data as NX and test as RO.
         //必须使能的选项
     (5)CONFIG_KGDB = y   
     位置:---->Kernel hacking     
            ---->KGDB: kernel debugger
     (6)CONFIG_KGDB_SERIAL_CONSOLE = y 使KGDB使用串口进行通信。
     位置:---->Kernel hacking      
        ---->KGDB: kernel debugger               
          ---->KGDB: use kgdb over the serial console
     (7)其余:
     在Kernel hacking-->KGDB: kernel debugger目录下,除了KGDB:internal test suite外的选项全部使能y。

      7.编译内核 执行make, 完成之后关机。

      8.创建第二台虚拟机,名为Fedora-Server-target, 从前面一台克隆如下所示

   

      9. 克隆完成之后,打开“编辑虚拟机设置”,更改串口配置如下所示:

  

     10. 测试串口通信,现在VMware 有两台虚拟机了,Fedora-Server-target和Fedora-Client-host,现在将这两台虚拟机启动起来。

     在Fedora-Server-target端 执行 cat /dev/ttyS1 【之所以是ttyS1是因为上图的管道为com_1】

     在Fedora-Client-host端执行      echo qwerrttt > /dev/ttyS1 【之所以是ttyS1是因为上图的管道为com_1】

     切换到Fedora-Server-target端, 看是否能看到 qwerrttt ,看到了就说明串口连接没有问题。

     11. 在Fedora-Server-target安装内核模块,执行make modules_install 模块被安装到 /lib/modules/3.12.1 目录下。

     12. 在Fedora-Server-target安装内核二进制映象文件,执行make install:

  (1)在此过程中,会在/boot目录下生成config-3.12.1 文件,自动生成根文件系统initrd.img-3.12.1 文件,内核映像文件vmlinuz-3.12.1 文件,以及符号表System.map-3.12.1 文件。

  (2)并且会自动修改grub.cfg文件,在该文件中自动添加3.12.1 内核的启动选项。

  (3)注意原来旧内核boot目录下的文件不要删掉,以防新内核启动不了。

  13. 重启Fedora-Server-target,用uname -a命令查看一下内核版本。并再次测试一下串口通信,测试通过后修改/boot/grub2/grub.cfg 在新内核先项里添加kgdbwait kgdboc=1【串口号或ttyS1】,115200【波特率】并把quiet 去掉【显示一些信息,例如波特率】波特率设置命令:stty ispeed 115200 ospeed 115200 -F /dev/ttyS1

  (1)kgdboc=0,9600 :kgdb over console,这里将kgdb连接的console设置为ttyS0,波特率为9600,如果不在内核启动项中配置该参数  

  (2)kgdbwait:使 kernel 在启动过程中等待 gdb 的连接。

  (3)然后重启target,系统就会暂停在kgdbwait处,等待host端的gdb连接。

  14.Fedora-Client-host端

    (1) 进入/usr/work/linux-3.12.1目录下

    (2)gdb vmlinux  //装入未压缩的内核映像

    (3)set remotebaud 9600   //设置串口速率

    (4)target remote /dev/ttyS1 //连接串口1 

     【注意】要确定两台机器在所设置的波特率下要能通信 

 







这部分从原文http://www.zeuux.com/blog/content/4210/转载

使用KGDB调试内核和模块 

内核空间的代码(包括内核和模块)无法像应用程序那样使用gdb进行简单直观的调试.

代码调试一般有两种方法:

一种是通过调试器辅助调试, 如gdb, Visual C++的集成调试环境, JTAG仿真器的集成调试环境; 这种调试方法可以对代码进行单步跟踪, 所以一次运行基本上就可以排查一条运行路径; 也可以在程序出错时检查调用栈信息, 从而迅速定位问题所在; 所以这种调试方法尤其适合于初期排查代码中的初级/低级错误, 可以大量的节省调试时间, 尤其是对那些一次编译和部署需要耗费大量时间的代码工程, 节省的时间更是可观.

另一种是打印/记录程序运行状态来间接观察程序的运行逻辑是否正确. 这种方法需要仔细分析程序的状态信息, 而且对于一个bug, 往往需要多次增加打印/记录代码才能彻底定位问题真正所在,  所以调试效率非常低; 而且一次编译部署运行一般只能定位一个错误, 因为一旦程序运行出错, 后续的状态信息基本上也就没有分析价值了. 所以这种调试方法一般不用于初期排查代码中的初级/低级错误(一般只在没有合适的辅助调试器的情况下选用). 但这种方法广泛用于后期程序的性能瓶颈问题/时序相关问题的调试. 因为调试器辅助调试往往会影响程序的执行性能和代码的执行时序, 从而无法暴露性能问题/时序问题的真正所在, 所以不适合用于调试此类要求严格贴近程序真实运行环境才能复现的问题.

 

所以KGDB对于内核空间代码早期错误排查具有显著意义, 能够显著加快早期的调试进度.

 

以下是使用KGDB进行内核和模块代码调试的简略过程:

1.准备开发机和调试机. 调试机运行的内核和模块必须是开发机上编译出来的. 开发机和调试机之间通过串口连接; 如果内核和模块调试不影响网络, 也可以通过网口连接进行调试; 一般都通过串口连接进行调试.

2.参照<Linux内核编译简略步骤>在开发机上编译内核和模块, 并在调试机上安装编译好的内核和模块.

3.修改调试机上新安装的内核的启动参数, 增加" kgdboc=ttyS0,115200 kgdbwait"参数(参考<修改Linux内核启动参数>一文).  重新引导系统并选择刚才修改的内核启动项.
4.在开发机内核编译目录下运行:

    gdb vmlinux

    进入gdb界面后运行下面两条命令连接调试机:

        set remotebaud 115200

        target remote /dev/ttyS0

    连接上调试机后其内核会被断在"wmb()"函数处, 键入"c"命令继续引导调试机内核

5.在调试机内:

    1) "sudo chmod 222 /proc/sysrq_trigger" 使得任意用户可以写访问该文件(否则只有root用户可以写访问该文件, sudo都不行)

    2) 任意时刻运行 "echo g > /proc/sysrq-trigger" 可使调试机进入kgdb中断状态; 开发机在kgdb中断状态下可对调试机上代码设置断点, 加载模块的符号文件等; 

6.模块调试:

在开发机内:

        "scp test.ko user@host:/directory/": 将开发机内编译好的模块拷贝到调试机内

在调试机内:

        "insmod test.ko": 加载模块

        "cat /sys/module/test/sections/.text": 获得模块在内核中的加载地址, 如"0xc0ae7000"

        "echo g > /proc/sysrq-trigger": 使调试机进入中断状态

在开发机内:

        gdb进入中断状态

        "add-symbol-file /test/test.ko 0xc0ae7000": 加载模块符号文件; "0xc0ae7000"为调试机中模块在内核中的加载地址.

        现在就可以像gdb调试应用程序一样调试内核模块了, 诸如设置断点; 模块进入断点后单步跟踪, 查看变量等等.

 

7.也可以在两台虚拟机上使用KGDB进行内核和模块的调试.

     使用虚拟机进行调试部署更方便, 对硬件环境要求更低(一台主机即可, 但配置要高一些, 要能跑得动两台虚拟机).

    先在开发机上将要调试的内核和模块编译好, 然后将开发机克隆一份为调试机, 这样可以简化开发机和调试机的部署过程;

    参照<Windows下VMWare虚拟机串口设置>一文设置好两台虚拟机之间的串口设置.

   后续的步骤就和上面的描述一样了.

8.内核自2.6.22版本开始内嵌KGDB支持; 2.6.16之前版本需要在网上找kgdb的补丁; 2.6.16和2.6.22之间的版本在网上找不到合用的补丁; 对于低于2.6.22版本的内核空间代码, 在实际调试时, 如果不是特别复杂, 建议花点时间先移植到2.6.22以上的版本上并调试通过, 然后再回到实际要求内核版本上进行调试

Windows下VMWare虚拟机串口设置

VMWare可以使用命名管道在两个虚拟机之间模拟串口, 这在同一主机上的两台虚拟机之间需要通过串口进行通信时非常方便, 使用kgdb调试linux内核就是其典型的应用.

具体操作: 

--> "Edit virtual machine settings"

--> "Add..."硬件设备--> 选择"Serial Port"

--> 点击 "Next" 并选择 "Output to named pipe"

--> 点击"Next"会看到4个属性:

    第一个是编辑框, 输入"Named Pipe"的名称, 保持默认输入即可, 如果要修改注意保留 "\\.\pipe\" 前缀. 相互通信的两台虚拟机"Named Pipe"的名称应相同.

    第二个是下拉框, 有两个选项, 相互通信的两台虚拟机一台选择"This end is the client", 另一台则选择"This end is the server.". 注意要让经常需要重启的虚拟机选择client, 不经常重启的虚拟机选择server.

    第三个是下拉框, 有两个选项, 保持默认输入即可, 即都选择"The other end is a virtual machine."

    第四个是勾选框, 勾上即可

--> 点击"Finish"结束.

此时在两台虚拟机上已经能够通过上述串口设备正常通信. 

在linux系统上测试方法:

--> 将两台虚拟机的串口波特率都设置为115200:

      stty ispeed 115200 ospeed 115200 -F /dev/ttyS0

--> 在一台虚拟机上执行:

      cat /dev/ttyS0

--> 在另一台虚拟机上执行:

      echo "test ok" > /dev/ttyS0; 

      此时上台虚拟机上的cat命令打印出"test ok"则表示串口工作正常. 

以上操作在两台Ubuntu10.04上测试通过.

在看另一文:http://www.eetop.cn/blog/html/21/766721-25573.html

KGDB 是个一特殊的内核辅助工具,除了在内核代码中加入了一些调试代码外也提供一个 gdbstub 用于和远程 gdb 调试程序联机用。以前,这样一个使用远程 gdb 调试内核的开发需要在一般linux内核上打 KGDB 补丁(patches)同时编译时使用特殊编译设置来完成。 可喜的是,至 linux-2.6.xx(xx多少记不清了)后的版本内核已经正式将 kgdb 加入为主流核心发布的一部份。换句话说,内核开发者几乎不需花任何额外的功夫就可使用 kgdb。 此外,kgdb 成为主流内核发行一部份也代表他的稳定性及实用性受到社群的肯定。
由于kgdb的方便易用,大大提高了linux平台下驱动开发者的效率。而使得他们不必像windows下的开发者一样使用痛苦的反汇编调试。
本文从最基本的开始详细描述了如何搭建一个linux驱动调试环境。如何加载模块开始调试内核模块,如何调试模块的初始化函数。文中除了一小部分自创外大部分用到的办法都是从网上搜索拼凑的。在不同的平台下对应的办法会有些不同,读者可以根据自身的情况修改。
软硬件环境 :
PC机CPU   AMD  5000+ :
主机linux版本 :Ubuntu10.04
主机linux内核版本:2.6.32-37-generic
虚拟机版本:VMware-Workstation-Full-7.1.4-385536.i386.bundle
虚拟机linux版本跟内核版本与主机一致。
第一步在主机上安装Ubuntu10.04。
第二步在Ubuntu主机上安装虚拟机VMWare软件这一步没什么把软件修改为可执行就行了,另外注意软件版本要对,我刚开始用的7.1.3版本的运行老是提示错误弄了很久都没解决最后还是换新版本VM软件才行。
第 三步创建一个虚拟机,在虚拟机上安装Ubuntu系统,并在安装好的系统中安装VMTools工具。安装步骤仿似在PC下安装。如果遇到什么问题可以到虚 拟机之家去找解决方法。(这里注意几点:process选项最好选择1,单核cpu调试可以降低以后内核开发的复杂度。虽然慢一点但作为被调试端效率不占 主要因素。HardDisk尽量选大一点,你选多大硬盘空间并不立即占用你真实的硬盘空间。另外再提供一个序列号VV3M0-42Z4M-M80XY- T5PNT-MAUZF)  做完这几步之后应当创建一个快照,以防后面除了差错。
一般搭建一个 kgdb 的调试环境包含以下3步骤:
步骤一, 编译核心(linux-2.6.xx以后内建 kgdb 功能的内核版本)以取得未经压缩的 vmlinux 内核可执行文件。 在调试端运行的 gdb 需要这个内核可执行文件以对目标端执行的内核作符号-地址比对以进行源码级(source-level debugging)除错。一般 linux 发行仅提供压缩之后的 vmlinuz内核文件,因此重新编译内核以取得 vmlinux 是必要的。当然存在其它间接取得 vmlinux的方法,但因为这些方法过于琐碎且非主流作法,因此此处不予讨论。
步骤二, 修改grub参数让其识别新编译的 KGDB 内核,以及远程调试所需的内核启动参数。当你编译完带有KGDB的内核后,一般让grub识别新内核的工作属内核编译安装标准程序的一部分而用户并不需要 作任何处理。 但由于 KGDB 调试需要一些特殊的参数,而这些参数的设定需要用户透过手动修改grub参数来达成。本文针对GRUB 2操作作说明。读者如使用其它版本Grub,可参考相关的使用手册作修改。
步骤三, 在调试端启动 gdb 连接目的端(被调试端)内核进行除错。这部份操作涉及调试端(主机)及被调试端(虚拟机)两台计算机,因此过程比较繁复。 同时又因应不同传输接口(以太网络,RS232,USB-RS232,USB)对应的操作又有差异。 为简化讨论,本文以串口调试为主,其它的调试接口方式不做说明。以下将依上述三步骤为骨干,详细解释上面所说的每一步。需说明的是本文仅仅对应一种硬件平 台和操作系统有用。并不一定适用于所有操作系统版本及硬件架构。如果读者使用不同平台进行 kgdb 搭建时,需注意平台的差异性。

详细步骤及说明
#安装编译必要工具套件(如果安装不成功就更换源,一般163源都是可以的)
$sudo apt-get install fakeroot kernel-wedge build-essential makedumpfile kernel-package 
$sudo apt-get install  libncurses5-dev

#安装内核程序编译依赖程序
$sudo apt-get build-dep --no-install-recommends linux-image-$(uname -r)
#在home目录里建立 src 子目录,并下载与目前系统相同版本的内核源码,然后切换工作目录到 linux-2.6.35
$mkdir ~/src
$cd ~/src
$apt-get source linux-image-$(uname -r)
$cd linux-2.6.32

#使用原始的内核设定参数
$cp   -vi    /boot/config-$(uname -r)      .config

#根据需要修改内核设置选项
$make menuconfig
在 这里建议关闭一个选项: DEBUG_RODATA   CONFIG_DEBUG_RODATA = n 该选项是将内核的一些内存区域空间设置为只读,这样可能导致kgdb的设置软断点功能失效。所以推荐将该选项关闭。(这个 这个选项需要修改 !其他的选项都已经是默认设置了无需修改,关于其它的内核选项意义可见本文末附录,如果是其它版本的linux就要注意这里提到的其它选项了)。
注:这里可不做任何修改!!

Location:  
     -> Kernel hacking      ->Write protect kernel read-only data structures
#修改内核Makefile优化选项将KBUILD_CFLAGS += -O2,修改为: KBUILD_CFLAGS += -O。这里仅仅能修改为-O了,以前曾经尝试修改为-O0老是错误弄了很久也没成功。主要是在编译期间内联函数出错,据说某些大牛在研究者个问题。小弟一人之力无法解决了,但是以后可以把单独的 一个驱动模块用-O0编译,这是后话)
$gedit Makefile

#使用多核心(如果你的编译平台有多核心处理器)加速编译过程,n = 1 +number_of_core.
$export CONCURRENCY_LEVEL=n

#使用 Debian 开发的 make-kpkg 套件工具编译内核,clean 目标在清除核心原始码中可能的非必要残余程序。详细说明可参考 make-kpkg 在线使用者手册。
$make-kpkg clean

# 以虚拟系统管理员身份,使用 Debian 开发的 make-kpkg 套件工具编译内核。此处注意在核心版本后加注“-kgdb”名字以供辨认,读者可使用不同名称。指令执行后会在~/src 及~/src/linux-2.6.32目录中产生一些新文件。(注意这一步需要数个小时,因此先检查一下前面是否已经没有错误,然后就去一边等待几个小 时后再回来吧。。。)
$fakeroot make-kpkg --initrd --append-to-version=- kgdb kernel-image kernel-headers

#回到~/src,准备安装核心.deb 套件。
$cd ~/src

#最后产生内核加载所需的RAMDISK 系统。注意,下面指令中蓝色字体在读者计算机中可能有不同内容。以下操作会在计算机/boot 种产生一些内核运行必要文件。
$sudo dpkg -i linux-headers-2.6.32.49+drm33.21-kgdb_2.6.32.49+drm33.21-kgdb-10.00.Custom_i386.deb
$sudo dpkg -i linux-image-2.6.32.49+drm33.21-kgdb_2.6.32.49+drm33.21-kgdb-10.00.Custom_i386.deb
$sudo update-initramfs -c -k  2.6.32.49+drm33.21-kgdb

#备份grub文件,非必要动作,但若是新建内核崩溃时,可还原原始 grub.cfg。
$sudo cp /boot/grub/grub.cfg /boot/grub/grub.cfg.bak

#加新建内核到 GRUB 开机选单中。
$sudo update-grub
一般内核编译安装到此一阶段即算完成,但由于 kgdb 操作的特殊性,我们需要手动透过 grub 传递一些核心参数给核心,所以有以下步骤二的操作。

步 骤二,设置Grub以加入新编译的 KGDB 内核,及远程调试所需的内核设置参数。GRUB 2 (Ubuntu 10.10所使用的 grub)的设置文件是 /boot/grub/grub.cfg 。注意不能直接修改 grub.cfg,正确的操作方法是修改 /etc/grub.d/40_custom 然后执行update-grub。此时 update-grub 将使用40_custom 内容更新 grub.cfg。当再次开机时,按住 SHIFT 键,一会儿开机内核选项会出现,此时你可以使用上下键选取新的内核开机。
如何产生/etc /grub.d/40_custom 呢?标准作法是复制/boot/grub/grub.cfg 中的一节(/boot/grub/grub.cfg 中的一节指的 是由 menuentry 开始到”}”结束的一段文字)。如下红色字体部分所示,将这节文字贴到/etc/grub.d/40_custom 中,再做必要修改:
### BEGIN /etc/grub.d/10_linux ###
menuentry 'Ubuntu, with Linux 2.6.32.49+drm33.21-kgdb' --class ubuntu --class gnu-linux --class gnu --class os {
    recordfail
    insmod ext2
    set root='(hd0,1)'
    search --no-floppy --fs-uuid --set 05c81d86-d0e4-450f-ae07-2062deae8ddf
    linux    /boot/vmlinuz-2.6.32.49+drm33.21-kgdb root=UUID=05c81d86-d0e4-450f-ae07-2062deae8ddf ro   crashkernel=384M-2G:64M,2G-:128M quiet splash
    initrd    /boot/initrd.img-2.6.32.49+drm33.21-kgdb
}
如下为拷贝后/etc/grub.d/40_custom文件的内容。
#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries. Simply type the
# menu entries you want to add after this comment. Be careful not to change
# the 'exec tail' line above.
menuentry ' Ubuntu, with Linux 2.6.32.49+drm33.21-kgdb-debug' --class ubuntu --class gnu-linux --class gnu --class os {
    recordfail
    insmod ext2
    set root='(hd0,1)'
    search --no-floppy --fs-uuid --set 05c81d86-d0e4-450f-ae07-2062deae8ddf
    linux    /boot/vmlinuz-2.6.32.49+drm33.21-kgdb root=UUID=05c81d86-d0e4-450f-ae07-2062deae8ddf ro  kgdboc=ttyS1,115200 kgdbwait
    initrd    /boot/initrd.img-2.6.32.49+drm33.21-kgdb
}
修改后的/etc/grub.d/40_custom 内容。注意,图中蓝色字体部分是你需要手动修改的地方。

上 面单引号引用的  Ubuntu, with Linux 2.6.32.49+drm33.21-kgdb-debug 字符串是将显示在开机选项上的内核辨任文字,你可以依喜好自行更改。而 kgdboc=ttyS1,115200 kgdbwait字符串是针对使用串口进行远程调试所需设置的内核参数,(注意上面的kgdboc=后面应当是ttyS1,而不是 ttyS0否则会导致串口无法使用,具体原因可以看我的另一篇文章  http://www.arm9home.net/read.php?tid-11519.html
)  相关各参数的意义可参考
[src/Documents/Kernel_parameter]。
待/etc/grub.d/40_custom 修改后,请执行
$sudo update-grub
随后/etc/grub.d/40_custom 的内容会新增到/boot/grub/grub.cfg 中,而开机时选项中亦会出现这个使用串行进行远程调试的内核选项。

步骤三,测试调试端与被调试端串口通信的情况是否通畅:
为被调试端(虚拟机新加虚拟串口) ,首先关闭虚拟机。
VM->Setting->HardWare->Add->Outputtosocket->next->socket:/tmp/iosocket->Form.:ServerToApplication->Connect At PowerOn->Finish
再次打开虚拟机:
在主机中执行命令ls /tmp看看iosocket有没有被创建:



再在主机执行命令将iosocket模拟为串口:
gudujian@gudujian-System-Product-Name:~$ socat -d -d /tmp/iosocket  PTY: 
2011/11/21 08:20:21 socat[11401] N opening connection to AF=1 "/tmp/iosocket" 
2011/11/21 08:20:21 socat[11401] E connect(3, AF=1 "/tmp/iosocket", 15): Permission denied 
2011/11/21 08:20:21 socat[11401] N exit(1) 

gudujian@gudujian-System-Product-Name:~$ sudo socat -d -d /tmp/iosocket  PTY: 
2011/11/21 08:21:06 socat[11419] N opening connection to AF=1 "/tmp/iosocket" 
2011/11/21 08:21:06 socat[11419] N successfully connected from local address AF=1 "\xCE" 2011/11/21 08:21:06 socat[11419] N successfully connected via  2011/11/21 08:21:06 socat[11419] N PTY is  /dev/pts/52011/11/21 08:21:06 socat[11419] N starting data transfer loop with FDs [3,3] and [4,4] 
注意到上面命令输出的那个/dev/pts/5,这就是我们要用到的串口设备文件,设置串口波特率:
$sudo stty ispeed 115200 ospeed 115200 -F /dev/pts/5
在主机端执行:sudo cat /dev/pts/5
在虚拟机串口中执行:echo gudujian > /dev/ttyS1
可见主机端有正确的输出。

步骤三,在调试端启动 gdb 连接被调试端内核进行除错。
首 先要做的是拷贝被调试端的源码目录及 vmlinux 到调试端。vmlinux 文件应该出现在被调试端的目录~/src/linux-2.6.35中(记得我们是在被调试端进行 KGDB 内核编译)。在这里可以使用虚拟机的文件共享功能将被调试端的~/src/linux-xxx拷贝到主机端的~/src/linux-xx:
gudujian@gudujian-virtual-machine:~/src$ cp -r linux-2.6.35/ /mnt/hgfs/src/
拷贝完之后关闭虚拟机重新打开时按住shift选择调试内核选项:

启动被调试内核。
被调试内核启动后中断下来等待调试端链接:

在调试端执行命令:
sudo socat -d -d /tmp/iosocket  PTY:
然后仿照上面串口通信的方式设置波特率:
sudo stty ispeed 115200 ospeed 115200 -F /dev/pts/5
在主机端vmlinux目录下用gdb链接被调试端:
sudo gdb ./vmlinux
taget remote /dev/pts/2
此时可以看到被调试端被链接上后的状态:


此时就可以设置其它断点(breakpoint),这样的话每次设定断点的函式被呼叫时,目的端的内核会停下来等你在开发端输入新指令作调试动作。如下所示,我们连续设定三个断点,分别是
#创建批量urb并发送到特定的设备。
(gdb)b usb_bulk_msg

#创建控制urb并发送到特定的设备。
(gdb)b  usb_control_msg

创建中断urb并发送到特定的设备。
(gdb)b  usb_interrupt_msg



每当上述函数被调用时,目的端内核执行会中断,然后你可透过其它指令操作查看内核执行的状态。接着执行
(gdb)c
此时,目的端内核执行会在第一次调用 usb_control_msg 时中断,开发端 gdb 显示如图。


对应的被调试端显示如。


此时你可在调试端下达 gdb 指令如 next 以显示对应源码并跟踪程序执行,如下所示。
(gdb) n



当目标机的内核已经break在 wmb()上时,如果你要目标机继续工作,可以c 一下,做一些事情,然后在目标机上再次敲 echo g > /proc/sysrq-trigger,又会回来。(敲此命令前,先在目标机上运行sudo su)


后面关于gdb的使用基本上与普通方式使用gdb是一样的。不熟悉的可以参考网上的资料这里略去。

附录:关于内核配置的一些选项。
KGDB_SERIAL_CONSOLE  CONFIG_KGDB_SERIAL_CONSOLE = y (使用串口进行通信) 
Location:     
    -> Kernel hacking    
       -> KGDB: kernel debugger 
              -> KGDB: use kgdb over the serial console

KGDB_LOW_LEVEL_TRAPCONFIG_KGDB_LOW_LEVEL_TRAP=y                                      使 能该选项可以kgdb不依赖notifier_call_chain()机制来获取断点异常, 
这样就可以对notifier_call_chain()机制实现相关的函数进行单步调试。 
Depends on: KGDB [=y] && (X86 [=y] || MIPS [=MIPS]) 
Location:  
     -> Kernel hacking   
       -> KGDB: kernel debugger (KGDB [=y]) 
      ->KGDB: Allow debugging with traps in notifiers

DEBUG_INFO      CONFIG_DEBUG_INFO = y 
该选项可以使得编译的内核包含一些调试信息,使得调试更容易。 
Location:  
     -> Kernel hacking 
      ->compile the kernel with debuginfo



FRAME_POINTER     CONFIG_FRAME_POINTER = y 
该选项将使得内核使用帧指针寄存器来维护堆栈,从而就可以正确地执行堆栈回溯,即函数调用栈信息。 (bt  where会用到这些??)
Location:  
     -> Kernel hacking 
        ->Compile the kernel with frame. pointers

MAGIC_SYSRQ  CONFIG_MAGIC_SYSRQ = y
(如果你选择了KGDB_SERIAL_CONSOLE,这个选项将自动被选上) 
激活"魔术 SysRq"键. 该选项对kgdboc调试非常有用,kgdb向其注册了‘g’魔术键来激活kgdb 。 
  Location:  
     -> Kernel hacking 
       ->magic SysRq key 
当你想手动激活kgdb时,你可以触发SysRq的g键, 如: 
$ echo "g" > /proc/sysrq-trigger
还有一篇比较好的文章: http://www.xfocus.net/articles/200509/820.html

1. 几种内核调试工具比较

kdb:只能在汇编代码级进行调试;
     优点是不需要两台机器进行调试。

gdb:在调试模块时缺少一些至关重要的功能,它可用来查看内核的运行情况,包括反汇编内核函数。

kgdb:能很方便的在源码级对内核进行调试,缺点是kgdb只能进行远程调试,它需要一根串口线及两台机器来调试内核(也可以是在同一台主机上用vmware软件运行两个操作系统来调试)

使用kdb和gdb调试内核的方法相对比较简单,这里只描述如何使用kgdb来调试内核。

2.软硬件准备

环境:
一台开发机developer(192.168.16.5 com1),一台测试机target(192.168.16.30 com2),都预装redhat 9;一根串口线

下载以下软件包:
linux内核2.4.23         linux-2.4.23.tar.bz2
kgdb内核补丁1.9版       linux-2.4.23-kgdb-1.9.patch
可调试内核模块的gdb     gdbmod-1.9.bz2

3.ok,开始

3.1 测试串口线
物理连接好串口线后,使用一下命令进行测试,stty可以对串口参数进行设置

在developer上执行:
stty ispeed 115200 ospeed 115200 -F /dev/ttyS0
echo hello > /dev/ttyS0
在target上执行:
stty ispeed 115200 ospeed 115200 -F /dev/ttyS1
cat /dev/ttyS1

串口线没问题的话在target的屏幕上显示hello

3.2 安装与配置

3.2.1 安装

下载linux-2.4.23.tar.bz2,linux-2.4.23-kgdb-1.9.patch,gdbmod-1.9.bz2到developer的/home/liangjian目录

*在developer机器上

#cd /home/liangjian
#bunzip2 linux-2.4.23.tar.bz2
#tar -xvf linux-2.4.23.tar
#bunzip2 gdbmod-1.9.bz2
#cp gdbmod-1.9 /usr/local/bin
#cd linux-2.4.23
#patch -p1 < ../linux-2.4.23-kgdb-1.9.patch
#make menuconfig

在Kernel hacking配置项中将以下三项编译进内核
KGDB: Remote (serial) kernel debugging with gdb
KGDB: Thread analysis
KGDB: Console messages through gdb

注意在编译内核的时候需要加上-g选项
#make dep;make bzImage

使用scp进行将相关文件拷贝到target上(当然也可以使用其它的网络工具)
#scp arch/i386/boot/bzImage root@192.168.16.30:/boot/vmlinuz-2.4.23-kgdb
#scp System.map root@192.168.16.30:/boot/System.map-2.4.23-kgdb
#scp arch/i386/kernel/gdbstart  root@192.168.16.30:/sbin
gdbstart为kgdb提供的一个工具,用于激活内核钩子,使内核处于调试状态

3.2.2 配置

*在developer机器上

在内核源码目录下编辑一文件.gdbinit(该文件用以对gdb进行初始化),内容如下:
#vi .gdbinit
define rmt
set remotebaud 115200
target remote /dev/ttyS0
end
#
以上在.gdbinit中定义了一个宏rmt,该宏主要是设置使用的串口号和速率

*在target机器上

编辑/etc/grub.conf文件,加入以下行:
#vi /etc/grub.conf
title Red Hat Linux (2.4.23-kgdb)
    root (hd0,0)
    kernel /boot/vmlinuz-2.4.23-kgdb ro root=/dev/hda1
#

在root目录下建立一个脚本文件debugkernel,内容如下:
#vi debug
#!/bin/bash
gdbstart -s 115200 -t /dev/ttyS1 <<EOF

EOF
#chmod +x debugkernel
这个脚本主要是调用gdbstart程序设置target机上使用的串口及其速率,并使内核处于调试状态

3.3 开始调试

target上的内核或内核模块处于调试状态时,可以查看其变量、设置断点、查看堆栈等,并且是源码级的调试,和用gdb调试用户程序一样

3.3.1 内核启动后调试

*在target机器上

重启系统,选择以 2.4.23-kgdb内核启动,启动完成后运行debugkenel,
这时内核将停止运行,在控制台屏幕上显示信息,并等待来自developer的
串口连接

#./debug
About to activate GDB stub in the kernel on /dev/ttyS1
Waiting for connection from remote gdb...

*在developer机器上

#cd /home/liangjian/linux-2.4.23
# gdb vmlinux
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...

执行rmt宏
(gdb) rmt
breakpoint () at kgdbstub.c:1005
1005                    atomic_set(&kgdb_setting_breakpoint, 0);

这时target上的内核处于调试状态,可以查看其变量、设置断点、查看堆栈等,和用gdb调试用户程序一样

查看堆栈
(gdb) bt
#0  breakpoint () at kgdbstub.c:1005
#1  0xc0387f48 in init_task_union ()
#2  0xc01bc867 in gdb_interrupt (irq=3, dev_id=0x0, regs=0xc0387f98) at
gdbserial.c:158
#3  0xc010937b in handle_IRQ_event (irq=3, regs=0xc0387f98, action=0xce5a9860)
at irq.c:452
#4  0xc0109597 in do_IRQ (regs=
      {ebx = -1072671776, ecx = -1, edx = -1070047232, esi = -1070047232, edi
= -1070047232, ebp = -1070039092, eax = 0, xds
= -1070071784, xes = -1070071784, orig_eax = -253, eip = -1072671729, xcs =
16, eflags = 582, esp = -1070039072, xss = -1072671582}) at irq.c:639
#5  0xc010c0e8 in call_do_IRQ ()

查看jiffies变量的值
(gdb) p jiffies
$1 = 76153

如果想让target上的内核继续运行,执行continue命令
(gdb) continue
Continuing.

3.3.2 内核在引导时调试

kgdb可以在内核引导时就对其进行调试,但并不是所有引导过程都是可调试的,如在kgdb 1.9版中,它在init/main.c的start_kernel()函数中插入以下代码:
start_kernel()
{
    ......
        smp_init();
#ifdef CONFIG_KGDB
        if (gdb_enter) {
                gdb_hook();             /* right at boot time */
        }
#endif
    ......
}

所以在smp_init()之前的初始化引导过程是不能调试的。

另外要想让target的内核在引导时就处于调试状态,需要修改其/etc/grub.conf文件为如下形式:
title Red Hat Linux (2.4.23-kgdb)
    root (hd0,0)
    kernel /boot/vmlinuz-2.4.23-kgdb ro root=/dev/hda1 gdb gdbttyS=1 gdbbaud=115200

*在target机器上

引导2.4.23-kgdb内核,内核将在短暂的运行后暂停并进入调试状态,打印如下信息:
Waiting for connection from remote gdb...

*在developer机器上

#cd /home/liangjian/linux-2.4.23
# gdb vmlinux
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...

执行rmt宏
(gdb) rmt
breakpoint () at kgdbstub.c:1005
1005                    atomic_set(&kgdb_setting_breakpoint, 0);

查看当前堆栈
(gdb) bt
#0  breakpoint () at kgdbstub.c:1005
#1  0xc0387fe0 in init_task_union ()
#2  0xc01bc984 in gdb_hook () at gdbserial.c:250
#3  0xc0388898 in start_kernel () at init/main.c:443

在do_basic_setup函数处设置断点,并让内核恢复运行
(gdb) b do_basic_setup
Breakpoint 1 at 0xc0388913: file current.h, line 9.
(gdb) continue
Continuing.
[New Thread 1]
[Switching to Thread 1]

Breakpoint 1, do_basic_setup () at current.h:9
9               __asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL));

内核在do_basic_setup断点处停止运行后查看当前堆栈
(gdb) bt
#0  do_basic_setup () at current.h:9
(gdb)

3.3.3 内核模块调试调试

要想调试内核模块,需要相应的gdb支持,kgdb的主页上提供了一个工具gdbmod,它修正了gdb 6.0在解析模块地址时的错误,可以用来正确的调试内核模块

*在developer机器上

写了个测试用的内核模块orig,如下:
void xcspy_func()
{
    printk("<1>xcspy_func\n");
    printk("<1>aaaaaaaaaaa\n");
}

int xcspy_init()
{
    printk("<1>xcspy_init_module\n");
        
    return 0;
}

void xcspy_exit()
{
    printk("<1>xcspy_cleanup_module\n");
}

module_init(xcspy_init);
module_exit(xcspy_exit);

编译该模块:
#cd /home/liangjian/lkm
#gcc -D__KERNEL__ -DMODULE -I/home/liangjian/linux-2.4.23/include -O -Wall -g -c -o orig.o orig.c
#scp orig.o root@192.168.16.30:/root

开始调试:
# gdbmod vmlinux
GNU gdb 6.0
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...

设置符号文件的搜索路径
(gdb) set solib-search-path /home/liangjian/lkm

执行rmt宏
(gdb) rmt
breakpoint () at kgdbstub.c:1005
1005                    atomic_set(&kgdb_setting_breakpoint, 0);

设置断点使得可以调试内核模块的init函数,查内核源码可知,内核是通过module.c文件的第566行(sys_init_module函数中)mod->init来调用模块的init函数的
(gdb) b module.c:566
Breakpoint 1 at 0xc011cd83: file module.c, line 566.
(gdb) c
Continuing.
[New Thread 1352]
[Switching to Thread 1352]

这时在target机器上执行insmod orig.o,developer则相应的在断点处被暂停,如下
                                                                                                                          
Breakpoint 1, sys_init_module (name_user=0xc03401bc "\001",
mod_user=0x80904d8) at module.c:566
566             if (mod->init && (error = mod->init()) != 0) {

使用step命令进入模块的init函数
(gdb) step
xcspy_init () at orig.c:12
12              printk("<1>xcspy_init_module\n");
(gdb) n
15      }
(gdb)

说明:
调试内核模块的非init函数相对比较简单,只要先在target上执行insmod orig.o,这时由于模块的符号被加载,可以直接在developer的gdb中对想调试的模块函数设置断点,如bt xcspy_func,后面当xcspy_func被调用时就进入了调试状态。
如果想调试内核模块的init函数,由于在执行insmod之前模块的符号还没有被加载,不能直接对模块的init函数设置断点,所以相对来说要困难一些。可以采用两种变通的方法:1,采用上面介绍的在内核调用模块的init函数被调用之前的某处插入断点,如bt sys_init_module()或bt module.c:566;2,在developer上让内核处于运行状态,在target上先执行一遍insmod orig.o,这时orig.o的符号已经被加载到内存中,可以直接在developer的gdb中对模块的init函数设置断点,如bt xcspy_init,然后在target上rmmod orig.o,当下次在target上重新加载orig.o时就进入了调试状态,developer在xcspy_init处被暂停。

还一篇:http://blog.csdn.net/ruixj/article/details/5698263

使用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是一个不错的内核调试工具,使用它可以进行对内核的全面调试,甚至可以调试内核的中断处理程序。如果在一些图形化的开发工具的帮助下,对内核的调试将更方便。








  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值