用户工具

站点工具


hacking:v:debugging_with_vm

一种使用虚拟机调试内核的方法

内核开发似乎一直没有很好的调试方法,虽然有大牛说,纯爷们儿是不调试的,但是,对于我等新手而言,一时还没有勇气做纯爷们儿 :-P 。简单介绍一种使用虚拟机的调试方法,未必太好用,但是与纯汇编调试相比也是各有千秋了。现在有些虚拟机(VMware workstation 6, QEMU i386 )在虚拟cpu内部集成了gdbstub可以进行远程调试。

原理概述

所谓调试,这里指的是debug,是系统有问题需要查找问题原因。怎么查找问题呢,最简单的办法,看看运行在某几个时刻的系统是什么状态,这些状态是怎么转换的,为什么有问题,改改状态看有什么效果。那么什么是系统的状态呢,我认为比较土的说,就是cpu的状态(比如寄存器的状态),内存的状态(比如一些变量的状态)。所以,简单的说,土土的说,调试,debug,就是在系统运行的时候查看(修改)系统状态。debug的方法,最简单的就是print,这是最有效最直接的方法,不过,如果想多看几个状态,那就要添加代码,编译,重新运行,很繁琐。比较简洁的方法是使用类似gdb的工具,在运行时按照实际需要查看各个状态。

对于运行在虚拟机上的系统,我们有机会通过虚拟机提供的接口查看整个虚拟机的状态,自然包括运行的系统的状态,比如,QEMU与VMware都提供了这种接口,他们接口对外通信的协议是gdb的远程调试协议。

GDB的远程调试

具体的协议内容可以通过 info gdb 在 Remote Protocol 部分找到。简单的说,这个协议主要提供了查看(修改)寄存器(内存)等方法。 gdb通过这个协议获得(修改)运行时被调试系统的状态。通过这个协议与gdb合作的另一端称之为gdbstub。

虚拟机内置的gdbstub

QEMU与VMware通过内置的gdbstub与gdb通信,从而我们可以在系统的运行时通过gdb来查看(修改)系统的状态,而且,gdb可以进行源码级的调试,那么,日子就更好过了。

优点

在理想状态下,如果虚拟机没有bug(当然是不可能的),我们通过gdb能像调试普通的应用程序那样调试内核,还是源码级的,方便,明了。实际上,虽然现在QEMU对gdbstub的支持一塌糊涂(VMware没实际尝试过),但是,我们仍然能享受到部分的方便,比如,部分源码级的调试(设置断点,查看结构体内容,局部变量内容等),如果内核没有打开中断,我们还可以单步执行,等等。

操作步骤

安装QEMU

以root用户到/usr/ports/qemu-devel 执行make install就可以了。

ports中的qemu对amd64下的gdbstub是不支持的,但是cvs中最新的代码看上去已经支持amd64,所以,如果想用来调试amd64系统,最好先make patch然后把cvs上的部分代码合并进来,然后make install。合并的代码不多,在gdbstub.c中cpu_gdb_read_registers函数内。因为这么做不能保证一定工作,所以这里不详细介绍。而且,随着ports更新,这个工作应该以后就不需要了。

建立用于调试的基本系统与内核

首先找到你的代码树,比如,我们在 /mnt/fb1/oskernel/fbcur/src 下保存最新代码,确定编译时的中间文件放到/mnt/fb1/cross-dev/fbcurobjs/ 最后的系统在/mnt/fb1/fbcurmin/ (注意,这里的目录只是用于举例,你可以自由选择目录结构),那么首先buildworld,虽然我的pc是amd64但是因为qemu支持的问题,我希望调试i386系统,所以我用下面命令来buildworld

$ export MAKEOBJDIRPREFIX=/mnt/fb1/cross-dev/fbcurobjs
$ make TARGET=i386 DESTDIR=/mnt/fb1/fbcurmin buildworld
$ make TARGET=i386 DESTDIR=/mnt/fb1/fbcurmin KERNCONF=VBOX buildkernel
$ su
Password:
# make TARGET=i386 DESTDIR=/mnt/fb1/fbcurmin installworld
# make TARGET=i386 DESTDIR=/mnt/fb1/fbcurmin KERNCONF=VBOX installkernel

其中VBOX是我的内核配置文件,这是第一次,所以总是痛苦一些 :-P 以后只是修改内核编译内核就不必如此麻烦了。下面我们需要把这个基本系统安装到虚拟机里。

建立一个虚拟机用的硬盘,大小先用1G吧

$ cd /mnt/fb1
$ qemu-img create dbgfbsd.img 1024M

初始化这个虚拟硬盘

按照handbook地说明在这块虚拟硬盘上用虚拟机装好一个基本系统,然后可以直接更新虚拟硬盘上的文件

# mdconfig -a -t vnode -f dbgfbsd.img
md0
# mount /dev/md0s1a /mnt/tmp

下面我们可以开始了,运行QEMU,注意 -s 参数 然后开始调试

调试步骤

$ cd /mnt/fb1/cross-dev/fbcurobjs/i386/mnt/fb1/oskernel/fbcur/src/sys/VBOX/
$ gdb kernel.debug
GNU gdb 6.1.1 [FreeBSD]
Copyright 2004 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 "amd64-marcel-freebsd"...
(gdb) target remote amd64box:1234
Remote debugging using amd64box:1234
pmap_pte_quick (pmap=Variable "pmap" is not available.
) at /mnt/fb1/oskernel/fbcur/src/sys/i386/i386/pmap.c:947
947                     if (PMAP1cpu != PCPU_GET(cpuid)) {
warning: Unable to find dynamic linker breakpoint function.
GDB will be unable to debug shared library initializers
and track explicitly loaded dynamic code.
warning: shared library handler failed to enable breakpoint
(gdb) bt
#0  pmap_pte_quick (pmap=Variable "pmap" is not available.
) at /mnt/fb1/oskernel/fbcur/src/sys/i386/i386/pmap.c:947
#1  0xc072a3e1 in pmap_copy (dst_pmap=0xc16cf08c, src_pmap=0xc16d008c, dst_addr=135081984, len=135168, src_addr=135081984)
    at /mnt/fb1/oskernel/fbcur/src/sys/i386/i386/pmap.c:2856
#2  0xc06d8465 in vmspace_fork (vm1=0xc16d0000) at /mnt/fb1/oskernel/fbcur/src/sys/vm/vm_map.c:2530
#3  0xc04f70aa in fork1 (td=0xc16ce000, flags=Variable "flags" is not available.
) at /mnt/fb1/oskernel/fbcur/src/sys/kern/kern_fork.c:280
#4  0xc04f85a9 in fork (td=0xc16ce000, uap=0xc7b21cfc) at /mnt/fb1/oskernel/fbcur/src/sys/kern/kern_fork.c:94
#5  0xc072d943 in syscall (frame=0xc7b21d38) at /mnt/fb1/oskernel/fbcur/src/sys/i386/i386/trap.c:1008
#6  0xc0713cc0 in Xint0x80_syscall () at /mnt/fb1/oskernel/fbcur/src/sys/i386/i386/exception.s:196
#7  0xc7b21d38 in ?? ()
#8  0x0000003b in ?? ()
#9  0x0000003b in ?? ()
#10 0x0000003b in ?? ()
#11 0x08049db0 in ?? ()
#12 0xbfbfee24 in ?? ()
#13 0xbfbfe9b8 in ?? ()
#14 0xc7b21d64 in ?? ()
#15 0x00000040 in ?? ()
#16 0x080c33f8 in ?? ()
#17 0x00000002 in ?? ()
#18 0x00000002 in ?? ()
#19 0x0000000c in ?? ()
#20 0x00000002 in ?? ()
#21 0x08054543 in ?? ()
#22 0x00000033 in ?? ()
#23 0x00080286 in ?? ()
#24 0xbfbfe8ec in ?? ()
#25 0x0000003b in ?? ()
#26 0x00000000 in ?? ()
#27 0x00000000 in ?? ()
#28 0x00000000 in ?? ()
#29 0x00000000 in ?? ()
#30 0x07c9a000 in ?? ()
#31 0x00000000 in ?? ()
#32 0xc16ce000 in ?? ()
#33 0xc7b21adc in ?? ()
#34 0xc7b21aa8 in ?? ()
#35 0xc16ce1e4 in ?? ()
#36 0xc0533cbb in sched_switch (td=0xbfbfeed8, newtd=Variable "newtd" is not available.
) at /mnt/fb1/oskernel/fbcur/src/sys/kern/sched_ule.c:1902
Previous frame inner to this frame (corrupt stack?)
(gdb) q
The program is running.  Exit anyway? (y or n) y

FIXME 虽然我用amd64上面的gdb来调试i386的系统看上去是work的,但是我不确定这样是否可靠,请达人告知。

下次我只是修改了一点内核,编译那么多岂不是浪费,我们这样做

export MAKEOBJDIRPREFIX=/mnt/fb1/cross-dev/fbcurobjs/
cd /mnt/fb1/oskernel/fbcur/src
make buildkernel KERNCONF=VBOX TARGET=i386 DESTDIR=/mnt/fb1/fbcurmin -DNO_KERNELCLEAN -DNO_KERNELCONFIG -DNO_KERNELDEPEND NO_MODULES=1

然后直接把得到的kernel拷贝到虚拟机里面就可以了

存在的问题

理论上说,这种调试方法是很完美的,可惜天公不作美,好事多魔,这种方法并非太可靠,而且存在很多问题,下面粗略列举几种。总之,这种调试方法只能算作借鉴,辅助,当你发现了问题,或者解决了问题时,除了在虚拟机上测试通过,还要在真实环境中通过。

虚拟机存在bug

某人曾说,人类之所以区别动物是因为会使用工具,又有古人云,君子生非异也,善假于物也。然而,倘使你使用的工具存在问题,对你的伤害可能比不使用工具更加严重。QEMU存在各种严重的bug,或者说还不成熟稳定,在前面的版本中(现在cvs版没有尝试)对于i386下的double-fault的反应是自己死掉,所以,如果代码会产生double-fault就无法调试;再比如,直到最近不久,gdbstub才支持amd64,而ports中的版本还不支持;再比如,如果没有关闭中断,那么单步中断总是进入中断处理函数,等等

虚拟环境与真实环境的差异

虚拟环境与真实环境有许多差别,最明显的大概是中断,比如时钟中断等。另外,出于性能的考虑,有些虚拟机可能对硬件的模拟并不全面,仅仅是模拟常用的功能。所以有些在真实机器上运行很好的代码死在虚拟机上是正常的。

MP 的支持

FIXME 对这部分了解不深,期待达人补充。简单的说,对于虚拟机调试是否支持MP我抱怀疑态度,而且,我确实遇到过代码在真实MP机器上运行的很好而在虚拟机上而没有效果的事情,不管是否进行调试。

/data/vhosts/wiki-data/pages/hacking/v/debugging_with_vm.txt · 最后更改: 2008/05/27 04:30 由 prime