昨天听别人讲使用Qemu和gdb来实现源码级内核调试,今天试了一下,果然非常方便,现简单的记录一下。

 Qemu是一个开源的虚拟机软件,能够提供全系统的仿真,可以运行在多个平台上,并仿真多个别的平台。Qemu虚拟机是采用动态翻译来实现CPU的仿真的,对硬件的依赖程度低,通过它提供的众多参数,你能够对虚拟的机器进行定制以满足你的需求。

 要想对内核进行调试,那自然需要重新编译内核了,编译内核的具体方法这里就不罗嗦了,需要注意的是在配置内核时,要将“kernel hacking"中的“compile the kernel with debug info"选上,否则没有调试信息,gdb也难为无米之炊。

 编译好内核之后,我们还需要制作一个rootfs作为Qemu虚拟机的硬盘,这个步骤可以通过Qemu来完成,大致的步骤如下:

1. 创建一个虚拟的硬盘

# qemu-img create foobar.img 8G  # 创建一个8G的虚拟硬盘
 2. 在虚拟的硬盘上安装一个Linux系统
 # qemu -hda foobar.img -cdrom xxx.iso -boot d -m 512 -enable-audio -localtime 
 # xxx.iso为某linux的发行版本的iso,该命令就是要从iso启动,并将系统安装到foobar.img中

当然,其实也可以将init ram disk作为Qemu的rootfs,但这时需要对initrd进行修改,如果你对initrd不熟悉的话,最好还是花点时间,做个rootfs,这个可以省去一些麻烦。

 

Qemu虚拟机只是提供了一个虚拟的机器,使得程序运行在虚拟机中如同运行真实的物理机器上一样,除此以外,Qemu还必须能够对虚拟机进行完全的控制,但Qemu和gdb又是怎么扯上关系的呢?这是因为Qemu中内置了gdbserver,这使得Qemu能够和远程的gdb进行通讯,通过远程的gdb来控制Qemu虚拟机的执行,从而达到调试的目的。具体的操作如下:

1. 切换到刚刚编译的内核的路径,然后启动Qemu
 # qemu -kernel arch/x86/boot/bzImage -initrd /boot/initrd.img-2.6.31-22-generic -gdb tcp::1234 -S
 # -kernel 用来指定内核,注arch/x86/bzImage是不带调试信息的内核,vmlinux是带有调试信息的内核
 # -initrd 用来指定内核启动时使用的ram disk,
 # -gdb tcp::1234表示启动gdbserver,并在tcp的1234端口监听,-S表示在开始的时候冻结CPU直到远程的gdb输入相应的控制命令
 2. 启动gdb,并和Qemu进行联系,然后你就可以像调试应用程序那些调试内核了
 # gdb
 (gdb) file vmlinux
 (gdb) target remote :1234
 (gdb) b start_kernel
 
(gdb) c
 

  呵呵,是不是很简单,下面再简单的介绍一下gdb常用的一些调试命令: 

 
 

 

  help:即时帮助,当你不太记得或不太熟悉某些命令时,一个help就可以搞定 

 
 

 

  edit:在gdb中使用$EDITOR对某个文件进行编辑,在开始之前可以将$EDITOR设置为自己喜欢的编辑器 

 
 

 

  list:列出被调试程序当前上下文的源程序 

 
 

 

  make:相当于在shell中运行make,对工程进行编译,编译完成后使用run即可重新开始调试 

 
 

 

  run:简写为r,开始运行被调试的程序 

 
 

 

  break:简写为b,设置一个断点,该断点可以为某个文件的某行,也可是某个函数名,还可以是某个地址, 

 

  此外通过if参数还可以设置条件断点 

 
  
 

  backtrace:输出调用堆栈 

 
 

 

  info:查看当前gdb的各种信息,如断点信息,调用堆栈等 

 
 step:简写为s,单步执行,每次执行一行源程序,会跟踪进入函数
 next:简写为n,单步执行,每次执行一行源程序,不跟踪进入函数
 

   stepi:简写为si,单步执行,每次执行一个指令,会跟踪进入函数 
  
 stepi N:简写为si N,多步执行,每次执行N步指令,会跟踪进入函数 
  
 

    nexti:简写为ni,单步执行,每次执行一个指令,不跟踪进入函数 
 
 
 finish:执行直到从当前函数返回
 
 

 

  until:简写为u,执行直到所在的循环结束 

 
 

 

  continue:简写为c,继续执行直到下一个断点或程序结束或gdb收到信号 

 
 

 

  print:简写p,用来输出某个变量的值,只输出一次,输出结构时可以设置“set print pretty on“,这样观察更方便  

 
 

 

  display:观察某个变量,每次执行时都显示相应的变量 

 
 

 
 x:查看内存,通过相应的参数来执行内存的地址和要显示多少数据
x/i: 显示内存下一条指令
 x/Ni ADDR: 从内存地址ADDR处开始显示N步指令b uploadmgr.c:upload 表示在uploadmgr.c文件的upload函数打上断点。
b uploadmgr.c:12  表示在uploadmgr.c文件的12行打上断点.
 
nfo break可以查看当前的断点信息
 
delete  1 表示删除第一个断点  (用info break查看的 断点号)
delete 不带参数是删除所有断点
 
gdb xxx |tee newfile  可以将gdb过程中,保存所有输出到newfile中去
 
 set args a b c 
 ,设置调试程序的参数