利用Qemu + Buildroot 进行内核源码级调试

原文地址: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                // 列出所有buildroot支持的平台的默认编译。
$ make qemu_x86_64_defconfig          //  Build for qemu_x86_64
$ make linux-menuconfig               //  打开内核编译选项
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

默认情况内核是没有开启调试信息的,需要开启内核调试信息,否则gdb时候会出现:no debugging symbols found,开启内核调试信息选项的方式如下:

图1 
图2 
图3 
可能有些时候这个”Compile the kernel with debug info”选项没有直接显示,可能会依赖其他编译选项。开启选项然后保存。

$ make
 
 
  • 1

直接编译,最后编译结果都会在output/images/目录下面: 
图4

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/>.
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

系统自带的gdb会有一个问题,当给内核设置断点之后,继续运行的时候回显示:

Remote 'g' packet reply is too long
 
 
  • 1

再继续运行则显示: 
图5 
这样没办法进行跟踪调试,在网上找到解决办法,给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);

   /* Further sanity checks, with knowledge of the architecture.  */
-  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)
+  //  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;
+        } 
+    }  
   /* Save the size of the packet sent to us by the target.  It is used
     as a heuristic when determining the max size of packets that the
     target can safely receive.  */
 
 
  • 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
 
 
  • 1

gdb-7.6.1-80.el7.src.rpm 即为我们下载的包含源码的rpm包

$ sudo rpm -ivh gdb-7.6.1-80.el7.src.rpm         
 
 
  • 1

安装完之后在/root/rpmbuild/SPECS目录下会生成一gdb对应的spec文件: 
图6 
注意这个时候是在root用户目录操作,必须先切换成root用户。然后通过下面的命令来编译gdb。

# rpmbuild -bc gdb.spec
 
 
  • 1

我个人建议是把上面命令的输出全部重定向到一个文件中,然后在文件中寻找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
 
 
  • 1
② 这步也是在配置
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
 
 
  • 1
③ 这才是真正的编译方法
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  '
 
 
  • 1
④ 这个是生成文档相关的方法
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'
 
 
  • 1
  • 2

然后在/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 
 
 
  • 1

ctrl+alt+1 与 ctrl+alt+2可以切换,前者是屏幕输出,后者是qemu控制台 
运行起来后是黑屏,我们要切换到控制台,用鼠标点击窗口,然后ctrl+alt+2, 
如下图所示输入,然后回车,ctrl+alt+1切换回来,ctrl+alt切出鼠标. 
图7 
在另一个终端中:

$ cd output/build/linux-4.5.3                                       
$ /home/james_xie/work/gdb-7.6.1/gdb vmlinux 
(gdb) target remote localhost:1234
 Remote debugging using localhost:1234
 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) 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

可以看到,qemu窗口,内核已经有部分显示: 
图8 
通过gdb列出当前的源码: 
图9 
可以看到现在就可以进行断点调试内核。

三、调试驱动模块

由于我要研究内核bridge的具体实现,所以把内核bridge部分以模块的形式编译进来。 
图10 
本来可以直接静态编译,编译成模块,顺便熟悉下怎么调试内核驱动模块。 
do_init_module函数位置设置断点。

图11 
由于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>
 
 
  • 1

图12
从图片中看到,虚拟机已经停在do_init_module函数位置,通过打印可知是正在加载stp.ko模块,这个模块在bridge模块前面加载。 
图13 
这个打印的地址就是stp.ko模块.text地址。通过add-symbol-file命令在gdb中引入该模块符号。 
图14
这个时候就可以调试stp.ko模块里面的函数,由于我首要看的是bridge模块,所以先没在stp.ko相关函数里面设置断点,在gdb里面继续运行等待其加载bridge.ko模块时再次停止在断点位置。 
图15
同上操作一样,我们引入bridge.ko模块的符号。 
图16
这样就可以跟踪内核代码进行调试,不过模块中__init, __devinit宏修饰的函数不能被找到,不知道是我编译模块的时候有问题,还是其他什么原因,后续再更正。至此我们就可以跟踪调试内核代码,以及模块代码。

注意:现在的qemu都是默认开启kvm加速的,这样会导致设置的内核断点无法被断住,当时最直接的想法是关闭kvm加速功能,打算重新编译qemu对应的源码包,但是关闭这个功能之后居然编译不能通过,没有深入纠结这个,而是无意中在另外一台电脑上发现,开启VirtualBox虚拟机之后,qemu命令再执行的时候,会显示“failed to initialize KVM: Device or resource busy”这样的错误,表示kvm设备被占用,这个时候设置的断点就可以被断住,之后想想其实要关闭kvm没必要重新编译qemu源码,在bios里面禁用虚拟化支持,或者卸载内核kvm相关驱动即可。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值