Gdb远程调试Linux内核遇到的Bug
本作品采用知识共享署名 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的更多相关文章
- Visual Studio 2015中使用gdb远程调试linux程序
VS的debug功能非常强大,相比而言linux上的图形化调试一直不是很好用. 如果可以使用VS来调试linux程序,应该是一件比较愉快的事情. 这在2015中变得可能,因为从2015开始VS支持An ...
- GDB 远程调试Linux (CentOS)
1.引用: https://blogs.msdn.microsoft.com/vcblog/2016/03/30/visual-c-for-linux-development/ 注意安装gdbserv ...
- 在qemu环境中用gdb调试Linux内核
简介 对用户态进程,利用gdb调试代码是很方便的手段.而对于内核态的问题,可以利用crash等工具基于coredump文件进行调试.其实我们也可以利用一些手段对Linux内核代码进行gdb调试,qem ...
- 用qemu+gdb tcp server+CDT调试linux内核启动-起步
用qemu+gdb tcp server+CDT调试linux内核启动-起步 说明: 环境信息与 用virtualbox+模拟串口+CDT调试linux内核 TCP IP协议栈-起步 提到的一样,并且 ...
- 【转】TI-Davinci开发系列之六CCS5.2调试Linux内核
上转博文<TI-Davinci开发系列之五CCS5.2使用gdbserver远程调试应用程序> 使用CCS5.2远程调试内核时,只需导入Linux内核源码,而不需要编译内核,也就不会用到交 ...
- 使用 ftrace 调试 Linux 内核【转】
转自:http://blog.csdn.net/adaptiver/article/details/7930646 使用 ftrace 调试 Linux 内核,第 1 部分 http://blog.c ...
- 用 kGDB 调试 Linux 内核
简介 这个文档记录了用kGDB调试Linux内核的全过程,都是在前人工作基础上的一些总结.以下操作都是基于特定板子来进行,但是大部分都能应用于其他平台. 要使用KGDB来调试内核,首先需要修改conf ...
- 使用QEMU调试Linux内核代码
http://blog.chinaunix.net/uid-20729583-id-1884617.html http://www.linuxidc.com/Linux/2014-08/105510. ...
- GDB+GDBServer调试Linux应用程序
参考:http://blog.csdn.net/shanghaiqianlun/article/details/7820401 一.gdb+gdbserver总体介绍 远程调试环境由宿主机GDB和目标 ...
随机推荐
- USACO 1.3.2
题目链接:USACO 1.3.2 这道题有点小坑,不是算法错了,而是文件名,是barn1不是barnl,恕我眼拙,找了十五分钟... 肯定是木板的个数用的越多越好,这样可以减少空隙. 简单的贪心,将每 ...
- CvvImage类
从OpenCV 2.2.0开始,OpenCV取消了CvvImage这个类.可是今天要用到,可以自己加入到工程中. 首先,找到CvvImage的原代码.我在网上已经找到了,具体代码如下. 这是CvvIm ...
- MFC-----在MFC中使用Picture控件加载任意图片
对于刚刚接触OpenCV的童鞋来说,如何在MFC中加载并显示一张图片.应该是初期必定会碰到的问题之一.因此本文在分享这方面经验的同时,也相当于是写给自己的一份备忘录. 本文使用的是OpenCV2.1+ ...
- 伸展二叉树树(C#)
参考过好几篇关于将伸展树的代码,发现看不懂.看图能看懂原理.就尝试自己实现了下. 自顶向上的算法. using System; using System.Collections.Generic; us ...
- java如何计算程序运行时间
long startTime = System.currentTimeMillis(); //获取开始时间 doSomething(); //测试的代码段 long endTime = S ...
- 控制流之if
if语句if语句用来检验一个条件, 如果 条件为真,我们运行一块语句(称为 if-块 ), 否则 我们处理另外一块语句(称为 else-块 ). else 从句是可选的. ~~~~~~~~~~~~~~ ...
- 改变导航栏title字体的大小和颜色
方法一:自定义视图的方法 就是在导航向上添加一个titleView,可以使用一个label,再设置label的背景颜色透明,字体什么的设置就很简单了. //自定义标题视图 UILabel *title ...
- uartz Spring与Spring Task总结
Spring对Quartz作了一个封装,同时,Spring自己也提供了一个任务定时器(spring-task),现把它总结一下. 对于Quartz,我们使用的时候主要是注重两个方面,一个是定时任 ...
- 如何使用UDP进行跨网段广播(转)
源:如何使用UDP进行跨网段广播 广播域首先我们来了解一下广播域的概念.广播域是网络中能接收任一台主机发出的广播帧的所有主机集合.也就是说,如果广播域内的其中一台主机发出一个广播帧,同一广播域内所有的 ...
- jsp 获取应用目录
${pageContext.request.contextPath} // 应用名为“demo1” 则得到的是"/demo1" <script>$(documen ...