自己動手從零寫桌面作業系統GrapeOS系列教程——12.QEMU+GDB除錯

成宇佳發表於2023-03-13

學習作業系統原理最好的方法是自己寫一個簡單的作業系統。


寫程式不免需要除錯,寫不同的程式除錯方式也不同。如果做應用軟體開發,相應的程式除錯方式是建立在有作業系統支援的基礎上的。而我們現在是要開發作業系統,如何除錯作業系統的程式呢?如果作業系統程式直接跑在真機上或虛擬機器上(比如VirtualBox)是很難除錯的,所以我們在開發階段作業系統程式主要在虛擬機器QEMU上跑,因為QEMU支援除錯。當然很多事情都是有利也有弊的,QEMU雖然支援除錯,但它的執行效率比VitrualBox要低,所以我們最終的GrapeOS程式是跑在VirtalBox上的。QEMU需要結合GDB才能實現除錯,下面我們一起來學習一下。

一、QEMU除錯模式

在Windows的cmd命令列中輸入如下一行命令:

qemu-system-i386 d:\GrapeOS\VMShare\GrapeOS.img -S -s

上面這行命令比之前多了兩個引數,“-S”表示讓CPU在將要執行第一條指令前暫停,“-s”表示讓QEMU開啟自帶的GDB服務端功能,且網路埠號是1234。截圖如下:

執行上面的命令後,會彈出QEMU的視窗:

從上圖中可以看到QEMU視窗中間顯示一行文字“Guest has not initialized the display(yet).”,此時QEMU已進入除錯模式。當QEMU進入除錯模式後,就在等待GDB客戶端來連線它。當GDB客戶端連線上QEMU的GDB服務端就可以除錯了。就像我們用PowerShell連線到CentOS就可以在PowerShell中操縱CentOS一樣,此時PowerShell是客戶端,CentOS是服務端。下面我們來介紹GDB客戶端。

二、GDB除錯

GDB分為服務端和客戶端,單說GDB,一般是指GDB客戶端。GDB是Linux中的一個除錯軟體,所以我們準備在CentOS中使用它。首先我們透過PowerShell登入CentOS。

1.安裝GDB

首次使用GDB可能需要安裝一下:

yum install gdb

2.啟動GDB

敲命令gdb就執行了,如下圖:

3.GDB連線到QEMU

在GDB中輸入如下命令連線QEMU:

target remote 192.168.10.102:1234

上面這行命令中的IP地址“192.168.10.102”是我的Windows的IP地址,你需要替換成你的Windows的IP地址。截圖如下:

如上截圖所示,我們已經透過GDB連線到QEMU了。圖中倒數第二行的十六進位制數“0x0000fff0”表示CPU將要執行的指令地址。還記得前面介紹的真實模式下1M記憶體的佈局嗎?這個地址在BIOS中,是CPU執行的第一條指令所在的地址。

4.設定斷點

設定斷點是除錯必備的一個功能,比如我們在0x7c00處設定個斷點:

b *0x7c00

這樣就設定好了一個斷點。可以用同樣的方式設定多個斷點。

5.繼續執行

這個命令簡單,只有一個字母“c”,然後回車即可讓CPU繼續執行,當遇到斷點時會自動暫停。截圖如下:

6.檢視暫存器

檢視所有暫存器的命令是i r,截圖如下:

從上圖中可以看到此時CPU中很多暫存器的值,有朋友可能會有個疑問,以前學的暫存器是“ax、bx、cx……”這些,上面截圖中怎麼是“eax、ebx、ecx……”呢?原因是當年8086CPU的暫存器都是16位的,也就是“ax、bx、cx……”這些,很多講x86組合語言的資料都只講了8086下的情況。而我們現在啟動的是32位x86模擬器“qemu-system-i386”,所有通用暫存器多了一個字母“e”表示擴充套件,從16位擴充套件成了32位。這些32位通用暫存器中的低16位就是原來的16位暫存器,比如eax的低16位還是ax,ah和al仍然表示ax的高8位和低8位,其它暫存器也一樣。這就是相容,能讓舊程式在新CPU上執行。之前的16位暫存器中只有段暫存器沒有擴充套件,還是16位的,而且還增加了2個,分別是fs和gs。增加的這2個段暫存器作用和es基本一樣,之所以增加是怕在複雜的程式中出現段暫存器不夠用的情況。當資料比較多的時候GDB一般只輸出一部分,此時如果按Enter鍵還會顯示出其它一些暫存器,但我們用不上,按“q”鍵退出繼續輸出即可。

下面來看一下如何檢視單個暫存器,比如我們要檢視暫存器ax的值,輸入命令p $ax,如果想以十六進位制顯示可以輸入命令p /x $ax,截圖如下:

7.檢視記憶體

命令格式:x /nfu addr
n表示數量
f表示格式:x(hex), d(decimal), c(char)等。
u表示顯示單位:b(byte), h(halfword), w(word), g(giant, 8 bytes)。
下面我們分別演示檢視0x7c00開始的8個單位元組、8個雙位元組、8個四位元組、8個八位元組的記憶體值。截圖如下:

雖然目前看到的資料都是0,但我們以後寫上程式就不一樣了。

8.反彙編

有時候需要將機器碼反彙編成彙編程式碼方便檢視,下面我們以反彙編0x7c00開始的10個位元組為例:

disas 0x7c00,+10

截圖如下:

上面截圖中顯示的彙編程式碼是GDB預設的AT&T語法,我們可以設定改成Intel語法:

set disassembly-flavor intel

截圖如下:

需要說明一下的是這裡的反彙編結果是錯的。因為它是按照32位模式反彙編的,而我們現在還處在16位真實模式中,所以這個反彙編功能只能等後面我們進入32位模式才有用。至於反彙編16位程式碼我們會在後續教程中介紹其它方法。

9.執行下一條指令

在除錯的時候有時需要一條指令一條指令的單步執行,單步執行的命令是si

從上面的截圖可以看到,每輸入一個si回車,就會執行一條命令。每個si命令下面一行中的十六進位制數表示下一條指令的地址,可以看到地址在不斷增加,說明的確在執行指令。如果想知道每一步都執行了什麼指令,可以用下面這個命令來反彙編下一條要執行的指令:

set disassemble-next-line on

從上面截圖中可以看到每一步的指令,但這個反彙編結果也是錯的,原因和上面的一樣。
順便介紹個小技巧,如果不輸入命令直接回車會重複上一個GDB命令,就像上圖中最後兩步,什麼命令都沒有直接回車就表示重複執行si這個GDB命令。

10.退出GDB

本講最後介紹的指令是退出GDB,非常簡單,輸入q,然後再輸入y即可。 截圖如下:

退出GDB後就又回到進入GDB前的Linux命令列環境中了。

三、退出GDB後的問題

如果大家按照上面順序做實驗,退出GDB後,CPU佔用率會比較高,和上講中的情況一樣,直接關閉QEMU視窗即可。這個問題我們在下一講中解決。


本講影片版地址:https://www.bilibili.com/video/BV18G4y1P7CU/
本教程程式碼和資料:https://gitee.com/jackchengyujia/grapeos-course
GrapeOS作業系統QQ群:643474045

相關文章