歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux內核

GDB遠程調試Linux內核遇到的bug

在用QEMU + GDB 調試Linux內核時,遇到一個gdb的bug:“Remote 'g' packet reply is too long” ,記錄一下。

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)

<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>


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調試程序用法 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 的下載地址:請點這裡

Copyright © Linux教程網 All Rights Reserved