Gdb远程调试Linux内核遇到的Bug
来源:互联网 发布:cisco 端口镜像 编辑:程序博客网 时间:2024/06/06 19:17
在用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)
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的错误。
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远程调试Linux内核遇到的Bug
- Gdb远程调试Linux内核遇到的Bug
- Gdb远程调试Linux内核遇到的Bug
- gdb qemu调试linux kernel并修改远程连接的bug
- Linux的GDB远程调试的实现
- Linux的GDB远程调试的实现
- gdb调试Linux内核
- linux下的GDB远程调试
- GDB 远程调试Linux (CentOS)
- 使用gdb调试linux内核
- 使用gdb调试linux内核
- linux内核调试gdb + KGDB
- 嵌入式Linux系统的GDB远程调试的实现
- 嵌入式Linux的GDB远程调试的实现
- 嵌入式Linux的GDB远程调试的实现
- 嵌入式Linux的GDB远程调试的实现
- 嵌入式Linux的GDB远程调试的实现
- 嵌入式Linux系统的GDB远程调试的实现
- upload 上传类
- Android_三种菜单介绍
- MySQL中函数CONCAT及GROUP_CONCAT
- JSP页面上实现权限树回显
- Ubuntu16.04LTS 安装搜狗输入法
- Gdb远程调试Linux内核遇到的Bug
- Angular.js 的一些学习资源
- HTML5 脚本编程之:跨文档消息传递&历史状态管理
- sql返回插入的主键ID
- linux 环境下头文件声明函数后使用makefile编译文件
- 安卓ListView详解
- Photoshop学习总结
- Java关键字static、final使用总结
- 【程序50】 题目:有五个学生,每个学生有3门课的成绩,从键盘输入 以上数据(包括学生号,姓名,三门课成绩),计算出平均 成绩,将原有的数据和计算出的平均分数存放在磁盘文件"stud"中。