这次来看如何调试内核模块,也就是驱动程序,模块的调试跟普通程序略有不同,不论是内核还是普通应用程序,在连接之后便以得知代码将要加载的位置,用户态程序有虚拟地址映射机制,而内核独占物理内存。内核运行与共享的内核地址空间,所以不能使用相同的线性地址,只能由内核加载模块时指定起始地址,模块中都以此为偏移运行。所以内核的调试不能使用普通的方式,需要知道模块的加载地址。
而且Qemu的调试原理与UML相似,也可用相同的方法进行模块的调试,这里仅以UML模块调试举例
接下来启动带网络的UML,拷贝go.ko模块文件到UML虚拟机中,之后启动GDB:
当然加载符号时还可以查看 .data 段和 .bss 段的基址,加载go.ko时同时可以设置二者的基址,以便可以调试全局变量等等,初看起来这个过程比较复杂,其实就是通过调试内核加载位置来确定模块最终将要加载的基址,而后通过机制加载模块符号。
这个复杂的过程如果每次都需要手动处理是非常烦人的一件事,好在GDB本身有脚本扩展(甚至可执行Python脚本),来简化这个过程,这里来试着写一个简单的打印模块section名字和基址的脚本。
GDB脚本运行可以由两种方式,一种是在GDB启动时,在当前目录查找.gdbinit文件解释执行,另一种在GDB运行期间使用 source script-file 命令来执行,脚本的说明可查阅文档,以下是简单的打印段信息的脚本:
以下是加载并执行脚本效果:
如此便可稍微方便一点的打印所有段名和段基址,如果添加其他脚本扩展,可以更加方便且高效的调试Linux内核。