一、背景和目的
qemu搭建arm64 linux kernel环境-CSDN博客
之前介绍了qemu启动kernel的配置步骤和方法,现在开始我们的调试,这篇文章主要讲解如何单步调试内核,所有的实验还是基于ARM64;
二、环境准备
需要准备host=x86 target = arm64的gdb, 有三种方式:一种是sudo apt install gdb-multiarch;
另外一种是用ARM官网下载交叉编译工具链,其中自带gdb(目前我使用的方式Arm GNU Toolchain Downloads – Arm Developer); 还有一种是下载gdb源码并编译;
不同的方式有些差异,用apt安装的如果ubuntu比较老,可能存在部分特性不支持(比如ARMv8.5的PAC,BTI等,之前用ubuntu18.04就遇到了);
注意:使用ARM 官网gdb的伙伴启动时可能会遇到缺少库和python3.8的报错(依赖libncurses5 ,libncursesw5及python3.8),可以参考下面解决(偷懒可以直接安装gdb-multiarch)
三、kernel debug
单步调试kernel 只需要三步:
第一步:qemu启动内核并暂停等待(暂停是可选的,如果不调启动,可以去掉),同时需要建立网络端口等待gdb attach;
第二步:启动gdb(target=arm64)加载对应kernel Image的vmlinux, attach到指定端口即可;
第三步:如果是启动是挂起,直接设置断点即可调试,如果未选择启动暂停,ctrl + c会触发挂起,然后就可以和前面一样,正常设置断点。
qemu启动调试脚本(注意这里有个小坑,直接调试的伙伴直接跳转到最后拷贝即可)
qemu-system-aarch64 \-machine virt,virtualization=true,gic-version=3 \-nographic \-m size=1024M \-cpu cortex-a72 \-smp 2 \ -kernel Image \-drive format=raw,file=rootfs.img \-append "root=/dev/vda rw" \-s \-S可以看到对比之前的启动参数也就是增加了-s 和-S,具体含义如下:
# -s 是-gdb tcp::1234 的简写,如果需要换端口可以用-gdb tcp::1234替换-s参数
# -S 是freeze cpu at startup的指令,也就是kernel 启动时就挂起,等待调试连接,如果不需要调试内核启动,这个参数也可以去掉
gdb启动
找到vmlinux所在目录(最好在linux编译的根目录,不要拷贝出来,这样调试源码可以直接显示,不然还要在gdb中设置src path),
geek@geek-virtual-machine:~/workspace/linux/linux-6.6.1$ aarch64-none-linux-gnu-gdb vmlinux
(gdb) target remote :1234
Remote debugging using :1234
0x0000000040000000 in ?? ()
(gdb) b start_kerne然后continue即可停在指定断点,后面就可以step 单步调试了,实际调试时会发现设置的断点start_kernel停不住,ctrl+c 触发挂起时会出现bt无法显示相关符号等问题(gdb) bt
#0 0xffffd43266a17f6c in ?? ()
#1 0xffffd43265ad7ad0 in ?? ()
#2 0x0000000000000002 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
这里的原因也是经常会遇到和遗忘的,查看kernel log可以看到KASLR字样
[ 0.000000] KASLR enabled
[ 0.000000] CPU features: kernel page table isolation forced ON by KASLR
KASLR是内核启动添加随机地址保护,启动后实际运行地址和vmlinux 有一个随机偏移值,在gdb中设置断点是基于vmlinux的(这个是不带偏移值的),实际qemu中运行的内核是在这个地址 + 随机偏移值,所以断点无法触发,出现上面的问题;
上面的问题有两种解决方法:
1、是重新编译内核,在arch/arm64/configs/defconfig 中将CONFIG_RANDOMIZE_BASE=y修改成CONFIG_RANDOMIZE_BASE=n
2、是在qemu启动的cmdline中增加nokaslr 参数,通过参数方式关闭
-append "root=/dev/vda rw nokaslr" \
修改正常后,断点能正确停止,bt调用栈显示正常
(gdb) bt
#0 cpu_do_idle () at arch/arm64/kernel/idle.c:32
#1 0xffff800081017f80 in arch_cpu_idle () at arch/arm64/kernel/idle.c:44
#2 0xffff800081018bcc in default_idle_call () at kernel/sched/idle.c:97
#3 0xffff8000800d7ad0 in cpuidle_idle_call () at kernel/sched/idle.c:170
#4 do_idle () at kernel/sched/idle.c:282
#5 0xffff8000800d7d48 in cpu_startup_entry (state=CPUHP_ONLINE) at kernel/sched/idle.c:380
#6 0xffff800081018eac in rest_init () at init/main.c:726
#7 0xffff800081aa08bc in arch_call_rest_init () at init/main.c:823
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
四、总结
qemu内核调试时需要注意关闭kaslr,更新正确的脚本,也不依赖kenerl config是否开启kaslr
qemu启动脚本(最终版本):
qemu-system-aarch64 \-machine virt,virtualization=true,gic-version=3 \-nographic \-m size=1024M \-cpu cortex-a72 \-smp 2 \ -kernel Image \-drive format=raw,file=rootfs.img \-append "root=/dev/vda rw nokaslr" \-s \-S
关于kaslr原理相关的知识,有兴趣的伙伴参考 文章 kaslr原理分析