Linux kernel调试

栏目: C · 发布时间: 7年前

内容简介:Linux kernel调试

本文发自 http://www.binss.me/blog/how-to-debug-linux-kernel/ ,转载请注明出处。

debug分为两大流派。打log流和断点流。

printk

对于小程序来说,打log太爽了,我可以花式打log,每行插一行log,一次加一行log。

在头文件 <linux/kernel.h> 中定义了 8 种可用的日志级别字符串:KERN_EMERG,KERN_ALERT,KERN_CRIT,KERN_ERR,KERN_WARNING,KERN_NOTICE,KERN_INFO,KERN_INFO。共有 8 种优先级,用户可以根据需要进行配置

KERN_EMERG    /* system is unusable                   */
KERN_ALERT    /* action must be taken immediately     */
KERN_CRIT     /* critical conditions                  */
KERN_ERR      /* error conditions                     */
KERN_WARNING  /* warning conditions                   */
KERN_NOTICE   /* normal but significant condition     */
KERN_INFO     /* informational                        */
KERN_DEBUG    /* debug-level messages                 */

打log:

printk(KERN_DEBUG "%d", HZ);

为了防止log太多撑爆ring buffer,启动时使用参数 log_buf_len=104857600 来指定buffer大小,这里为100M。

注意启动参数不要带 quite 和 splash。

QEMU + GDB

打log固然爽,然而对于kernel这种"程序",每次rebuild可以等半天。每次调试都要不断加printk和rebuild显然不实际,于是考虑使用GDB进行动态调试。由于是调试kernel,在不考虑kGDB等方法的情况下,最简单的是跑一个虚拟机,然后GDB远程连上去。QEMU为此提供了较好的支持。

编译kernel

为了能够动态调试kernel,在编译前需要进行相应的配置,流程如下:

make mrproper
make x86_64_defconfig

cat <<EOF >.config-fragment
CONFIG_DEBUG_INFO=y
CONFIG_GDB_SCRIPTS=y
EOF

./scripts/kconfig/merge_config.sh .config .config-fragment

make -j $(nproc)

这里首先清理掉编译生存的文件,重新产生一个配置文件(.config),并和配置了DEBUG的.config-fragment进行合并,最后有多少个核就开多少路进行编译。

注意.config很重要,先前我直接拷贝了/boot/config-xxx(当前kernel的配置)到.config,最后编译后发现无论是break(通过修改内存产生debug异常来断点)还是hbreak(通过debug寄存器来设置断点)都无法断点。只能通过 make x86_64_defconfig 生成一份新的配置,才能成功GDB。不知道因为哪个选项导致在gdb中无法断点,苦查无果......

2017.4.11更新

今晚洗澡的时候回想起这个问题,打算花点时间解决之。手头有两份文件,一份是原kernel的配置文件 .config_old ,无法断点;另一份是通过 make x86_64_defconfig 新生成的配置文件 .config_new ,可以断点,但缺乏一些配置导致编译的kernel无法带起物理机。于是想通过对比的方式找出问题所在。

结果一看, .config_old 有8000行, .config_new 有4000行,用diff一比发现一大堆不同,如果研究每一个diff那今晚不用睡了。于是决定暴力地使用二分查找法:每次用 .config_old 替换掉 .config_new 一半的配置条目,看编译后能否成功断点。

经过若干次查找后发现问题出在 Performance monitoring 里面,怀疑是以下几行出了问题:

CONFIG_RELOCATABLE=y
CONFIG_RANDOMIZE_BASE=y
CONFIG_X86_NEED_RELOCS=y
CONFIG_PHYSICAL_ALIGN=0x1000000
CONFIG_RANDOMIZE_MEMORY=y
CONFIG_RANDOMIZE_MEMORY_PHYSICAL_PADDING=0xa

是否是因为ASLR的原因可能会导致gdb无法断到正确的位置?Google一下发现这篇文章: https://www.phoronix.com/scan.php?page=news_item&px=Linux-4.8-ASLR-Kernel-Mem-Sects ,说是4.8引进了 CONFIG_RANDOMIZE_MEMORY 的新特性:

randomizing the virtual address space of kernel memory sections, the goal is to mitigate predictable memory locations.

于是利用 make menuconfig把 Processor type and features -> Randomize the kernel memory sections 关了,重新编译后发现依然无法断点。干脆把其父级选项 Randomize the address of the kernel image (KASLR) 也关了,这时终于好了。此时查看 .config 发现少了以下两行配置:

CONFIG_X86_NEED_RELOCS=y
CONFIG_RANDOMIZE_MEMORY=y

个人猜测是内存的重新布局导致无法成功断点。虽然开48核编译kernel每次只需几分钟,但前后调试还是花费了两个小时,蛋疼。

调试kernel

通过QEMU跑一个VM来加载kernel,同时启动gdbserver提供调试信息。在宿主机中通过连接该server来进行调试。命令为:

sudo qemu-system-x86_64 -m 2048 -kernel /home/binss/work/GDB-Kernel/arch/x86/boot/bzImage -initrd ~/work/initrd.img-4.4.0-66-generic -gdb tcp::8889 -nographic -serial mon:stdio -append 'console=ttyS0' -S --enable-kvm

其中kernel用来指定kernel的镜像文件,initrd用来指定initramfs,gdb用于启动gdbserver并指定监听的端口(也可以用-s来监听1234端口), -nographic -serial mon:stdio -append 'console=ttyS0' 用来指将输出重定向到当前终端,便于观察kernel运行时的输出。S表示在开始的时候停止直到通过gdb输入c才继续运行。

在运行过程中随时可以通过Ctrl-A + C 来切换到qemu monitor进行操作(重复操作退出qemu monitor),如输入quit可以结束当前VM。

启动后,在另一个 shell 中cd到编译kernel的目录下,启动gdb,依次执行以下命令:

add-auto-load-safe-path /home/binss/work/GDB-Kernel/
file /home/binss/work/GDB-Kernel/vmlinux
directory /home/binss/work/GDB-Kernel
target remote:8889

这里从当前目录的vmlinux(带有符号信息的kernel,巨达几百M)中加载符号表。也可以把以上命令保存到当前目录( /home/binss/work/GDB-Kernel/ )的.gdbinit中,然后在 ~/.gdbinit 中添加:

add-auto-load-safe-path /home/binss/work/GDB-Kernel/.gdbinit

这样在gdb启动时就会自动执行以上命令。

然后我们就能够通过函数名进行断点了,比如断在入口:

hbreak start_kernel
c

注意对于QEMU模拟的VM,可以使用break,但对于KVM模拟的VM,需要使用hbreak。

挂载磁盘

前面的指令拉起的VM会挂在initramfs,因为没有指定要挂载的磁盘,可以通过hda挂载已有磁盘并配置root参数,从而成功进入某个虚拟机:

sudo qemu-system-x86_64 -m 2048 -kernel /home/binss/work/KVM-Learning/arch/x86/boot/bzImage -initrd ~/work/initrd.img-4.4.0-66-generic -hda myvm2.img -gdb tcp::8889 -nographic -serial mon:stdio -append 'root=/dev/sda1 console=ttyS0' --enable-kvm

当然为了加强鲁棒性,建议使用UUID来指定root设备,UUID可以在进入系统后查询/boot/grub/grub.cfg得到。

sudo qemu-system-x86_64 -m 2048 -kernel /home/binss/work/KVM-Learning/arch/x86/boot/bzImage -initrd ~/work/initrd.img-4.4.0-66-generic -hda myvm2.img -gdb tcp::8889 -nographic -serial mon:stdio -append 'root=UUID=02cf5ccd-f57f-4b25-b923-add3adb5d6c3 console=ttyS0' --enable-kvm

调试模块

对于内核模块,我们同样能够通过虚拟机的方式对其进行GDB。首先需要确保模块已被加载,对于自行编译的模块,可以通过scp等方式将文件发到guest中,通过insmod进行安装。注意需要保证是在当前kerenl的目录下编译模块,确保它们的版本相同。

然后需要定位模块地址(可能没有data和bss):

sudo cat /sys/module/kvm/sections/.text
sudo cat /sys/module/kvm/sections/.data
sudo cat /sys/module/kvm/sections/.bss

结果:

[email protected]
:~$ sudo cat /sys/module/kvm/sections/.text 0xffffffffa00e4000[email protected]:~$ sudo cat /sys/module/kvm/sections/.data 0xffffffffa0143000 [email protected]

:~$ sudo cat /sys/module/kvm/sections/.bss 0xffffffffa0152140

然后在host的GDB中用这些地址加载模块:

(gdb) add-symbol-file ~/work/GDB-Kernel/arch/x86/kvm/kvm.ko 0xffffffffa00e4000 -s .data 0xffffffffa0143000 -s .bss 0xffffffffa0152140
add symbol table from file "/home/binss/work/GDB-Kernel/arch/x86/kvm/kvm.ko" at
    .text_addr = 0xffffffffa00e4000
    .data_addr = 0xffffffffa0143000
    .bss_addr = 0xffffffffa0152140
(y or n) y
Reading symbols from /home/binss/work/GDB-Kernel/arch/x86/kvm/kvm.ko...done.

用同样的方式加载kvm-intel的符号信息:

sudo cat /sys/module/kvm_intel/sections/.text
sudo cat /sys/module/kvm_intel/sections/.data
sudo cat /sys/module/kvm_intel/sections/.bss

结果:

[email protected]
:~$ sudo cat /sys/module/kvm_intel/sections/.text 0xffffffffa01a3000[email protected]:~$ sudo cat /sys/module/kvm_intel/sections/.data 0xffffffffa01cb000 [email protected]

:~$ sudo cat /sys/module/kvm_intel/sections/.bss 0xffffffffa01cbec0

加载:

(gdb) add-symbol-file ~/work/GDB-Kernel/arch/x86/kvm/kvm-intel.ko 0xffffffffa01a3000 -s .data 0xffffffffa01cb000 -s .bss 0xffffffffa01cbec0
add symbol table from file "/home/binss/work/GDB-Kernel/arch/x86/kvm/kvm-intel.ko" at
    .text_addr = 0xffffffffa01a3000
    .data_addr = 0xffffffffa01cb000
    .bss_addr = 0xffffffffa01cbec0
(y or n) y
Reading symbols from /home/binss/work/GDB-Kernel/arch/x86/kvm/kvm-intel.ko...done.

然后打断点:

(gdb) hb vcpu_enter_guest
Hardware assisted breakpoint 1 at 0xffffffffa0103d37: file ./arch/x86/include/asm/processor.h, line 482.
(gdb) hb vmx_vcpu_run
Hardware assisted breakpoint 2 at 0xffffffffa01b30e0: file arch/x86/kvm/vmx.c, line 8798.
(gdb) c
Continuing.

然后就可以进行调试了。在VM中运行:

qemu-img create -f qcow2 mytest.img 5G
sudo qemu-system-x86_64 -cpu host -hda mytest.img -boot c -nographic -serial mon:stdio -vnc :1 -smp 1 -m 2048 --enable-kvm

回到gdb:

Thread 2 hit Breakpoint 1, vcpu_run (vcpu=<optimized out>)
    at /home/binss/work/GDB-Kernel/arch/x86/kvm/x86.c:6788
6788                            r = vcpu_enter_guest(vcpu);
(gdb) p vcpu
$1 = <optimized out>
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, vmx_vcpu_run (vcpu=0xffff8800778a0000)
    at /home/binss/work/GDB-Kernel/arch/x86/kvm/vmx.c:8798
8798    {
(gdb) p vcpu
$5 = (struct kvm_vcpu *) 0xffff8800778a0000
(gdb) n
8799            struct vcpu_vmx *vmx = to_vmx(vcpu);
(gdb) n
8803            if (unlikely(!cpu_has_virtual_nmis() && vmx->soft_vnmi_blocked))
(gdb) p vmx
$6 = (struct vcpu_vmx *) 0xffff8800778a0000

缺陷在于编译kernel时强制采用了 -O2 进行编译,导致一些值被优化后显示为 <optimized out> ,可以考虑反汇编。

参考

http://stackoverflow.com/questions/11408041/how-to-debug-the-linux-kernel-with-gdb-and-qemu

https://wiki.ubuntu.com/Kernel/KernelDebuggingTricks

http://www.elinux.org/Debugging_The_Linux_Kernel_Using_Gdb

https://www.phoronix.com/scan.php?page=news_item&px=Linux-4.8-ASLR-Kernel-Mem-Sects


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

火球

火球

张传波 / 2012-2 / 39.80元

《火球:UML大战需求分析》融合UML、非UML、需求分析及需求管理等各方面的知识,帮助读者解决UML业界问题、需求分析及需求管理问题。全书主要介绍UML的基本语法、面向对象的分析方法、应用UML进行需求分析的最佳实践及软件需求管理的最佳实践四个方面的内容。 《火球:UML大战需求分析》各章以问题为引子,通过案例、练习、思考和分析等,由浅入深地逐步介绍UML综合应用的知识。《火球:UML大战......一起来看看 《火球》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具