1. 程式人生 > >自己動手實現作業系統載入程式(OS bootloader)——藉助QEMU/GDB/losetup/dd等工具

自己動手實現作業系統載入程式(OS bootloader)——藉助QEMU/GDB/losetup/dd等工具

    載入程式可以認為是PC加電啟動後執行的第一段程式碼,它是一段長度為512位元組的16位運行於真實模式的程式碼。事實上,機器啟動後會首先執行0xFFFF0處(也有的資料說是0xFFFFFFF0,BIOS這塊我也不熟:-( )ROM中的BIOS程式碼,之後會跳轉到0x07C00處執行載入程式。

        1,首先給出一段完整的示例程式碼,此程式碼只為說明載入程式的執行流程,不具有載入實際作業系統的功能,只是在螢幕上列印一段資訊。

  1. #define BOOTSEG 0x07C0
  2.           .code16  
  3.           .section ".bstext""ax"
  4.           .global bootsect_start  
  5.   bootsect_start:  
  6.           # Normalize the start address
  7.           ljmp    $BOOTSEG, $start2  
  8.   start2:  
  9.           movw    %cs, %ax  
  10.           movw    %ax, %ds  
  11.           movw    %ax, %es  
  12.           movw    %ax, %ss  
  13.           xorw    %sp, %sp  
  14.           sti  
  15.           cld  
  16.           movw    $bugger_off_msg, %si  
  17.   msg_loop:  
  18.           lodsb  
  19.           andb    %al, %al  
  20.           jz      bs_die  
  21.           movb    $0xe, %ah  
  22.           movw    $7, %bx  
  23.           int     $0x10  
  24.           jmp     msg_loop  
  25.   bs_die:  
  26.           # Allow the user to press a key, then reboot
  27.           xorw    %ax, %ax  
  28.           int     $0x16  
  29.           int     $0x19  
  30.           # int 0x19 should never return.  In case it does anyway,
  31.           # invoke the BIOS reset code...
  32.           ljmp    $0xf000,$0xfff0  
  33.   bugger_off_msg:  
  34.           .ascii  "Hello Boot!\r\n"
  35.           .ascii  "by harvey\r\n"
  36.           .ascii  "\n"
  37.           .byte   0  
  38.           .org 510  
  39.           .word 0xAA55  
                這段程式碼有幾個地方需要注意:

                1).code16偽指令指示彙編器將此段程式碼彙編成16位程式碼。

                2)ljmp    $BOOTSEG, $start2指令中,BOOTSEG定義為0x07C0,並假設標號start2在所在程式碼段中的偏移為S。我們知道真實模式地址模式為:段基址*16+偏移,那麼這條ljmp指令執行後,控制會跳轉到0x7C00+S處。又因為整個引導程式碼在之前會被載入到0x7C00處,所以此時控制“正好”跳轉到標號start2處程式碼。

                3)程式碼末端的偽指令.org 510,將位置計數器(location counter)設定為510,那麼緊跟其後的0xAA55就被設定在第511,512位元組。0xAA55是BIOS識別並載入載入程式的標誌。

        2,編譯連結此程式

               1)用as命令彙編生成目標檔案。

                       as -gstabs -o boot.o boot.S

               2)用ld命令連結生成可執行檔案。

                       ld -o boot boot.o -Tboot.ld

                       boot.ld為連結指令碼,內容如下:

  1. OUTPUT_FORMAT("elf32-i386""elf32-i386""elf32-i386")  
  2. OUTPUT_ARCH(i386)  
  3. ENTRY(bootsect_start)  
  4. SECTIONS  
  5. {  
  6.     . = 0;  
  7.     .boot : {*(.bstext)}  
  8.     . = ASSERT(. <= 512, "Boot too big!");  
  9. }     

               此指令碼指示ld將目標檔案boot.o中的.text段連結拷貝到可執行檔案boot中的.boot段,並且.boot段的起始VMA地址為0。.boot程式碼段就是我們需要的載入程式程式碼。更多連結指令碼語法參考http://sourceware.org/binutils/docs/ld/index.html

       3,製作引導軟盤映象

              1)用dd命令新建軟盤映象flp.img。

                      dd if=/dev/zero of=flp.img bs=512 count=2880

              2)用losetup命令將flp.img與loop裝置關聯,這樣我們可以通過/dev/loop3裝置,像操作真實軟盤樣操作flp.img檔案。

                      losetup /dev/loop0 flp.img

              3)將可執行檔案boot中的引導程式碼寫入flp.img的第一個扇區。首先我們要確定.boot段在可執行檔案boot中的位置,注意此位置不是指.boot段的VMA地址,而是指其儲存在磁碟檔案boot中的物理位置,我們用objdump命令檢視:

                      objdump -h boot

                      輸出如下:

                      從File off欄可知.boot段位於距boot檔案頭0x00001000處。然後用dd命令將.boot段寫入flp.img的第一個扇區。

                      dd if=boot ibs=512 skip=8 of=/dev/loop0 obs=512 seek=0 count=1

                      其中skip * ibs = 0x00001000為待寫資料,即.boot段,在輸入原始檔,即boot檔案中的偏移距離,seek * obs = 0為待寫資料將要被寫入輸出目標檔案,即flp.img檔案的起始位置,即從flp.img檔案頭位元組開始寫入資料,count*obs=512為待寫資料的長度。

                      到這裡,引導軟盤映象準備好了。關於dd,losetup,objdump命令更多資訊可藉助man命令,也可參考我前一篇文章

        4,藉助QEMU從引導軟盤映象啟動系統。

                執行如下命令啟動QEMU。

                qemu -boot order=a -fda /dev/loop0

                此時,我們應該可以在QEMU模擬器的視窗中看到Hello Boot!字樣。


                QEMU也提供單步除錯功能。配合GDB,可以方便的除錯載入程式。先用如下命令啟動QEMU。

                qemu -s -S -boot order=a -fda /dev/loop0

                其中-s -S選項與gdb除錯有關,執行此命令後QEMU模擬器會停止並等待gdb傳送單步執行命令。更多QEMU資訊可參考http://qemu.weilnetz.de/qemu-doc.html

                在另一個終端呼叫gdb命令進入gdb命令列,依次輸入以下命令。