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和目标 ...
随机推荐
- Android---Parcelable包装类的作用
android提供了一种新的类型:Parcel.本类被用作封装数据的容器,封装后的数据可以通过Intent或IPC传递. 除了基本类型以外,只有实现了Parcelable接口的类才能被放入Parcel ...
- android里uri和url的区别
URI :是从虚拟根路径开始的 URI,是uniform resource identifier URL:是整个链接 URI,是uniform resource location uri:file: ...
- C语言写的俄罗斯方块
源:C语言写的俄罗斯方块 2014年最后一天, 任天堂将风靡全球30年的经典游戏<<俄罗斯方块>>下架. 作为全球最畅销的游戏, 其移植版本遍布各个平台. 下面这个是我去年在5 ...
- 丢手帕问题as3版
N个孩子围成一圈报数,报到M的退出,剩下的再从1继续报数,报到M的再退出,一直持续到只剩一个人,问剩下的是哪个? package { import flash.display.Sprite; publ ...
- java中try 与catch的使用
(2011-10-08 17:08:43) 转载▼ 标签: 杂谈 分类: Java try{//代码区}catch(Exception e){//异常处理}代码区如果有错误,就会返回所写异常的处理. ...
- windows 下nginx 虚拟主机搭建
需要在 nginx.conf里面引入刚才配置的那个文件 第一步 加东西 http的节点里面加上 一定要注意的是:必须以 ; 结尾 include D:/phpen/nginx-1.3.6/co ...
- iOS学习笔记1--在xcode6以上的版本中不使用storyboard以及部分控件使用
首先建立一个iOS新工程,删除工程自动建立的main.storyboard以及xib文件,并且在info.plist上删除这两个选项 然后在项目配置中将maninterface设置为空,将launch ...
- POJ 3187 Backward Digit Sums
暴力DFS+验证. 验证如果是暴力检验可能复杂度会太高,事实上可以o(1)进行,这个可以o(n*n)dp预处理. #include<cstdio> #include<cstring& ...
- python web开发基本概念
参考了廖雪峰的Python博客. web请求顺序: 浏览器发送一个http请求 服务器收到请求后,生成一个html文档. 服务器将html文档作为http相应的body发送给浏览器 浏览器收到http ...
- WCF必须使用证书验证吗
你说的 ASP.NET Web Service在消息头里加个字段,服务端做验证,这个是可以的,但是无法保证传输的用户名和密码是加密安全的. 要求使用证书,也是强制服务器端,这里涉及到服务器身份鉴别的问 ...