事前准备
1、 内核选项
使用gdb 调试必须要在内核里面添加两个编译选项
CONFIG_DEBUG_INFO=y
CONFIG_GDB_SCRIPTS=y
2、gdb安装
ubuntu 默认安装的gdb 是x86的版本,需要调试aarch64架构的内核必须要安装gdb-multiarch
命令,gdb版本应该也有要求,当前笔者使用的是7.11 可以顺利调试
apt-get install gdb-multiarch
3、image和文件系统的准备
文件系统和image 内核以及 vmlinux 需提前准备好,本文不再详述
4、qemu的安装
qemu 编译的和安装本文也不再详述,网上有大量教程可以参考,老版本qemu是使用qemu-system-arrch64
新版本qemu是使用qemu-kvm
5、gdb的一些常用命令
本文只涉及gdb相关命令只涉及最简单的调试部分,其他的请查看gdb手册
开始调试
1、启动虚拟机
参考命令如下
./qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine type=virt -nographic -m 4096 -kernel /home/l00221334/Image -initrd /home/l00221334/minifs_vm_st5.cpio.gz --append console=ttyAMA0
-S -gdb tcp::8889这里解释一下一些特殊的参数:
-S 是等待gdb的启动信号后,再继续启动
-gdb tcp::8889 这里是指定gdb 链接的端口号,
执行这条命令后 qemu 就会暂停,等待gdb接入
2、启动GDB
gdb-multiarch vmlinux
这里启动的同时读取vmlinux的符号表
进入gdb后设置架构为aarch64
set architecture aarch64
设置代码路径(如果有内核代码就可以设置,打断点和栈信息可以看到所有代码行信息,如果没有源代码可以跳过这一步骤)
directory /home/mykernel
3、连接qemu的内核
target remote:8889
正常连接后,可以看到如下打印
4、打断点
这里可以先打个断点,断到kernel启动入口(注意,使能KVM的情况下,需要使用hbreak)
b start_kernel
5、继续运行
然后开始继续跑就按c 就可以了
然后就可以根据需要打自己的断点进行调试了。
6、常见的学习调试方法举例
这里举一个看调用栈的例子,这种调试方法很有利于理解函数处理流程
注意1:GDB里面回车表示执行上条命令,没事不要轻易敲回车
注意2:行过程中,可以随时按CTRL+C 暂停运行
Ko的debug
1、将目标ko 放到虚拟机内
一般通过SCP命令
scp [email protected]:/home/111/222/test/hal.ko /home
2、获取ko的段信息(麻烦之处)
1、 断点断到load_module
2、 看内核代码load_module函数内,调用 do_init_module的地方加个断点
事例如下:
看到代码在3761行
断点断到3761
b 3761
3、查找各个段的地址信息
通过
p mod->sect_attrs->attrs[0]
查找代码段信息
我们关注的是如下段落信息,如果不知道是数组里对应的哪几个
(gdb) print mod->sect_attrs->attrs[14]$30 = {mattr = {attr = {
name = 0xffff80000a69df80 ".data", mode = 292}, show = 0xffff000008164174 <module_sect_show>,
store = 0x0 <reg_main_init>, setup = 0x0 <reg_main_init>, test = 0x0 <reg_main_init>, free = 0x0 <reg_main_init>},
name = 0xffff80000a69df80 ".data", address = 18446462598749908992}
(gdb) print mod->sect_attrs->attrs[16]$32 = {mattr = {attr = {
name = 0xffff80000a69d700 ".bss", mode = 292}, show = 0xffff000008164174 <module_sect_show>,
store = 0x0 <reg_main_init>, setup = 0x0 <reg_main_init>, test = 0x0 <reg_main_init>, free = 0x0 <reg_main_init>},
name = 0xffff80000a69d700 ".bss", address = 18446462598750179328}
(gdb) print mod->sect_attrs->attrs[0]$35 = {mattr = {attr = {
name = 0xffff80000a69ed00 ".text", mode = 292}, show = 0xffff000008164174 <module_sect_show>,
store = 0x0 <reg_main_init>, setup = 0x0 <reg_main_init>, test = 0x0 <reg_main_init>, free = 0x0 <reg_main_init>},
name = 0xffff80000a69ed00 ".text", address = 18446462598749437952}
将这三段地址打印出来
(gdb) print /x mod->sect_attrs->attrs[0]->address$34 = 0xffff000000fd4000
(gdb) print /x mod->sect_attrs->attrs[14]->address$36 = 0xffff000001047000
(gdb) print /x mod->sect_attrs->attrs[16]->address$37 = 0xffff000001089000
4、加载ko
将这三段地址在ko加载过程中添加上,
add-symbol-file /home/111/222/test/hal.ko 0xffff000000fd4000 -s .data 0xffff000001047000 -s .bss 0xffff000001089000
加载完成后就可以正常调试了,
当前还没找到一种更简单的办法可以在加载的时候就能直接通过简单命令来解析这些字段地址。
5、另一种查找地址的方式
如果不需要在加载过程中打断点,可以在加载后,读取文件内容来获取相关地址
/sys/module/hal/sections这个目录下有加载ko的地址信息,hal是ko的名字,
[email protected]:/sys/module/hal/sections# cat .data
0xffff000001047000
[email protected]:/sys/module/hal/sections# cat .bss
0xffff000001089000
[email protected]:/sys/module/ hal/sections# cat .text
0xffff000000fd4000
总结
优势
1、可以像用户态一样调试,gdb所有功能都能用,不用额外添加打印,不用另外编译
2、可以了解虚拟机内的所有处理流程,一些内核的公共流程不清楚的也可以通过这种方式来学习
限制
1、虚拟机流程和物理机流程不能完全一致,特别是内核启动和实际单板的流程有区别
2、如果是调试驱动,要依赖arm芯片上的硬件,就要在ARM单板上,用类似的流程,并且要把设备映射到虚拟机内