现在的位置: 首页 > 操作系统 > 正文

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

2020年02月10日 操作系统 ⁄ 共 5400字 ⁄ 字号 评论关闭

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

1. 实验环境

1. qemu 版本:

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

2. gdb版本:

luzeshu@localhost:~$ gdb --versionGNU gdb (Debian 7.7.1+dfsg-5) 7.7.1Copyright (C) 2014 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>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:<http://www.gnu.org/software/gdb/bugs/>.Find the GDB manual and other documentation resources online at:<http://www.gnu.org/software/gdb/documentation/>.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, %ecx153 rdmsr154 btsl $_EFER_LME, %eax155 wrmsr156157 /*158 * Setup for the jump to 64bit mode159 *160 * When the jump is performend we will be in long mode but161 * in 32bit compatibility mode with EFER.LME = 1, CS.L = 0, CS.D = 1162 * (and in turn EFER.LMA = 1). To jump into 64bit mode we use163 * 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 can165 * used to perform that far jump.166 */167 pushl $__KERNEL_CS168 leal startup_64(%ebp), %eax169 pushl %eax170171 /* Enter paged protected Mode, activating Long Mode */172 movl $(X86_CR0_PG | X86_CR0_PE), %eax /* Enable Paging and Protected mode */173 movl %eax, %cr0174175 /* Jump from 32bit compatibility mode into 64bit mode. */176 lret177 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)

<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的错误。

<center>图3-2</center>


4. 原因与方案

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

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

到gdb的官方站点:https://www.gnu.org/software/gdb/current/(gdb代码托管在这里:git clone git://sourceware.org/git/binutils-gdb.git )找到它的Bug database:https://sourceware.org/bugzilla/搜索到一个相关的patch:https://sourceware.org/bugzilla/show_bug.cgi?id=13984

--- remote.c 2015-02-20 19:11:44.000000000 +0200+++ remote-fixed.c 2015-08-12 20:00:14.966043900 +0300@@ -6154,8 +6154,20 @@ buf_len = strlen (rs->buf); /* Further sanity checks, with knowledge of the architecture. */- if (buf_len > 2 * rsa->sizeof_g_packet)- error (_("Remote 'g' packet reply is too long: %s"), rs->buf);+ //if (buf_len > 2 * rsa->sizeof_g_packet)+ // error (_("Remote 'g' packet reply is too long: %s"), rs->buf);++ if(buf_len > 2 * rsa->sizeof_g_packet) {+ rsa->sizeof_g_packet = buf_len;+ for(i = 0; i < gdbarch_num_regs(gdbarch); i++){+ if(rsa->regs->pnum == -1)+ continue;+ if(rsa->regs->offset >= rsa->sizeof_g_packet)+ rsa->regs->in_g_packet = 0;+ else+ rsa->regs->in_g_packet = 1;+ }+ } /* Save the size of the packet sent to us by the target. It is used as a heuristic when determining the max size of packets that the


5. 解决

1. 下载gdb 7.12版本的源码

2. 验证7.12是否存在该问题

为了不与原本安装的gdb冲突,configure时指定make install的路径 “./configure --prefix=/home/luzeshu/tools/gdb-7.12” “make” “make install” 重复上面几个step,发现7.12一样有该问题。

3. 解决 方法1: 按照上面的patch,修改源码。

方法2(版本7.9): 如果下载了7.9的源码,可以把patch保存成“fix-remote.patch”文件,直接用“patch < fix-remote.patch” 打补丁。 如果出现下面错误,或许是空格的问题,给patch命令加上 --ignore-whitespace

patching file remote.cHunk #1 FAILED at 6154.1 out of 1 hunk FAILED -- saving rejects to file remote.c.rej

方法2(版本7.12): 用7.12版本的,同样可以用上面的patch文件,不过line number要把6154改成7.12版对应的line number。

改完代码,再重新编译安装。再重复上面几个step,解决了。


6. gdb set architecture

最后一点,执行完lret切换到长模式之后,需要通过“set architecture i386:x86-64:intel”给gdb设置成64位。如果CPU进入长模式,而GDB没有跟着设置,显示的信息都是错乱的。 这里猜想是gdb所处的模式(32位或64位)对报文数据解读出的差错。

GDB调试程序用法 http://www.xuebuyuan.com/Linux/2013-06/86044.htm

GDB+GDBserver无源码调试Android 动态链接库的技巧 http://www.xuebuyuan.com/Linux/2013-06/85936.htm

使用hello-gl2建立ndk-GDB环境(有源码和无源码调试环境) http://www.xuebuyuan.com/Linux/2013-06/85935.htm

在Ubuntu上用GDB调试printf源码 http://www.xuebuyuan.com/Linux/2013-03/80346.htm

GDB调试命令 http://www.xuebuyuan.com/Linux/2017-01/139028.htm

强大的C/C++ 程序调试工具GDB http://www.xuebuyuan.com/Linux/2016-09/135171.htm

Linux GDB调试 详述 http://www.xuebuyuan.com/Linux/2016-11/137505.htm

使用GDB命令行调试器调试C/C++程序 http://www.xuebuyuan.com/Linux/2014-11/109845.htm

GDB调试命令总结 http://www.xuebuyuan.com/Linux/2016-08/133988.htm

GDB调试工具入门 http://www.xuebuyuan.com/Linux/2016-09/135168.htm

GDB 的详细介绍:请点这里GDB 的下载地址:请点这里

以上就上有关GDB远程调试Linux内核遇到的bug的全部内容,学步园全面介绍编程技术、操作系统、数据库、web前端技术等内容。

抱歉!评论已关闭.