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 */

Multiboot USB Bootloader

因為之前工作上的需要,所以我有一顆usb專門裝上DOS,有一個usb裝winpe,另一個usb是裝上Gparted linux。因為usb實在是太多顆了,突發奇想,看看能不能把所有的OS放在一個USB上面。又因為winpe是目的端Windows時,才需要load起來,Linux是目的端Linux,才需要load起來。後來就參考了solrex的文章自己寫了一個bootloader判斷目的端的作業系統,然後自動載入usb上面對應的作業系統。至於如何製作一個multiboot partition請參考我的另一篇文章

其實這篇文章主要的概念就在下面的圖示。
我會在USB partition做好了以後,把我的bootloader用Winhex放到USB MBR裡面。(ctrl+c & ctrl +b千萬記住不能蓋掉partition table,可以分兩次copy,用winhex把編好的bin先copy第一個sector的446bytes,再copy第二個sector的512bytes,事實上第二支code只要copy有東西的地方就可以了,後面零的地方可以不用理他。但在copy前要確認usb已經被低階格式化過了,都清成零才不會錯)。這支程式的架構是分為兩段code。每段code分別為一個sector也就是512 bytes的大小,放在usb最前面MBR開始連續兩個sectors的地方。一開機,MBR的512bytes會被load到記憶體0x0000:0x7C00的位置。這支程式接著會把接續著MBR後面的第二支程式copy到0x9000:0000的位置。並且jmp到第二支code,把權力交給他之後他會到硬碟上去把第一個partition的boot sector 0x0000003F抓到9000:0200並且檢查byte number 3也就是第四個byte處(9000:0203),檢查是否為NTFS。這會有一個小問題,以我這顆usb為例,開機時被認為硬碟,所以int 13h的dl硬碟代號就是80h。而PC上面的硬碟就會被認為是第二顆,就是81h。若是有的usb隨身碟開機的時候被認做是floppy則dl會是零。則硬碟會是80h。這要自己判斷,可以先假設為80h,若是int 13,call完後carry flag on,則為錯誤,這時就可以判斷為被當作是floppy。判斷完了以後,就把要load in的os所在partition的bootsector抓出來放到0x0000:0x7c00處,並且jmp過去,他bootsector裡面的NTLoader(Windows PE) or SYSlinux(GParted linux)就會幫你做loader OS的動作。看完了以後就知道,我是用偷懶的方式,用chainloading load進相對的bootloader。真正的bootloader是不好寫的,以linux為例,要去/doc底下看boot protocol。才知道linux kernel要抓到哪個相對的位置。

首先下面這個是Makefile
CC=gcc
LD=ld
OBJCOPY=objcopy

CFLAGS=-c
TRIM_FLAGS=-R .pdr -R .comment -R.note -S -O binary

LDFILE_BOOT=solrex_x86_boot.ld
LDFILE_DOS=solrex_x86_dos.ld
LDFLAGS_BOOT=-e c -T$(LDFILE_BOOT)
LDFLAGS_DOS=-e c -T$(LDFILE_DOS)

all: boot.img LOADER.BIN

boot.bin: boot.S
$(CC) $(CFLAGS) boot.S
$(LD) boot.o -o boot.elf $(LDFLAGS_BOOT)
$(OBJCOPY) $(TRIM_FLAGS) boot.elf $@

LOADER.BIN: loader.S
$(CC) $(CFLAGS) loader.S
$(LD) loader.o -o loader.elf $(LDFLAGS_DOS)
$(OBJCOPY) $(TRIM_FLAGS) loader.elf $@
#下面這邊可以看到編完的loader.bin是接在boot.bin的後面
boot.img: boot.bin LOADER.BIN
dd if=boot.bin of=boot.img bs=512 count=1
dd if=LOADER.BIN of=boot.img seek=1 bs=512 count=1

# You must have the authority to do mount, or you must use "su root" or
# "sudo" command to do "make copy"
copy: boot.img

clean:
@rm -f *.o *.elf *.bin *.BIN

再下來是solrex_x86_boot.ld,因為一開機時是0x0000:0x7c00所以要offset 0x7c00。
SECTIONS
{
. = 0x7c00;
.text :
{
_ftext = .;
} = 0
}

solrex_x86_dos.ld(因為是放在0x9000:0x0000所以offset 0x0000)
其實這邊如果為了要在dos下面的debug.exe可以run,則可以改成0x0100。
前面的100是program segment prefix。放一些類似命令列參數的資料。
SECTIONS
{
. = 0x0000;
.text :
{
_ftext = .;
} = 0
}


再來就是第一支code boot.S,真的不長啦,很簡單!
.code16
.set BaseOfStack, 0x7c00 /* Stack base address, inner */
.set BaseOfLoader, 0x9000 /* Section loading address of LOADER.S */
.set OffsetOfLoader, 0x0000 /* Loading offset of LOADER.S */
.text
jmp LABEL_START /* Start to boot. */
BS_DrvNum: .byte 0x80 /* Driver number of interrupt 13 */


/* Initial registers. */
LABEL_START:
mov %cs,%ax
mov %ax,%ds
mov %ax,%es
mov %ax,%ss
mov $BaseOfStack, %sp


/* Reset floppy 把磁頭歸零*/
xor %ah,%ah
xor %dl,%dl /* %dl=0: floppy driver 0 */
int $0x13 /* BIOS int 13h, ah=0: Reset driver 0 */
//我們這邊是用int 13h extension所以si那邊指向一個結構。
mov $0x80,%dl
mov $0x42,%ah
mov $(HardPacket),%si
int $0x13

jmp $BaseOfLoader,$OffsetOfLoader

//因為第二支code是放在usb mbr後面的第二個sector,所以是0x00000001
HardPacket:
size_packet: .byte 0x10
reserved: .byte 0
SectorToTran: .2byte 1
Offset: .2byte OffsetOfLoader
Segment: .2byte BaseOfLoader
SectorLow: .4byte 0x00000001
SectorHigh: .4byte 0

.org 510 /* Skip to address 0x510. */
.2byte 0xaa55 /* Write boot flag to 1st sector(512 bytes) end */



Loader.S
.code16
.set BaseOfLoader, 0x0000
.set OffsetOfLoader, 0x7C00
.text
LabelStart:

mov %cs,%ax
mov %ax,%ds
//我們的硬碟是第二顆,因為usb是第一顆,所以硬碟代號是0x81h
mov $(HardDPacket),%si
mov $0x81,%dl
mov $0x42,%ah
int $0x13
//把硬碟bootsector放到9000:0200以後開始比對,第四個byte 0x0203。
mov $0x0203,%si
cld
mov $CompS,%di
mov %ds,%ax
mov %ax,%es
mov $4,%cx //因為四個字"NTFS"

CompNTFS:
cmp $0,%cx
jz EQUAL
dec %cx
lodsb
cmp %bl,%al
jz KeepGoingComp
jmp DIFFERENT

KeepGoingComp:
inc %di
jmp CompNTFS
//如果比對後等於"NTFS",則載入第一個partition也就是windows PE。
EQUAL:
mov $HardPacket,%di
add $8,%di
mov $0,%bx
mov %bx,%ds
mov $0x7c00,%si
add $0x1C6,%si
mov %ds:(%si),%eax
mov %eax,%es:(%di)
jmp DONE
//不同則是第二個partition。
DIFFERENT:
mov $HardPacket,%di
add $8,%di
mov $0,%bx
mov %bx,%ds
mov $0x7c00,%si
add $0x1D6,%si
mov %ds:(%si),%eax
mov %eax,%es:(%di)

DONE:
mov %cs,%bx
mov %bx,%ds
mov $(HardPacket),%si /* ds:si--> offset of disk information */
mov $0x80,%dl /* Drive number of floppy disk */
mov $0x42,%ah

int $0x13

jmp $BaseOfLoader,$OffsetOfLoader
//要比對的那顆硬碟的bootsector,載入後放在9000:0200。硬碟上的第一個partition原則上
//初始位置都是在0x0000003F
HardDPacket:
.byte 0x10
.byte 0
.2byte 1
.2byte 0x0200
.2byte 0x9000
.4byte 0x0000003F
.4byte 0

//這個就是我們最後載入OS loader的詳細資訊了。
HardPacket:
size_packet: .byte 0x10
reserved: .byte 0
SectorToTran: .2byte 1
Offset: .2byte 0x0000
Segment: .2byte 0x07C0
SectorLow: .4byte 0x00000000 //這個位置會依照partition table裡面的值來填
SectorHigh: .4byte 0


CompS: .ascii "NTFS"
.org 512

其實寫bootloader最麻煩的就是debug,原本的code我裡面加了很多的print message所以比較長,這邊為了要容易理解,所以把那些code全部都拿掉了。如果想要debug可以把link script裡".=0x7c00"這行改成".=0x0100",之後副檔名設為.com就可以在dos底下run,就算不改成.com也可以直接debug。改成".=0x0100"是因為com格式的檔案,在被載到記憶體run的時候最前面有一個128 bytes Program Segment Prefix,可知dos在分配記憶體空間時,後面的offset都是從零開始。且在com檔裡code segment 和 data segment是合在一起的。執行檔案大小不可超過64k。

這邊再記錄一下linker的原理,以免忘掉。因為最近又要開始趕touch panel,恐怕以後沒有時間再摸這塊了。這邊".=0x7c00"可以想想看他的原理,先看看開機的時候cs=0x0000, ip=0x7c00。因為寫好的code裡面變數儲存都是根據相對的位置。假設一個變數宣告在整個檔案的0x3位置。而在開機的時候binary被load進0000:7c00,此時變數的相對位置便為0000:7c03。
這個資訊在compile時期是看不到的,在object file裡頭看到的還是0x3相對位置,在link以後才會變成0x7c03。可以用objdump來查看原本被組譯的二進位檔,如:"objdump boot.o -d -M data16,addr16"這些檔案是沒有絕對位置的資訊。當link後的binary,可以用winhex或是khexedit來看。

##說明上面兩段code的link運作原理。
起先因為我們知道一開機的時候,MBR裡面的code是被放到cs:ip = 0000:7c00的地方。所以第一段code的solrex_x86_boot.ld linker script裡面".=0x7c00"就是這樣來的。後來,我們的第二段code是接在MBR後面的一個sector。預設會load到9000:0000,因此solrex_x86_dos.ld裡面會寫成".=0x0000"。