本作品采用知识共享署名 4.0 国际许可协议进行许可。转载联系作者并保留声明头部与原文链接https://luzeshu.com/blog/gdb-bug

本博客同步在http://www.cnblogs.com/papertree/p/6298774.html


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

(声明:不敢保证这是bug,有相关争论认为不应当在gdb client打patch,而应当在gdb server打patch,但是该博客提及的patch能够解决遇到的问题并且暂未发现其他问题。如果要在gdb server打patch,那么博主遇到的场景中,应当修改qemu内置的gdb server,并且编译qemu的源码。)

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 <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, %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)

图3-1

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

图3-2


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.c
Hunk #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远程调试Linux内核遇到的Bug的更多相关文章

  1. Visual Studio 2015中使用gdb远程调试linux程序

    VS的debug功能非常强大,相比而言linux上的图形化调试一直不是很好用. 如果可以使用VS来调试linux程序,应该是一件比较愉快的事情. 这在2015中变得可能,因为从2015开始VS支持An ...

  2. GDB 远程调试Linux (CentOS)

    1.引用: https://blogs.msdn.microsoft.com/vcblog/2016/03/30/visual-c-for-linux-development/ 注意安装gdbserv ...

  3. 在qemu环境中用gdb调试Linux内核

    简介 对用户态进程,利用gdb调试代码是很方便的手段.而对于内核态的问题,可以利用crash等工具基于coredump文件进行调试.其实我们也可以利用一些手段对Linux内核代码进行gdb调试,qem ...

  4. 用qemu+gdb tcp server+CDT调试linux内核启动-起步

    用qemu+gdb tcp server+CDT调试linux内核启动-起步 说明: 环境信息与 用virtualbox+模拟串口+CDT调试linux内核 TCP IP协议栈-起步 提到的一样,并且 ...

  5. 【转】TI-Davinci开发系列之六CCS5.2调试Linux内核

    上转博文<TI-Davinci开发系列之五CCS5.2使用gdbserver远程调试应用程序> 使用CCS5.2远程调试内核时,只需导入Linux内核源码,而不需要编译内核,也就不会用到交 ...

  6. 使用 ftrace 调试 Linux 内核【转】

    转自:http://blog.csdn.net/adaptiver/article/details/7930646 使用 ftrace 调试 Linux 内核,第 1 部分 http://blog.c ...

  7. 用 kGDB 调试 Linux 内核

    简介 这个文档记录了用kGDB调试Linux内核的全过程,都是在前人工作基础上的一些总结.以下操作都是基于特定板子来进行,但是大部分都能应用于其他平台. 要使用KGDB来调试内核,首先需要修改conf ...

  8. 使用QEMU调试Linux内核代码

    http://blog.chinaunix.net/uid-20729583-id-1884617.html http://www.linuxidc.com/Linux/2014-08/105510. ...

  9. GDB+GDBServer调试Linux应用程序

    参考:http://blog.csdn.net/shanghaiqianlun/article/details/7820401 一.gdb+gdbserver总体介绍 远程调试环境由宿主机GDB和目标 ...

随机推荐

  1. Lua学习笔记4. coroutine协同程序和文件I/O、错误处理

    Lua学习笔记4. coroutine协同程序和文件I/O.错误处理 coroutine Lua 的协同程序coroutine和线程比较类似,有独立的堆栈.局部变量.独立的指针指令,同时又能共享全局变 ...

  2. CG之基本光照模型计算公式

    在一个基本模型里,一个物体表面的颜色是由放射(emissive).环境反射(ambient).漫反射(diffuse)和镜面反射(specular)等光照作用的总和.每种光照作用取决于表面材质的性质( ...

  3. 解决airserver在Windows下安装失败的问题

    airserver 可以将iphone 实时投影到mac 和 pc.在mac上安装非常简单.但是在Windows上安装时会有很多问题.之前我电脑安装很快就完成了(因为我之前已经在不知情的前提先事先装过 ...

  4. mac和xcode快捷键

    mac中: 1.怎么建立快捷方式 首先 按住option+command  ,在用鼠标拖动目标文件到指定地点,先松开鼠标,然后在松开键盘

  5. JdbcTemplate的主要用法

    JdbcTemplate主要提供以下五类方法: execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句: update方法及batchUpdate方法:update方法用于执行新增.修 ...

  6. 如何在我自己的web 项目的jsp页面中添加链接,直接让别人通过内网在我的电脑上下载文件

    今天接到一个任务,将昨天年会的视频,音频,图片等放在公司自己的服务器上,使连接同一个路由器的(即同一个内网)的同事可以通过内网下载视频(通过内网下载,可以提高下载速度). 备注:本次用的是tomcat ...

  7. Memcached源码分析之请求处理(状态机)

    作者:Calix 一)上文 在上一篇线程模型的分析中,我们知道,worker线程和主线程都调用了同一个函数,conn_new进行事件监听,并返回conn结构体对象.最终有事件到达时,调用同一个函数ev ...

  8. 横向滚动视图scroll-into-view不起作用

    横向视图scroll-into-view指定的id为hpink,但是效果图中显示的还是第1个view(未达到效果); 纵向视图scroll-into-view指定的id为yellowgreen,效果图 ...

  9. Set笔记

    Set 继承自Collection的一个接口,特点是:无序,不可重复.注意啊!!只有Collection实现了迭代器!也就是说Map是没有实现迭代器的,需要keySet,values,entrySet ...

  10. CSS如何让DIV的宽度随内容的变化

    [css]CSS如何让DIV的宽度随内容的变化 让div根据内容改变大小 div{ width:auto; display:inline-block !important; display:inlin ...