自己動手實現作業系統載入程式(OS bootloader)——藉助QEMU/GDB/losetup/dd等工具
1,首先給出一段完整的示例程式碼,此程式碼只為說明載入程式的執行流程,不具有載入實際作業系統的功能,只是在螢幕上列印一段資訊。
- #define BOOTSEG 0x07C0
- .code16
- .section ".bstext", "ax"
- .global bootsect_start
- bootsect_start:
- # Normalize the start address
- ljmp $BOOTSEG, $start2
- start2:
- movw %cs, %ax
- movw %ax, %ds
- movw %ax, %es
- movw %ax, %ss
- xorw %sp, %sp
- sti
- cld
- movw $bugger_off_msg, %si
- msg_loop:
- lodsb
- andb %al, %al
- jz bs_die
- movb $0xe, %ah
- movw $7, %bx
- int $0x10
- jmp msg_loop
- bs_die:
- # Allow the user to press a key, then reboot
- xorw %ax, %ax
- int $0x16
- int $0x19
- # int 0x19 should never return. In case it does anyway,
- # invoke the BIOS reset code...
- ljmp $0xf000,$0xfff0
- bugger_off_msg:
- .ascii "Hello Boot!\r\n"
- .ascii "by harvey\r\n"
- .ascii "\n"
- .byte 0
- .org 510
- .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為連結指令碼,內容如下:
- OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
- OUTPUT_ARCH(i386)
- ENTRY(bootsect_start)
- SECTIONS
- {
- . = 0;
- .boot : {*(.bstext)}
- . = ASSERT(. <= 512, "Boot too big!");
- }
此指令碼指示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命令列,依次輸入以下命令。