什么是Bochs?
簡單地說,Bochs是一款仿真軟件,可以用軟件的方式模擬硬件的工作。同類軟件有Qemu,仿真軟件與虛擬機(hypervisor)還不完全相同,仿真軟件是完全軟件模擬硬件,而虛擬機軟件(比如Vmware, VirtualBox)是利用主機的硬件進行工作。
Bochs的主頁地址:http://bochs.sourceforge.net/
Bochs軟件的下載地址:http://sourceforge.net/projects/bochs/files/bochs/
Bochs的使用
Bochs的使用依賴配置文件,通過配置文件指定不同的硬件,以及指定存儲介質的映像文件(BIOS的ROM文件、磁盤文件等)。
在Windows環境下安裝過Bochs后,在配置文件上右鍵菜單會出現Run, Debug的菜單選項,從而啟動運行或者調試。
Bochs調試器
Bochs的調試器命令與gdb命令十分相似,但是更加強大。簡單介紹幾條命令的使用:
1: #流程控制
2: c #continue, 繼續執行
3: s [count] #step, 單步執行count次
4: #斷點
5: vb seg:off #設置邏輯地址斷點
6: lb addr #設置物理地址斷點
7: info break #查看斷點
8: d n #刪除斷點
9: #查看內存
10: x/n[bhwg][xduotc] #查看內存
11: [bhwg] #顯示單元大小,分別代表byte, half, word, giant word
12: [xduotc] #顯示格式,分別代表hex, dec, unsigned, octal, binary, char
13: #查看寄存器
14: r #查看基本寄存器
15: sreg #查看段寄存器
更多的命令,請輸入help查看。
在Windows環境下編譯Bochs源代碼
首先說一下編譯源碼的動機,當我們安裝了Bochs之后就已經可以使用它來運行或者調試一個被仿真的系統了。 這種調試類似於gdb,調試目標是運行在Bochs之上的系統。
然而,我們知道,既然Bochs是一個開源的項目,以通過軟件的方式仿真了硬件系統,那么我們就可以通過查看Bochs的源碼來學習相關的硬件知識(比如Intel體系結構,BIOS,DMA等)了。
從上面的下載地址下載一份源代碼,解壓后,能看到vs2008/bochs.sln文件,從而打開Visual Studio項目進行編譯。
默認配置選項中沒有包含對bochsdbg的支持,因此我們需要重新運行configure程序,悲劇的是configure是Linux下面的程序,我們可以通過以下方式來達到同樣的目的:
- 1. 安裝mingw,以及msys,將msys/bin目錄添加到系統PATH環境變量中;
- 2. 修改源碼目錄下的.conf.win32_vcpp文件,添加
1: --enable-debugger --enable-disasm - 3. 打開Visual Studio的Prompt命令行,cd到源碼目錄下,運行
1: bash.exe .conf.win32_vcpp
完成以上步驟之后,就可以編譯出具有debug功能的Bochs可執行程序了。
Bochs是怎樣處理調試命令的?
我們可以在位置上設置斷點:
1: void bx_dbg_user_input_loop(void) /*dbg_main.cc*/
然后在調試窗口中輸入命令
1: r
程序會在這兩個斷點處中斷,這個bx_dbg_user_input_loop函數就是不斷接收調試命令的循環體,它會把接收到的調試命令經過lex&yacc框架進行解析,然后調用到相應的handler來處理調試請求。
這些handler都在debug.h文件中進行聲明,比如處理r命令的handler定義為
1: void bx_dbg_info_registers_command(int);
在該函數的定義處設置斷點,我們就能夠了解到Bochs是怎樣處理r這樣的調試請求的。
通過跟蹤幾個調試命令的實現,我們發現了三個重要的全局變量:
1: BOCHSAPI BX_CPU_C bx_cpu;
2: BOCHSAPI BX_MEM_C bx_mem;
3: bx_devices_c bx_devices;
分別保存着用來描述CPU、內存和外部設備的數據結構。
指令IN和OUT是如何處理的?
由於我們希望通過Bochs來學習硬件相關的內容,所以會對IN和OUT這兩條指令很感興趣,因為CPU就是通過這兩條指令與外部設備之間進行協調工作的。
我們通過嘗試,找到了下面這個函數
1: /*
2: * Write a byte of data to the IO memory address space.
3: */
4:
5: void BX_CPP_AttrRegparmN(3)
6: bx_devices_c::outp(Bit16u addr, Bit32u value, unsigned io_len)
bx_devices會在內部維護一個外設端口對應的讀和寫的handler的數組
1: struct io_handler_struct **read_port_to_handler;
2: struct io_handler_struct **write_port_to_handler;
這是兩個二維指針數組,用端口號作為下標可以找到某個端口對應的讀寫處理函數,默認會把每個handler都設置成io_write_handlers
1: /* set handlers to the default one */
2: for (i=0; i < PORTS; i++) {
3: read_port_to_handler[i] = &io_read_handlers;
4: write_port_to_handler[i] = &io_write_handlers;
5: }
通過查找函數
1: #define DEV_register_ioread_handler(b,c,d,e,f) bx_devices.register_io_read_handler(b,c,d,e,f)
2: #define DEV_register_iowrite_handler(b,c,d,e,f) bx_devices.register_io_write_handler(b,c,d,e,f)
我們可以找到哪些設備支持了自己的IO讀寫功能,以及其對應的handler。
以DMA為例,我們可以找到如下的注冊handler代碼
1: // 0000..000F
2: for (i=0x0000; i<=0x000F; i++) {
3: DEV_register_ioread_handler(this, read_handler, i, "DMA controller", 1);
4: DEV_register_iowrite_handler(this, write_handler, i, "DMA controller", 3);
5: }
6:
7: // 00080..008F
8: for (i=0x0080; i<=0x008F; i++) {
9: DEV_register_ioread_handler(this, read_handler, i, "DMA controller", 1);
10: DEV_register_iowrite_handler(this, write_handler, i, "DMA controller", 3);
11: }
12:
13: // 000C0..00DE
14: for (i=0x00C0; i<=0x00DE; i+=2) {
15: DEV_register_ioread_handler(this, read_handler, i, "DMA controller", 1);
16: DEV_register_iowrite_handler(this, write_handler, i, "DMA controller", 3);
17: }
只要在DMA模塊的read_handler和write_handler處理設置斷點,我們就可以動態地調試DMA的處理邏輯了。
經過了以上的准備工作之后,我們就可以開始調試一個具體的系統了。我是以DLX為目標進行調試的,在調試過程中,我們可以一步一步地了解到從計算機加電后執行BIOS開機自檢程序,到加載MBR,通過LILO一步一步地把Linux操作系統啟動起來的全過程,一個奇妙的旅程即將開始!
有人已經這樣做了,並且根據Bochs的代碼,出了一本書:http://www.mouseos.com/books/x86-64/index.html
