原文地址:http://blog.youkuaiyun.com/xj178926426/article/details/53118589
利用Qemu + Buildroot 进行内核源码级调试
概要:最近想研究下内核里Bridge的具体实现过程,加之公司使用的嵌入式开发环境是基于Buildroot的,想着借用Buildroot来编译qemu可以使用的内核和文件系统,顺便熟悉下Buildroot环境,也省去自己编译内核通过busybox制作文件系统的麻烦。
一、环境
1. 宿主机(64位)系统:
$ lsb_release -d
Description: CentOS Linux release 7.2.1511 (Core)
2. Buildroot环境:
Buildroot是网上https://buildroot.org/download.html下载的最新版本:buildroot-2016.05.tar.gz,默认编译qemu_x86_64可用的版本。
$ tar zxvf buildroot-2016.05.tar.gz
$ cd buildroot-2016.05
$ make list-defconfigs
$ make qemu_x86_64_defconfig
$ make linux-menuconfig
默认情况内核是没有开启调试信息的,需要开启内核调试信息,否则gdb时候会出现:no debugging symbols found,开启内核调试信息选项的方式如下:
可能有些时候这个”Compile the kernel with debug info”选项没有直接显示,可能会依赖其他编译选项。开启选项然后保存。
$ make
直接编译,最后编译结果都会在output/images/目录下面:

3. Gdb环境
[james_xie@james-desk images]$ gdb -v
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
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 "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
系统自带的gdb会有一个问题,当给内核设置断点之后,继续运行的时候回显示:
Remote 'g' packet reply is too long
再继续运行则显示:
这样没办法进行跟踪调试,在网上找到解决办法,给gdb打一个patch,patch内容如下:
--- remote.c 2015-02-20 19:11:44.000000000 +0200
+++ remote-fixed.c 2015-08-12 20:00:14.966043900 +0300
@@ -6154,8 +6154,20 @@
buf_len = strlen (rs->buf);
- if (buf_len > 2 * rsa->sizeof_g_packet)
- error (_("Remote 'g' packet reply is too long: %s"), rs->buf);
+
+
+
+ if(buf_len > 2 * rsa->sizeof_g_packet) {
+ rsa->sizeof_g_packet = buf_len;
+ for(i = 0; i < gdbarch_num_regs(gdbarch); i++){
+ if(rsa->regs->pnum == -1)
+ continue;
+ if(rsa->regs->offset >= rsa->sizeof_g_packet)
+ rsa->regs->in_g_packet = 0;
+ else
+ rsa->regs->in_g_packet = 1;
+ }
+ }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
Centos系统上可以通过如下命令可以下载系统已经安装的对应版本的gdb源码:
$ sudo yumdownloader --source gdb
gdb-7.6.1-80.el7.src.rpm 即为我们下载的包含源码的rpm包
$ sudo rpm -ivh gdb-7.6.1-80.el7.src.rpm
安装完之后在/root/rpmbuild/SPECS目录下会生成一gdb对应的spec文件:
注意这个时候是在root用户目录操作,必须先切换成root用户。然后通过下面的命令来编译gdb。
# rpmbuild -bc gdb.spec
我个人建议是把上面命令的输出全部重定向到一个文件中,然后在文件中寻找gdb源码包编译的真正过程,其实可以直接通过匹配configure和make关键字即可,我得到的gdb编译的具体过程如下:
① 指定编译选项,生成Makefile文件
./configure --prefix=/usr --libdir=/usr/lib64 --sysconfdir=/etc --mandir=/usr/share/man --infodir=/usr/share/info --htmldir=/usr/share/doc/gdb-doc-7.6.1 --pdfdir=/usr/share/doc/gdb-doc-7.6.1 --with-system-gdbinit=/etc/gdbinit --with-gdb-datadir=/usr/share/gdb --enable-gdb-build-warnings=,-Wno-unused --enable-werror --with-separate-debug-dir=/usr/lib/debug --disable-sim --disable-rpath --with-system-readline --with-expat --without-libexpat-prefix --enable-tui --with-python --with-rpm=librpm.so.3 --with-lzma --without-libunwind --enable-64-bit-bfd --enable-inprocess-agent '--with-auto-load-dir=$debugdir:$datadir/auto-load' '--with-auto-load-safe-path=$debugdir:$datadir/auto-load:/usr/bin/mono-gdb.py' --enable-targets=s390-linux-gnu,powerpc-linux-gnu,powerpcle-linux-gnu x86_64-redhat-linux-gnu
② 这步也是在配置
make -j4 'CFLAGS=-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic ' 'LDFLAGS=-Wl,-z,relro ' maybe-configure-gdb
③ 这才是真正的编译方法
make -j4 'CFLAGS=-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong
④ 这个是生成文档相关的方法
make -j4 -C gdb/doc gdb.info gdb/index.html gdb.pdf annotate.info annotate/index.html annotate.pdf MAKEHTMLFLAGS=--no-split MAKEINFOFLAGS=--no-split
make: Entering directory `/root/rpmbuild/BUILD/gdb-7.6.1/build-x86_64-redhat-linux-gnu/gdb/doc'
然后在/root/rpmbuild/BUILD目录下会有gdb的源文件,已经打好patch的源文件。拷贝该源文件到非root用户的工作目录,打上上面中的patch,按照上面的步骤重新编译即可。
二、运行调试
$ qemu-system-x86_64 -m 5120 -smp 4 -M pc -kernel output/images/bzImage -drive file=output/images/rootfs.ext2,if=virtio,format=raw -append root=/dev/vda -net nic,model=virtio -net user -s -S
ctrl+alt+1
与 ctrl+alt+2
可以切换,前者是屏幕输出,后者是qemu控制台
运行起来后是黑屏,我们要切换到控制台,用鼠标点击窗口,然后ctrl+alt+2
,
如下图所示输入,然后回车,ctrl+alt+1
切换回来,ctrl+alt
切出鼠标.
在另一个终端中:
$ cd output/build/linux-4.5.3
$ /home/james_xie/work/gdb-7.6.1/gdb vmlinux
(gdb) target
0x0000000000000000 in irq_stack_union ()
(gdb) b start_kernel
Breakpoint 1 at 0xffffffff81882aad: file init/main.c, line 498.
(gdb) c
Continuing.
Breakpoint 1, start_kernel () at init/main.c:498
498 {
(gdb) step
507 set_task_stack_end_magic(&init_task);
(gdb)
可以看到,qemu窗口,内核已经有部分显示:
通过gdb列出当前的源码:
可以看到现在就可以进行断点调试内核。
三、调试驱动模块
由于我要研究内核bridge的具体实现,所以把内核bridge部分以模块的形式编译进来。
本来可以直接静态编译,编译成模块,顺便熟悉下怎么调试内核驱动模块。
在do_init_module
函数位置设置断点。
由于buildroot种有对ko文件随内核启动的时候自动加载的机制,所以这里不需要手动去qemu里面进行自动加载,在gdb里面直接继续运行,等待断点即可,参数mod->sect_attrs->attrs放的是各个section的信息。只有.text信息,并没有.bss和.data,我们需要将这些信息提供给gdb。使用如下命令即可:
add-symbol-file xxx.ko <text addr> -s .data <data addr> -s .bss <bss addr>

从图片中看到,虚拟机已经停在do_init_module函数位置,通过打印可知是正在加载stp.ko模块,这个模块在bridge模块前面加载。
这个打印的地址就是stp.ko模块.text地址。通过add-symbol-file命令在gdb中引入该模块符号。

这个时候就可以调试stp.ko模块里面的函数,由于我首要看的是bridge模块,所以先没在stp.ko相关函数里面设置断点,在gdb里面继续运行等待其加载bridge.ko模块时再次停止在断点位置。

同上操作一样,我们引入bridge.ko模块的符号。

这样就可以跟踪内核代码进行调试,不过模块中__init, __devinit宏修饰的函数不能被找到,不知道是我编译模块的时候有问题,还是其他什么原因,后续再更正。至此我们就可以跟踪调试内核代码,以及模块代码。
注意:现在的qemu都是默认开启kvm加速的,这样会导致设置的内核断点无法被断住,当时最直接的想法是关闭kvm加速功能,打算重新编译qemu对应的源码包,但是关闭这个功能之后居然编译不能通过,没有深入纠结这个,而是无意中在另外一台电脑上发现,开启VirtualBox虚拟机之后,qemu命令再执行的时候,会显示“failed to initialize KVM: Device or resource busy”这样的错误,表示kvm设备被占用,这个时候设置的断点就可以被断住,之后想想其实要关闭kvm没必要重新编译qemu源码,在bios里面禁用虚拟化支持,或者卸载内核kvm相关驱动即可。