學習作業系統原理最好的方法是自己寫一個簡單的作業系統。
寫程式不免需要除錯,寫不同的程式除錯方式也不同。如果做應用軟體開發,相應的程式除錯方式是建立在有作業系統支援的基礎上的。而我們現在是要開發作業系統,如何除錯作業系統的程式呢?如果作業系統程式直接跑在真機上或虛擬機器上(比如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