赞
踩
学内核之一:基于QEMU搭建Linux内核启动调试环境_启动Linux内核
学内核之二:基于QEMU搭建Linux内核运行调试环境_加载文件系统
目录
通过之前的两篇,我们利用QEMU构建了基于ARM平台的Linux调试系统。现在,我们就可以基于此对一些问题进行研究。
最常用的,我想肯定是函数调用了。比如某个函数是怎么调用过来的。另外就是,产生某种问题或现象时,都调用了哪些函数。这里,我们以内核Oops为例,来看看产生异常后,内核是如何调用处理的。
要分析函数调用,最关键的就是了解栈结构。通过栈回溯,就可以梳理出函数调用层次。内核里,如果想知道某个函数的调用栈,也可以利用WARN_ON宏,相比与BUG_ON,WARN不会停止执行,对于只是想看看某个处理流程,不想这么麻烦构建环境(比如有真机环境),喜欢偷懒的同学,就可以直接在内核相关代码处添加,跟打印类似。
当然,要是经常处理这类问题,为提高效率,建议还是通过其他高效手段来搞定。这篇文章采用GDB的方法。
因为Oops很典型,也很棘手。当我们在系统运行过程中遇到Oops时,就表明内核的某个地方出现了问题。比如,典型的空指针访问。分析Oops,需要对堆栈有所了解。Oops会展示出异常出现时,内核的现场环境,开发人员就需要根据这些现场信息,分析到底是哪里,什么原因导致的问题。
不过,现场的信息,似乎并不是很友好。有很多简写字符,有很多寄存器,还有很多十六进制数据,等等。如果不能根据堆栈分析出问题,那么这些寄存器和内存数据,就是进一步分析的重要参考了。
但是,前提是,你需要了解这些信息都表示什么含义。获取这些信息最简便的方式,就是看出现Oops时,内核做了什么,也就知道了打印的信息都是什么了。今天,我们是通过一种调试手段研究另外一种调试手段。
对于一个不熟悉内核,或者说不是很熟悉内核的开发人员,想要一下子定位到Oops的处理代码,并非易事。搞定这件事最简单的办法,就是构建一个模拟环境,主动触发Oops,然后通过加断点方式,跟踪内核处理,从而理清整个脉络。
前面有提到,触发Oops的一种常见问题,就是内核空指针。我们可以在内核的某些执行点上,加入空指针访问代码,然后设定断点,就可以跟踪整个流程了。
不过,今天讲的方法,不使用修改内核的方法,而是编写一个模块ko,然后触发问题。
我们编写一个极为简单的模块,在init方法中,触发空指针访问。
编写makefile
make
后面我们 insmod test_module.ko来观察现象。
在insmod之前,我们需要将模块拷贝到根文件系统中,然后重新打包根文件系统
具体可参考系列之二。
现在,我们就可以运行系统了。
跟之前直接启动不同的是,为了gdb调试,我们需要使用-s -S参数,这样可以让内核被gdb挂载上。
qemu-system-arm -nographic -s -S -m 512M -M virt -kernel /home/work/KernelStudy/Kernel/linux-4.19.244/arch/arm/boot/zImage -append "rdinit=/linuxrc root=/dev/ram console=ttyAMA0 loglevel=8" -initrd /home/work/KernelStudy/rootfs/rootfs.img
之后,gdb连接
系统启动后,加载模块,出现Oops,如下图
现在,有了复现手段,我们就可以调试了。
注意,内核需要配置CONFIG_DEBUG_KERNEL=y,这样才能生成调试符号信息
因为我们是从用户空间加载ko触发驱动,设定断点就稍微复杂点了。不过,我们可以看到,调用栈里面有do_init_module,我们可以再系统起来后,加载ko之前,设定断点到这个函数,就可以断住了。从这个地方跳转到问题点还是有点困难的,因为这中间要经历异常处理。
当然,如果对内核有稍微了解,就可以知道这类问题基本都在trace里处理,你也可以在内核代码里搜索一下上述问题日志的字符串,找到关联文件,类似函数,就可以断住测试验证。我们还是用这种方式来看。
进一步的,在__die中就可以单步跟踪整个日志信息的输出流程了。比如下面这个寄存器的输出
对应到终端输出为
后面的就不再展示了。
这里提供一种方法,协助开发人员快速定位问题点的代码。通过调试,对应看代码,效果会更好。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。