在用QEMU + GDB 調試Linux內核時,遇到一個gdb的bug:“Remote 'g' packet reply is too long” ,記錄一下。
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
出現這次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就是在該指令執行之後。
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>
通過搜索,原因是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
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,解決了。
最後一點,執行完lret切換到長模式之後,需要通過“set architecture i386:x86-64:intel”給gdb設置成64位。如果CPU進入長模式,而GDB沒有跟著設置,顯示的信息都是錯亂的。 這裡猜想是gdb所處的模式(32位或64位)對報文數據解讀出的差錯。
GDB調試程序用法 http://www.linuxidc.com/Linux/2013-06/86044.htm
GDB+GDBserver無源碼調試Android 動態鏈接庫的技巧 http://www.linuxidc.com/Linux/2013-06/85936.htm
使用hello-gl2建立ndk-GDB環境(有源碼和無源碼調試環境) http://www.linuxidc.com/Linux/2013-06/85935.htm
在Ubuntu上用GDB調試printf源碼 http://www.linuxidc.com/Linux/2013-03/80346.htm
GDB調試命令 http://www.linuxidc.com/Linux/2017-01/139028.htm
強大的C/C++ 程序調試工具GDB http://www.linuxidc.com/Linux/2016-09/135171.htm
Linux GDB調試 詳述 http://www.linuxidc.com/Linux/2016-11/137505.htm
使用GDB命令行調試器調試C/C++程序 http://www.linuxidc.com/Linux/2014-11/109845.htm
GDB調試命令總結 http://www.linuxidc.com/Linux/2016-08/133988.htm
GDB調試工具入門 http://www.linuxidc.com/Linux/2016-09/135168.htm
GDB 的詳細介紹:請點這裡
GDB 的下載地址:請點這裡