GDB远程调试Linux内核遇到的bug

在用QEMU + GDB 调试Linux内核时,遇到一个gdb的bug:“Remote 'g' packet reply is too long” ,记录一下。

1. 实验环境

1. qemu 版本:

luzeshu@localhost:~$ qemu-system-x86_64 --version QEMU emulator version 2.1.2 (Debian 1:2.1+dfsg-12+deb8u6), Copyright (c) 2003-2008 Fabrice Bellard

2. gdb版本:

luzeshu@localhost:~$ gdb --version GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1 Copyright (C) 2014 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <>. Find the GDB manual and other documentation resources online at: <>. For help, type "help". Type "apropos word" to search for commands related to "word".

3. 装有linux内核与grub的镜像 fd.img Linux内核版本:3.0.0 grub版本:grub-2.02~beta3

2. 目标指令: lret

出现这次bug的指令是在linux内核启动时,从32位兼容模式进入64位长模式时的一条指令。 源代码在 linux-3.0.0/arch/x86/boot/compressed/head_64.S 里面:

37 __HEAD 38 .code32 39 ENTRY(startup_32) 40 +---111 lines: cld------------------------------------------------------------------------------------------------- 151 /* Enable Long mode in EFER (Extended Feature Enable Register) */ 152 movl $MSR_EFER, %ecx 153 rdmsr 154 btsl $_EFER_LME, %eax 155 wrmsr 156 157 /* 158 * Setup for the jump to 64bit mode 159 * 160 * When the jump is performend we will be in long mode but 161 * in 32bit compatibility mode with EFER.LME = 1, CS.L = 0, CS.D = 1 162 * (and in turn EFER.LMA = 1). To jump into 64bit mode we use 163 * the new gdt/idt that has __KERNEL_CS with CS.L = 1. 164 * We place all of the values on our mini stack so lret can 165 * used to perform that far jump. 166 */ 167 pushl $__KERNEL_CS 168 leal startup_64(%ebp), %eax 169 pushl %eax 170 171 /* Enter paged protected Mode, activating Long Mode */ 172 movl $(X86_CR0_PG | X86_CR0_PE), %eax /* Enable Paging and Protected mode */ 173 movl %eax, %cr0 174 175 /* Jump from 32bit compatibility mode into 64bit mode. */ 176 lret 177 ENDPROC(startup_32)

首先把EFER寄存器的MSR值(0xC0000080)放到ecx,给EFER设置LME,启用长模式。 但此时CS.L = 0,CS.D = 1(读取自GDT的CS表项,装载到CS寄存器16位以上的隐藏位中,这些隐藏位只作为CPU内部使用,包括GDT表项的base地址其实都装在这里面。参考 《segment 寄存器的真实结构》,CPU 处于32位的兼容模式。

接下来把新的CS值和EIP值压栈(模拟lcall指令,以成功执行lret指令)。

此时,如果执行了lret指令,那么CPU就会从栈顶取出CS和EIP,跳转到新的指令位置。同时因为新的CS.L = 1,CPU会从32位兼容模式进入64位模式。 那么gdb出现的bug就是在该指令执行之后。

3. 实验步骤

step1. export DISPLAY=:0.0 设置 X server

step2. “qemu-system-x86_64 fd.img -s -S” (-s 等同于 -gdb tcp::1234) 此时会把qemu界面(作为一个X client)发送到X server对应的桌面环境。 启动qemu虚拟机,并挂起在CPU复位后的状态,停在F000:FFF0这一条指令,仍未进入BIOS芯片初始程序。 开启gdb tcp远程调试的监听端口,等待gdb连接进行远程调试。

step3. 启动gdb,输入“target remote :1234” 此时,如图3-1,gdb 远程连接上qemu,并且停在CPU的初始状态(F000:FFF0)

GDB远程调试Linux内核遇到的bug

<center>图3-1</center>

step4. 打断点 “break *0x10000ed” 这是上面lret指令的地址。 (声明:grub可以用linux16(从16位实模式启动内核)、linux(从32位保护模式启动内核)两条命令装载内核,这里只考虑从保护模式启动的情况,而且linux内核版本不同该指令装载地址可能都会出现偏差。)

step5. gdb 按“c” 继续执行。 此时qemu界面会停在grub shell,依次输入“linux /boot/bzImage”、“boot”启动linux内核。启动内核后,执行到0x10000ed 这一行中断。

step6. gdb “按ni” 下一条指令 出现图3-2的错误。

GDB远程调试Linux内核遇到的bug

<center>图3-2</center>

4. 原因与方案

通过搜索,原因是gdb在远程调试时,因为寄存器大小的切换,导致gdb出现的bug。

那么对上面的解释可能就是,lret指令,执行后,CPU从32位兼容模式进入长模式,导致传输报文中的寄存器大小发生了变化。
(声明:这里博主未深入探究GDB源码、也未探究GDB远程调试协议,原因来自搜索,解释来自联系)

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/9e29381cb826a8f0e56a8a394cbda16e.html