Saturday, September 14, 2024

自己動手寫作業系統 第二章bootloader範例

這是拜讀對岸高手Solrex所著自己動手寫作業系統之後的心得。
下面是第二章的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 //這邊是程式開始
//
前面的這些欄位是BPB(Bios Parameter Block)儲存後面程式用的到的變數
/* 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 */
//在下面可以看到SecNoOfRootDir = 19,而實際上是第20個sectors,因為從零開始。
//因為wSectorNo是變數所以用括號。而SecNoOfRootDir是被define的所以用"$"的符號。
movw $SecNoOfRootDir, (wSectorNo)

/* 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:
//第三個字串
"No LOADER"
mov $2,%dh
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 */

No comments: