引導程式可以認為是PC加電啟動後啟動並執行第一段代碼,它是一段長度為512位元組的16位運行於實模式的代碼。事實上,機器啟動後會首先運行0xFFFF0處(也有的資料說是0xFFFFFFF0,BIOS這塊我也不熟:-( )ROM中的BIOS代碼,之後會跳轉到0x07C00處執行引導程式。
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命令列,依次輸入以下命令。