这是一篇《使用qemu搭建linux内核开发环境详细教程》的续篇。需要合起来看
之前总结了一篇使用qemu搭建内核实验环境的文章,既然环境搭建起来了,那么就要用起来。跟踪内核代码,去实时看内核的走向,一直是一个不怎么容易的操作,要么缺乏硬件资源,要么缺乏软件环境,这里给出一种选择,使用qemu+gdb的方式,不依赖硬件,在我们自己的电脑上就可以完成这个过程。这里记录一下这个过程。
这是一篇纯操作的文章,没有什么知识点,虽然回头看很简单,但是流程上的的东西,一步不对就无法进行下去,不过还好,折腾出来了,借助于前人的分享,我也分享出来,希望帮助到更多和我一样热爱内核的人。
以下所有的操作都是基于《使用qemu搭建linux内核开发环境详细教程》https://blog.csdn.net/weixin_38227420/article/details/88402738中的环境去操作,如果你按照自己的方式搭建了环境,只需要修改编译链和路径即可
我这里使用的linux-3.10.104,高版本的4.4以上内核也可以
-
1.修改内核配置项,时内核带上debug信息
执行该操作,依次配置配置项
make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm menuconfig
Kernel hacking --->
[*] Compile the kernel with debug info
-
2.编译内核
make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm -j4
-
3.使用qemu加载内核
qemu-system-arm -M vexpress-a9 -s -kernel ./qemu-image/zImage -m 512M -append "root=/dev/ram rdinit=sbin/init console=ttyAMA0" -nographic
注意其中的-s。这个-s表示
-gdb tcp::1234
缩写,监听1234端口。使用了-s参数,待会我们就可以使用gdb去调试内核了
其中./qemu-image/zImage和qemu-image/rootfs.img需要根据你自己的环境中的路径来填写,如果路径不匹配会导致找不到这些镜像导致启动失败
-
4.使用gdb连接内核
4.1重新开一个终端
4.2使用gdb加载
进入到之前编译的内核代码中vmlinux所在目录执行
arm-linux-gnueabihf-gdb vmlinux
这里有一点一定要注意,你要调试的内核是用什么编译链编的,这里的gdb就要用这个编译链的gdb,因为我要调试的内核是用arm-linux-gnueabihf-xxx编译链编的,所以这里使用arm-linux-gnueabihf-gdb去加载vmlinux。
(小插曲:因为我的电脑是centos,它自带gdb,所以我刚开始直接是gdb vmlinux,但是怎么也无法断点调试,折腾了很久,因为很多前人写的博客也是用的gdb,但我这里就是不行,一点进展也没有,非常郁闷,直接怀疑自己了。直到后面看到一篇文章说,“调试的时候需要选对编译链”才意识到,我用的centos自带的gdb是x-86的gdb和我的版本不一样。。。。)
执行后终端是这样:
可以看到Reading symbols from vmlinux...done.表示已经加载了符号表了
4.3gdb连接内核:
在gdb中输入target remote tcp:localhost:1234,表示gdb去连接1234端口
target remote tcp:localhost:1234
连接成功后有如下显示
-
5.开始调试:
接下来的调试和在用户态使用gdb调试应用程序是一样的,使用的命令也是一样的,只是换成了调试内核
做两个简单的实验:
1.在kernel的启动过程中打2个断点,对start_kernel和kernel_restart这两个启动和重启必调的函数打断点
(gdb) b start_kernel
Breakpoint 1 at 0x80488790: file init/main.c, line 473.
(gdb) b kernel_restart
Breakpoint 2 at 0x80031d34: file kernel/sys.c, line 397.
(gdb)
然后执行c:contimue,等待内核操作
然后在另一个跑kernel的终端执行命令reboot,
可以看到,此时内核重启了,但是此时停在了Requesting system reboot,并且内核终端此时无响应,这是因为已经进入gdb的断点了,看下gdb的所在的终端:
可以看到已经停在了kernel_restart这个函数里了,表示在执行关机操作,此时使用gdb的如下命令:l 和bt命令
和在用户态使用gdb一样,使用l和bt命令可以查看此时的代码段和此时的调用栈,非常方便。
这里我们继续让内核往下走,在gdb中敲c命令
此时发现进入了第二个断点start_kernel,表示已经到了重启的操作,进入了c入口,此时使用n命令继续走可以看到
可以看到单步的运行过程
此时在执行c命令。切换到内核的终端,此时跳过了所有的断点,内核完成重启了
-
总结:
这种方式的好处是轻量简单,只需一台电脑即可,不涉及反复的烧写和硬件支持,效率高啊,对于感兴趣的内核模块,在看代码的时候可以打个断点跟一下,实践一下。比如可以触发一个中断,跟踪下中断的执行流程,我想这比看再多书也要管用。
不过也有局限,和真实的板上环境还是有区别,对于一些极度依赖具体硬件的驱动可能无法进行,但是这种方式比较适合学习和硬件不那么紧密的模块,例如内核的纯软件层面,可以用来做为一个学习内核的辅助手段。