linux内核调试

来源:互联网 发布:教务系统数据库 编辑:程序博客网 时间:2024/06/06 23:55

使用qemu进行内核源码级调试 (2011-08-23 22:58)
标签:  转载 

原文地址:使用qemu进行内核源码级调试 作者:chinahhucai


内核源码调试对于内核初学者而言是一件有一定难度的事.工欲善其事,必先利其器,要想成功地进行内核源码级的调试,首先,必须现找到一个合适的工具,下面,笔者就来介绍内核源码调试的一款工具QEMU.
QEMU是一个通用并开放源代码的模拟器,其功能相当的强大,例如:可以用QEMU来模拟一个完整的系统,同时,也可以用QEMU来实现系统源码级的调试.如果您想对QEMU仿真器有更加深入的了解,请参阅其官方网站:
http://www.qemu.org/ 

下面笔者从如何得到QEMU,以及如何在linux下安装QEMU并进行源码级的调试做一个详细的介绍.
(一) qemu的获得以及安装
得到qemu是相当方便的,到其官方网站
http://www.nongnu.org/qemu/download.html下载QEMU Linux
下载最新版本即可.

接下来是在linux下安装qemu的详细步骤:
第一步:
把下载的文件放到工作目录下,解压缩:
例如:
huanghucai@huanghucai-laptop:~/kernel_learning$ tar zxvf qemu-0.10.5.tar.gz
第二步:切换到qemu目录下
huanghucai@huanghucai-laptop:~/kernel_learning$ cd qemu-0.10.5
第三步:配置安装
huanghucai@huanghucai-laptop:~/kernel_learning/qemu-0.10.5$ ./configure
huanghucai@huanghucai-laptop:~/kernel_learning/qemu-0.10.5$ sudo make
huanghucai@huanghucai-laptop:~/kernel_learning/qemu-0.10.5$ sudo make install
这样,qemu就安装好了,安装好了之后,对于一个新的软件,我们需要查看其具体的使用方法,那么请查看它的man手册.或者是到其官方网站去了解其基本的应用.

(二) 使用QEMU进行内核源码级调试

接下来,就是利用QEMU进行内核源码调试.要想在QEMU下进行内核源码调试,和其他内核调试方式一样,首先是准备内核镜像.为了能够进行源码级的跟踪、调试,需要一个包含调试信息的内核镜像.

下面将会以linux-2.6.23.2内核版本为例来进行讲解.
首先是到linux内核官方网站www.kernel.org获得linux-2.6.23.2内核源码linux-2.6.23.2.tar.gz,下载后解压缩,将源代码放到工作目录下,
例如:放到目录
huanghucai@huanghucai-laptop:~/kernel_learning$
(这里说点题外话,这里笔者推荐一款下载工具axel,下载速度很快的,为了尽快的得到源代码,安装axel,复制链接,之后终端执行下面命令:
axel -n 50 -o . http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.23.2.tar.gz不过60秒,你就可以得到linux内核源码了,上面的.表示你把下载源代码放在了当前工作目录下,使用了50线程,要想对axel有深入的了解,你可以man一下就OK了)

下载内核源代码之后,解压缩,并进入内核源码目录:
huanghucai@huanghucai-laptop:~/kernel_learning$ tar zxvf linux-2.6.23.2 linux-2.6.23.2.tar.gz
huanghucai@huanghucai-laptop:~/kernel_learning$ cd linux-2.6.23.2

第一步、生成新的.config文件,其中的配置采用默认的选项就可以了 
huanghucai@huanghucai-laptop:~/kernel_learning/linux-2.6.23.2$ defconfig 

第二步、修改已有的.config文件,在这个步骤中,添加新的内核选项
huanghucai@huanghucai-laptop:~/kernel_learning/linux-2.6.23.2$ make memuconfig

进入顶级选项Kernel hacking选项,选中kernel debugging,之后再次选中Compile kernel with debug info选项,选择以后出来并保存就可以了;

第三步、创建大内核镜像bzImage
huanghucai@huanghucai-laptop:~/kernel_learning/linux-2.6.23.2$ make bzImage
第三步运行结束需要较长的时间,当第三步完成之后,如果没有出现什么问题,就可以直接略去下面的这些内容跳到第四步就OK了;如果你使用的是gcc 4.3,那么很有可能会出现创建失败的情况,错误提示如下所示:
    ......
    ......
LD      .tmp_vmlinux1
kernel/built-in.o: In function `getnstimeofday':
(.text+0x1b2b1): undefined reference to `__umoddi3'
kernel/built-in.o: In function `do_gettimeofday':
(.text+0x1b36c): undefined reference to `__udivdi3'
kernel/built-in.o: In function `do_gettimeofday':
(.text+0x1b38f): undefined reference to `__umoddi3'
kernel/built-in.o: In function `timekeeping_resume':
timekeeping.c:(.text+0x1b520): undefined reference to `__udivdi3'
timekeeping.c:(.text+0x1b543): undefined reference to `__umoddi3'
kernel/built-in.o: In function `update_wall_time':
(.text+0x1bb9d): undefined reference to `__udivdi3'
kernel/built-in.o: In function `update_wall_time':
(.text+0x1bbc0): undefined reference to `__umoddi3'
kernel/built-in.o: In function `update_wall_time':
(.text+0x1bc57): undefined reference to `__udivdi3'
kernel/built-in.o: In function `update_wall_time':
(.text+0x1bc81): undefined reference to `__umoddi3'
make: *** [.tmp_vmlinux1] Error 1

这个错误的原因是因为gcc 4.3在处理64位整数运算的时候出现的问题,解决的办法据我所知有两种,这里介绍其中一种.在linux2.6.23.2目录下的Makefile文件中给变量CFLAGS_KERNEL赋值为:-fno-tree-scev-cprop,
改变值之后重新编译一次就OK了.如果出现了undefined reference to `__stack_chk_fail',可以在文件Makefile中的CFLAGS设置值
-fno-stack-protector,即关闭掉栈的保护.
如果出现其他编译失败的情况,你可以借助于google找到相关的解决方案.

第四步就是准备根文件系统镜像,这里我从qemu的官方网站下载了linux-0.2.img.bz2,解压缩并将解压缩后的文件放到源代码目录下.

经过上面四个步骤的工作,现在你已经将内核源码级的调试环境搭建完了,下一步就来进入qemu下的内核源码级调试.

(三) QEMU下内核源码级调试
首先是启动qemu:
可以运行类似于下面命令来启动qemu:
qemu -S -kernel arch/i386/boot/bzImage -hda linux-0.2.img -append "root = dev/hda" -no-kqemu
截图如下所示:




运行了此命令后就可以进行源码级的调试了.
当执行了上面的启动命令之后,会弹出下面的QEMU工作终端,



此时QEMU工作终端是一个黑色方框,没有任何内容,
现在要切换进QEMU,按下Ctrl+Alt+2即切换到QEMU工作台下.启动gdb服务,并设置主机连接端口,使用下面命令即可实现:
gdbserver 1234
这里要理解的一点是:当启动QEMU后,QEMU仿真器在等待着gdb的连接.
如图所示:



要在仿真器下进行源代码级的调试,需在另外一个终端下启动'vmlinux'调试,对应的命令为:
gdb vmlinux.这些工作都是在源码目录下实现的。
之后,在调试状态下进行QEMU的连接:
target remote localhost:1234
建立QEMU连接后,就可以进行gdb的正常使用了,例如,通过c命令启动内核
(gdb) c





参考:《linux2.6 内核标准教程》 河泰 王洪涛 编著



VMWare Workstation 6.0调试Linux Kernel,竟如此方便
来源: ChinaUnix博客  日期: 2007.04.20 16:14 (共有条评论) 我要评论 刚刚在LKML上看到的。可以做到C源代码级别的内核单步调试,真是简单的无以复加了。  如果不存在LICENSE方面的考虑, 我觉得UML(User Mode Linux)已经可以抛弃了。
一、环境: 
    1, 一台Linux机器, 用于运行gdb和VMWare Workstation 6.0, 我们称之为HOST机器。
    2, VMWare6.0中安装一个Linux系统, 我们称这个Linux系统为GUEST机器, 它运行被调试的内核。
二、设置:
    很简单,在你的HOST Linux中,往GUEST Linux的vmware配置文件(即后缀名为.vmx的文件)追加一个字符串。 在我的机器上是这样的:
    # echo "debugStub.listen.guest32=1" >> /root/vmware/FC4\ Linux/FC4\ Linux.vmx 
三、调试:
    
1, 把GUEST Linux上的内核映像文件(vmlinux)和内核源代码拷贝到HOST机器上。  
   
    为了能够用gdb的list命令显示源代码, 看看GUEST机器上的/lib/modules//source (以下简称source) 和/lib/modules//build (以下简称build)这两个符号链接各自指向哪里(注:如果编译内核时没有用O=选项指定输出的路径,那么这两个符号链接就指向同一个路径,通常是/usr/src/linux-2.x.xx)。 拷贝到HOST机器上相应的路径上。
    提醒: 编译内核之后build目录下会有很多临时文件, 有上G之大, 可以先把build目录下的vmlinux拷贝到HOST机器上, 然后进入source目录, 运行:
    make O=/lib/module//build clean
    然后再把build和source拷贝到HOST机器上。
2, 启动安装在VMWare中的GUEST Linux。
    
3, 在HOST机器上运行gdb, 进行调试:
    
(gdb) file vmlinux  (vmlinux就是从GUEST拷贝过来的内核映像文件)
Reading symbols from /root/vmlinux...done.
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) target remote localhost:8832
Remote debugging using localhost:8832
[New thread 1]
0x000f4299 in ?? ()
warning: shared library handler failed to enable breakpoint
作为例子,下面是几个调试动作:
3.1 设置断点:
(gdb) b do_IRQ
Click here to open new windowCTRL+Mouse wheel to zoom in/out
3.2 Continue和Step单步调试:
Click here to open new windowCTRL+Mouse wheel to zoom in/out
可以看出, gdb用运行以下step命令, GUEST Linux就往下执行一句;否则就停着。
3.3 清除断点,Continue
Click here to open new windowCTRL+Mouse wheel to zoom in/out
没有断点了, continue命令之后GUEST Linux就会正常的跑下去了。
3.4 gdb的quit命令
Click here to open new windowCTRL+Mouse wheel to zoom in/out
可见, 在gdb中quit了被调试程序, GUEST Linux就关机了。
VMWare Workstation 6.0真是个好东西, 推荐! :)


VMware下搭建linux内核kgdb调试环境

本文讲述如何在VMware环境下搭建linux内核kgdb调试环境。
首先,linux内核的调试需要两台机器,被调试的系统称之为server,调试的系统称之为client(server和client通过串口相连)。
由于进行linux内核的源码级别的调试,需要内核的支持,因此需要在编译内核时加入对kgdb选项的支持,否则无法进行kgdb的调试,关于编译这一步我还会在后面详细讲述的。
而且,我们还需要明确一点,支持kgdb功能的内核需要在server上运行,而且client也需要有一份相同的内核。因此,我们只需要在其中一台机器上编译支持kgdb功能的内核,然后将相同的内核拷贝一份到另外一台机器上,两台机器上的内核必须一致。由于是在client上进行源码级别的调试,因此client上需要保留一份内核源码,而且编译好的内核符号链接中的路径信息应该与内核源码的路径一致。因此,我建议大家在client上编译内核,然后再将编译好的内核复制到server相应的路径下即可。而且,如果系统内核是2.6.26及以上版本的,可以只将/usr/src/linux/arch/x86/boot/bzImage和System.map文件拷贝到server相同的路径下即可。将bzImage和System.map拷贝到server的相同位置后,别忘了在server上执行相应的make install和make modules_install命令,以正确安装内核及其模块。
下面就如何编译带kgdb选项的linux内核做一个介绍。这一步是在执行make menuconfig命令的时候设置的。在make menuconfig的时候,在Files System选项中把ext3和ext2都编译进内核,也就是把前面的M变成*号。然后,在kernel Hacking中,选中如下选项:
选中Compile the kernel with frame pointers
选中KGDB: kernel debugging with remote gdb
并确认以下两项也是选中的(他们应该默认是选中的)
选中 kernel debugging
选中 Compile the kernel with debug info
对于其他选项,根据实际情况选择。
保存并退出。

然后,就接着执行:
make
make install
make modules
make modules_install
执行结束后,会在/boot目录下新添加vmlinuz-2.6.28.10和initrd.img-2.6.28.10这两个文件。

完成了上述步骤后,我们还只是得到了一个支持kgdb功能的内核,要让这个内核真正运行起来,还需要在/boot/grub/menu.lst文件中做一些修改和补充才行。

打开menu.lst文件,我们可以看到这样一个加载选项:
title  Ubuntu 9.04, kernel 2.6.28-11-generic
uuid  b836e381-ad06-402d-9066-62da2f588a27
kernel  /boot/vmlinuz-2.6.28-11-generic root=UUID=b836e381-ad06-402d-9066-62da2f588a27 ro quiet splash
initrd  /boot/initrd.img-2.6.28-11-generic
quiet
这就是系统原来的加载选项,为了能够在启动中选择加载支持kgdb模式的内核,我们可以在上面这个加载选项内容上做一些添加和修改即可,修改后的选项如下所示:
title  Ubuntu 9.04, kernel 2.6.28.10(kgdb kernel)
uuid  b836e381-ad06-402d-9066-62da2f588a27
kernel  /boot/vmlinuz-2.6.28.10
root=UUID=b836e381-ad06-402d-9066-62da2f588a27 ro quiet splash text kgdboc=ttyS1,115200 kgdbwait
initrd  /boot/initrd.img-2.6.28.10
quiet

首先,kernel部分的/boot/vmlinuz-2.6.28-11-generic替换成了/boot/vmlinuz-2.6.28.10,而这个vmlinuz-2.6.28.10恰恰是我们刚才新编译好的支持kgdb功能的新内核;initrd部分的
/boot/initrd.img-2.6.28-11-generic也换成了新生成的/boot/initrd.img-2.6.28.10。
其次,kgdboc=ttyS1,115200和 kgdbwait内容是新添加的。
kgdboc表示:kgdb over console,ttys1是串口设备名称,115200是串口通信的波特率。
如果不加kgdbwait则这样就可以调试内核模块,即可以动态加载内核模块然后进行调试,如果加了kgdbwait则可以直接调试linux系统的内核启动过程。这样设置表明server的内核在启动过程中会等待client的连接,连接完成后就直接进入调试状态。
下面我分别就两种方式展开讲述,第一种就是直接调试linux的内核,第二种是对可加载的内核模块进行调试。
第一种方式也就是加了kgdbwait的方式。我们在启动的菜单中选择了kgdb kernel后,server会出现如下信息:
Kgdb:Waiting for connection from remote gdb
这表明server正在等待client的连接。
此时,我们进入client系统,并在console中启动gdb:
cd  /usr/src/linux (进入/usr/src/linux目录)
gdb vmlinux
(gdb) set remoteband 115200
(gdb) target remote /dev/ttyS1
这些步骤获得成功后,就进入了内核调试状态了,之后的操作就跟用gdb调试应用程序一样简单了。
在第二种方式中,server启动时并不会进入wait状态,而是直接启动完成。当server启动完成后,我们可以在server 的console终端中输入echo g > /proc/sysrq-trigger来使server进入内核调试状态。这样,server就会中断,进入调试状态,等待远端client 的gdb连接。
Server进入调试状态后,再转到client调用gdb,就可以调试了。如果要对server上的内核模块进行调试,需要在编译模块时加入调试信息,并且client和server的内核模块要处在同一个路径下面,而且client上需要有内核模块的源代码,同时client还要设置相应的符号文件的搜索路径等,完成这些步骤后,就可以通过gdb命令来调试了。

这样,我们就可以利用VMware的环境来对linux的内核和动态加载模块进行源码级别的调试了。




通过UML可以方便的在本机调试Linux内核,UML是一种特殊的虚拟机,另外一种更为灵活的虚拟机是Qemu,Qemu是一种完全仿真虚拟机, 可以在i386平台仿真任意其他处理器构架,而且支持GDB调试,这里尝试一下使用Qemu调试Linux内核,Qemu参数-kernel可以直接指定 内核启动,这与UML有相似之处。

首先需要编译安装Qemu,这里并没有什么疑惑之处,直接从官方网站下载源码,使用Linux最常用的编译命令即可:

# 默认选项会编译所有处理器构架虚拟机# 可以 ./configure --help 查看编译特定平台的配置cpp@dark:~/qemu-0.12.4$ ./configure --target-list=i386-softmmucpp@dark:~/qemu-0.12.4$ makecpp@dark:~/qemu-0.12.4$ make install

接下来以默认选项编译内核,不过巨崩溃的是内核默认没有DEBUG_INFO选项,所以虽然能在导出符号部分断下来,但是没有源码,所以需要选则Compile the kernel with debug info选项。

# 默认选项cpp@dark:~/linux-2.6.34$ make defconfig# 配置菜单,选择调试信息cpp@dark:~/linux-2.6.34$ make menuconfig# 编译内核cpp@dark:~/linux-2.6.34$ make

然后使用Qemu加载内核启动

cpp@dark:~/linux-2.6.34$ qemu -s -S -kernel arch/x86/boot/bzImage -hda rootfs.img -append "root=/dev/sda"# 其中一些选项的解释如下# -s 监听tcp:1234端口以等待GDB连接# -S 虚拟机启动后停止,以便GDB连接后调试启动过程# -kernel 压缩内核# -hda 硬盘# -appand 启动参数

这里系统很容易启动不了,如果系统由于VFS加载错误无法启动,首先尝试启动参数root=/dev/sda为root=/dev /hda,hda是IDE硬盘标识,sda为SCSI硬盘,这要看内核识别成哪种,如果仍然不行,那要检测rootfs.img根文件系统的格式跟内核所 支持的格式是否匹配,2.6.34支持ext3格式,如果格式错误,可以利用 Linux 内核调试1 里的方法,自己手工创建一个ext3格式的文件,然后mount到临时文件,把其他根文件系统全部拷贝进去即可。

这 样启动Qemu之后,发现系统一片黑屏,这里因为Qemu启动参数被设置为禁止,Qemu本身具有显示窗口和控制窗口,利用 Ctrl+Alt+2 进入控制台,c命令继续虚拟机,Ctrl+Alt+1 返回显示窗口,Qemu的控制台可以控制很多选项,从这点来看要比VMware等要灵活一些,UML本身也支持运行时控制。

接下来使用GDB启动虚拟机,当系统停止在启动之后时,用GDB加载:

# 启动GDB,GDB加载未压缩内核cpp@dark:~/linux-2.6.34$ gdb vmlinuxGNU gdb 6.8Copyright (C) 2008 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.  Type "show copying"and "show warranty" for details.This GDB was configured as "i486-slackware-linux"...(gdb)# 设置断点(gdb) br start_kernelBreakpoint 1 at 0xc172f5d4: file init/main.c, line 533.# 连接Qemu监听端口1234(gdb) target remote localhost:1234Remote debugging using localhost:1234[New Thread 1]0x0000fff0 in ?? ()# 继续Qemu虚拟机执行(gdb) cContinuing.# 内核解压之后很快便到达断点Breakpoint 1, start_kernel () at init/main.c:533533             smp_setup_processor_id();(gdb)# 接下来便可任意调试内核(gdb) list528     asmlinkage void __init start_kernel(void)529     {530             char * command_line;531             extern struct kernel_param __start___param[], __stop___param[];532533             smp_setup_processor_id();534535             /*536              * Need to run as early as possible, to initialize the537              * lockdep hash:(gdb)

Qemu由于是完全仿真实现,所以可以在任意平台调试其他构架内核,理论上甚至可以在Windows平台交叉编译Linux内核,再去由Qemu加载调试(交叉编译比较麻烦,需要先编译目标构架的binutil,而后链接内核)。

相关系列文章:
Linux 内核调试1-UML http://www.linuxidc.com/Linux/2012-07/66410.htm
Linux 内核调试2-UML调试内核 http://www.linuxidc.com/Linux/2012-07/66411.htm
Linux 内核调试3-UML网络配置 http://www.linuxidc.com/Linux/2012-07/66412.htm
Linux 内核调试4-Qemu调试Linux内核 http://www.linuxidc.com/Linux/2012-07/66413.htm
Linux 内核调试5-UML和Qemu调试模块 http://www.linuxidc.com/Linux/2012-07/66414.htm
Linux 内核调试6-使用KGDB双机调试 http://www.linuxidc.com/Linux/2012-07/66415.htm