下面是第二章的sample code。作為在閱讀這本書時的筆記。
這段code做的事情為,在boot sector內寫一個程式,之後把另外一個loader名為
"loader.bin"從fat12 file system裡頭找出來並且load到記憶體位置9000:0100的
地方之後jmp過去開始run。如果你無聊,loader.bin裡頭可以秀一段hello world字串。
完成了這段code以後,用它書裡頭的方法,先把這個image在linux底下mount起來
。之後因為裡面有fat12 file system,所以可以把loader.bin copy到mount的資料夾內,
這樣linux就會自動去把loader.bin加到我們的file system。umount以後,我再用windows
底下的vmware新增一個floppy的Device,把掛載的映像檔,設為我們的image,再用vmware開啟。
同時實驗後發現了用usb開機也是可行的,這樣就可以用usb開啟自己寫的bootloader了。
首先,把image從usb的MBR開始位置用winhex寫入。因為在eeepc底下,
usb開機被視為floppy disk,所以可以用int 13 , dl = 0去讀取sector。
winhex的寫入法有兩種,第一種把我們要copy的磁區先反白起來,接著ctrl+c,然後
切換到要貼上的磁碟磁區,把滑鼠游標移到第一個Byte,ctrl + b。第二種是用winhex
中的tools -> disk tools -> clone disk。就可以選擇來源檔案為我們的image,目的選
usb實體磁碟機(不能選logical disk不然會寫到partition的boot sector去)。或是你要
在linux底下用dd也是可以。
一開始所需要的必備知識就是GAS組語的語法,和FAT的格式。
GAS的語法,網路上很多可以搜尋一下,或者這個bootsector多看幾次就會了解。
至於file system可以參考我的另外一篇文章FAT系列檔案系統
/* chapter2/2/boot.S
Author: Wenbo Yang
This file is part of the source code of book "Write Operating System with
Free Software". The latest version, see
This file is licensed under the GNU General Public License; either
version 3 of the License, or (at your option) any later version. */
.code16 //因為一開機是Real mode所以是以16 bits為定址模式
.set BaseOfStack, 0x7c00 /* Stack base address, inner */
.set BaseOfLoader, 0x9000 /* Section loading address of LOADER.BIN */
.set OffsetOfLoader, 0x0100 /* Loading offset of LOADER.BIN */
.set RootDirSectors, 14 /* Root directory sector count */
.set SecNoOfRootDir, 19 /* 1st sector of root directory */
.set SecNoOfFAT1, 1 /* 1st sector of FAT1 */
.set DeltaSecNo, 17 /* BPB_(RsvdSecCnt+NumFATs*FATSz) -2 */
/* Start sector of file space =*/
.text //這邊是程式開始
//
/* Floppy header of FAT12 */
jmp LABEL_START /* Start to boot. */
nop /* nop required 因為前面是三個bytes所以還需要一個nop來湊*/
BS_OEMName: .ascii "WB. YANG" /* OEM String, 8 bytes required */
BPB_BytsPerSec: .2byte 512 /* Bytes per sector */
BPB_SecPerCluse: .byte 1 /* Sector per cluse */
BPB_ResvdSecCnt: .2byte 1 /* Reserved sector count */
BPB_NumFATs: .byte 2 /* Number of FATs */
BPB_RootEntCnt: .2byte 224 /* Root entries count */
BPB_TotSec16: .2byte 2880 /* Total sector number */
BPB_Media: .byte 0xf0 /* Media descriptor */
BPB_FATSz16: .2byte 9 /* FAT size(sectors) */
BPB_SecPerTrk: .2byte 18 /* Sector per track */
BPB_NumHeads: .2byte 2 /* Number of magnetic heads */
BPB_HiddSec: .4byte 0 /* Number of hidden sectors */
BPB_TotSec32: .4byte 0 /* If TotSec16 equal 0, this works */
BS_DrvNum: .byte 0 /* Driver number of interrupt 13 */
BS_Reserved1: .byte 0 /* Reserved */
BS_BootSig: .byte 0x29 /* Boot signal */
BS_VolID: .4byte 0 /* Volume ID */
BS_VolLab: .ascii "Solrex 0.01" /* Volume label, 11 bytes required */
BS_FileSysType: .ascii "FAT12 " /* File system type, 8 bytes required */
/* Initial registers. */
//因為在一開機時只有cs有被填值,其他的segment register要靠自己填
LABEL_START:
mov %cs,%ax
mov %ax,%ds
mov %ax,%es
mov %ax,%ss
mov $BaseOfStack, %sp //BaseOfStack = 0x7c00,很明顯stack就是從0x7c00往下減
/* Clear screen */
mov $0x0600,%ax /* %ah=6, %al=0 */
mov $0x0700,%bx /* Black white */
mov $0,%cx /* Top left: (0,0) */
mov $0x184f,%dx /* Bottom right: (80,50) */
int $0x10 /* BIOS int 10h, ah=6: Initialize screen */
/* Display "Booting**" */
//它下面有三個字串,每個分別都是九個bytes那麼大。而第零個就是booting**
mov $0,%dh
call DispStr /* Display string(index 0)*/
/* Reset floppy */
//這邊我也不知道為什麼要重設floppy
xor %ah,%ah
xor %dl,%dl /* %dl=0: floppy driver 0 */
int $0x13 /* BIOS int 13h, ah=0: Reset driver 0 */
/* Find LOADER.BIN in root directory of driver 0 */
//因為wSectorNo是變數所以用括號。而SecNoOfRootDir是被define的所以用"$"的符號。
/* Read root dir sector to memory */
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
//wRootDirSizeForLoop=14因為整個Root Directory只有14個sectors這麼大
cmpw $0,(wRootDirSizeForLoop) /* If searching in root dir */
jz LABEL_NO_LOADERBIN /* can find LOADER.BIN ? */
decw (wRootDirSizeForLoop)
mov $BaseOfLoader,%ax
mov %ax,%es /* %es <- BaseOfLoader*/
mov $OffsetOfLoader,%bx /* %bx <- OffsetOfLoader */
mov (wSectorNo),%ax /* %ax <- sector number in root */
mov $1,%cl //cl是儲存一次要讀幾個Sectors到es:bx去
call ReadSector //呼叫完了以後要讀取的Sector就會被copy到es:bx
mov $LoaderFileName,%si /* %ds:%si -> LOADER BIN */
mov $OffsetOfLoader,%di /* BaseOfLoader<<4+100*/> %al*/
cld //這邊清除了direction以後,後面的lodsb才會以遞增的方式去load
mov $0x10,%dx //2^9 / 2^5 = 16 因為一個sector 512 bytes,一個root entry 32 bytes
/* Search for "LOADER BIN", FAT12 save file name in 12 bytes, 8 bytes for
file name, 3 bytes for suffix, last 1 bytes for '\20'. If file name is
less than 8 bytes, filled with '\20'. So "LOADER.BIN" is saved as:
"LOADER BIN"(4f4c 4441 5245 2020 4942 204e).
*/
LABEL_SEARCH_FOR_LOADERBIN:
cmp $0,%dx /* Read control */
//一個Sector如果16個root entry都讀完了當然就讀下個sector
jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR
dec %dx
mov $11,%cx //每個entry的最前面11個bytes就是檔名
LABEL_CMP_FILENAME:
cmp $0,%cx //如果全部比完了,當然就是找到
jz LABEL_FILENAME_FOUND /* If 11 chars are all identical? */
dec %cx
//這個的lodsb就會把ds:si的值放到al去,並且由於"cld"的原因所以遞增一
lodsb /* %ds:(%si) -> %al*/
cmp %es:(%di),%al //es:di就是我們所要比對的root entry
jz LABEL_GO_ON
jmp LABEL_DIFFERENT /* Different */
LABEL_GO_ON:
inc %di
jmp LABEL_CMP_FILENAME /* Go on loop */
LABEL_DIFFERENT:
//因為只有後面的五個bits會用到2^5=32所以把di後面五個bits清掉
//之後跳到下一個檔名繼續比對
and $0xffe0,%di /* Go to head of this entry */
add $0x20,%di
mov $LoaderFileName,%si /* Next entry */
jmp LABEL_SEARCH_FOR_LOADERBIN
LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
addw $1,(wSectorNo) //這邊就是讀下一個Sector,所以把Sector number加一
jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN
/* Not found LOADER.BIN in root dir. */
LABEL_NO_LOADERBIN:
//第三個字串
call DispStr /* Display string(index 2) */
jmp . /* Infinite loop */
/* Found. */
LABEL_FILENAME_FOUND:
//RootDirSectors是14,DeltaSecNo是17,FAT表的前兩個欄位是系統欄位,不代表
//檔案的資訊,所以第三個欄位也就是2,開始是代表第33號sector(實際上第34個)。
//也就是Data的開始。
mov $RootDirSectors,%ax
and $0xffe0,%di /* Start of current entry, 32 bytes per entry */
add $0x1a,%di /* First sector of this file */
mov %es:(%di),%cx
push %cx /* Save index of this sector in FAT */
add %ax,%cx
add $DeltaSecNo,%cx /* LOADER.BIN's start sector saved in %cl */
//到這邊為止,要存取的Data放在哪個sector已經算好且放在cx裡面
//接下來就是把ES:BX兩個register的值填好,再把cx放到ax裡,接下
//來的code就可以呼叫ReadSector把Sector載入。
mov $BaseOfLoader,%ax
mov %ax,%es /* %es <- BaseOfLoader */
mov $OffsetOfLoader,%bx /* %bx <- OffsetOfLoader */
mov %cx,%ax /* %ax <- Sector number */
/* Load LOADER.BIN's sector's to memory. */
LABEL_GOON_LOADING_FILE:
//抓幾個sectors就秀出幾個點
push %ax
push %bx
mov $0x0e,%ah
mov $'.',%al
/* Char to print */
mov $0x0f,%bl /* Front color: white */
int $0x10 /* BIOS int 10h, ah=0xe: Print char */
pop %bx
pop %ax
//到這邊為止,前面這幾行印出一個點,都不重要
//下面這行開始就要把剛剛填好的ax號sector抓到ES:BX內。
mov $1,%cl //只抓一個sector
call ReadSector
pop %ax //這邊pop出來的是剛剛存入的cx,代表現在在第幾個FAT欄位
/* Got index of this sector in FAT */
call GetFATEntry
cmp $0x0fff,%ax //如果這個Fat欄位顯示結束就jmp
jz LABEL_FILE_LOADED
push %ax /* Save index of this sector in FAT */
mov $RootDirSectors,%dx
add %dx,%ax
add $DeltaSecNo,%ax
add (BPB_BytsPerSec),%bx //load完一個sector要把offset加上512bytes
jmp LABEL_GOON_LOADING_FILE
LABEL_FILE_LOADED:
mov $1,%dh
call DispStr /* Display string(index 1) */
/* Jump to LOADER.BIN's start address in memory. */
//當然把loader.bin load到9000:0100也就是90100的位置就要jmp過去了
jmp $BaseOfLoader,$OffsetOfLoader //(這邊是整個boot sector跳到loader.bin的地方)
/*================================= Variable table */
wRootDirSizeForLoop: .2byte RootDirSectors
wSectorNo: .2byte 0 /* Sector number to read */
bOdd: .byte 0 /* odd or even? */
/* String table */
LoaderFileName:
.asciz "LOADER BIN" /* File name */
.set MessageLength,9
BootMessage: .ascii "Booting**" /* index 0 */
Message1: .ascii "Loaded in" /* index 1 */
Message2: .ascii "No LOADER" /* index 2 */
/* Routine: DispStr Action: Display a string, string index stored in %dh */
DispStr:
mov $MessageLength, %ax
mul %dh
add $BootMessage,%ax
mov %ax,%bp /* String address */
mov %ds,%ax
mov %ax,%es
mov $MessageLength,%cx /* String length */
mov $0x1301,%ax /* ah = 0x13, al = 0x01(W) */
mov $0x07,%bx /* PageNum 0(bh = 0), bw(bl= 0x07)*/
mov $0,%dl /* Start row and column */
int $0x10 /* BIOS INT 10h, display string */
ret
/* Routine: ReadSector Action: Read %cl Sectors from %ax sector(floppy) to %es:%bx(memory) Assume sector number is 'x', then: x/(BPB_SecPerTrk) = y, x%(BPB_SecPerTrk) = z. The remainder 'z' PLUS 1 is the start sector number; The quotient 'y' devide by BPB_NumHeads(RIGHT SHIFT 1 bit)is cylinder number; AND 'y' by 1 can got magnetic header. */
//剛開始在ax裡頭,我門只會有sector number,接著我門會計算總共
//占有幾個tracks,所以會先把sector number除以18,這個值代
//表總共占有幾個tracks。餘數加一以後就是sector number,因為
//sector number是從0開始。接著因為floppy讀寫的順序是cylinder
//從裡到外head 0->1讀完就到下一個cylinder。所以還要把它除以二
//就代表有幾個cylinder。接著也可以從tracks是偶數還是奇數判斷
//我門的head是多少,這個時候就用and $1的方式。
ReadSector:
push %ebp
mov %esp,%ebp
sub $2,%esp /* Reserve space for saving %cl */
mov %cl,-2(%ebp)
push %bx /* Save bx */
mov (BPB_SecPerTrk), %bl /* %bl: the devider */
div %bl /* 'y' in %al, 'z' in %ah */
inc %ah /* z++, got start sector */
mov %ah,%cl /* %cl <- start sector number */
mov %al,%dh /* %dh <- 'y' */ //這邊就是除以二,shift right 1 bit
shr $1,%al /* 'y'/BPB_NumHeads */
mov %al,%ch /* %ch <- Cylinder number(y>>1) */
//檢查tracks是奇數還是偶數
and $1,%dh /* %dh <- Magnetic header(y&1) */
pop %bx /* Restore %bx */
/* Now, we got cylinder number in %ch, start sector number in %cl, magnetic header in %dh. */ mov (BS_DrvNum), %dl //設dl = 0,代表是對floppy做讀取
GoOnReading:
mov $2,%ah
mov -2(%ebp),%al /* Read %al sectors */
int $0x13
jc GoOnReading /* If CF set 1, mean read error, reread. */
add $2,%esp
pop %ebp
ret
/* Routine: GetFATEntry Action: Find %ax sector's index in FAT, save result in %ax */
//其實這邊每call一次GetFATEntry就要copy兩個Sector到記憶體0x8F000還蠻貴的,
//而且其實常常會重複,因為這個例子來說,只會用到FAT的第一個Sector。
GetFATEntry:
push %es
push %bx
push %ax
mov $BaseOfLoader,%ax
sub $0x0100,%ax
mov %ax,%es /* Left 4K bytes for FAT */
pop %ax //這邊pop出來的ax就是第幾個FAT entry
movb $0,(bOdd)
mov $3,%bx
mul %bx /* %dx:%ax = %ax*3 */
mov $2,%bx
div %bx /* %dx:%ax/2 */
cmp $0,%dx /* remainder %dx = 0 ? */
jz LABEL_EVEN
movb $1,(bOdd)
LABEL_EVEN:
xor %dx,%dx /* Now %ax is the offset of FATEntry in FAT */
mov (BPB_BytsPerSec),%bx
div %bx /* %dx:%ax/BPB_BytsPerSec */
push %dx
mov $0,%bx
add $SecNoOfFAT1,%ax /* %ax <- FATEntry's sector */
mov $2,%cl /* Read 2 sectors in 1 time, because FATEntry */
call ReadSector /* may be in 2 sectors. */ //有可能fat的entry剛好在兩個sector的交界
pop %dx
add %dx,%bx
mov %es:(%bx),%ax
cmpb $1,(bOdd) //如果是奇數的話就不用shift
jnz LABEL_EVEN_2 //如果是偶數的話就要shift right 4 bits
shr $4,%ax
LABEL_EVEN_2:
and $0x0fff,%ax
LABEL_GET_FAT_ENTRY_OK:
pop %bx
pop %es
ret
.org 510 /* Skip to address 0x510. */
.2byte 0xaa55 /* Write boot flag to 1st sector(512 bytes) end */