Saturday, September 14, 2024

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"。

4 comments:

Anonymous said...

I have been browsing online more than 3 hours today, yet I never
found any interesting article like yours. It is pretty worth enough for me.

In my view, if all website owners and bloggers made good content as you did, the internet will be a lot more useful than
ever before.

Also visit my blog post :: travel

Anonymous said...

It is not my first time to go to see this web site, i am visiting this web site dailly
and get nice information from here everyday.

Check out my blog post: vakantiewoningen frankrijk huren []

Anonymous said...

Good post. I am going through some of these issues as well.
.

Feel free to visit my page ... luxe villa frankrijk ()

yanmaneee said...

yeezy shoes
yeezy
adidsas yeezy
goyard handbags
golden goose
golden goose
yeezy supply
golden goose sneakers