/* -*-Asm-*- */
#include <stage1.h>
/*BIOS 自檢後將MBR加載到0x7C00,ABS(x)計算地址x在內存中的絕對地址*/
#define ABS(x) (x-_start+0x7c00)
/* Print message string */
#define MSG(x) movw $ABS(x), %si; call message
/* XXX: binutils-2.9.1.0.x doesn't produce a short opcode for this. */
#define MOV_MEM_TO_AL(x) .byte 0xa0; .word x
.file "stage1.S"
/*.text段保存代碼,是隻讀和可執行的,後面那些指令都屬於這個.text段。*/
.text
/* 告訴GAS(gnu asm compiler)生成16-bit指令以便這個代碼可以在實模式下運行*/
.code16
/*
_start是一個符號(Symbol),符號在彙編程序中代表一個地址,可以用在指令中,彙編程序經過彙編器的處理後所有的符號都被替換成它所代表的地址值。在C中我們可以通過變量名訪問一個變量,其實就是讀寫某個地址的內存單元,我們通過函數名調用一個函數其實就是調轉到該函數的第一條指令所在的地址,所以變量名和函數名都是符號,本質上是代表內存地址的。
.globl指示告訴彙編器_start這個符號要被鏈接器用到,所以要在目標文件的符號表中給它特殊標記。_start就像C程序的main函數一樣特殊是整個程序的入口,鏈接器在鏈接時會查找目標文件中的_start符號代表的地址,把它設置爲整個程序的入口地址,所以每個彙編程序都要提供一個_start符號並且用.globl聲明。如果一個符號沒有用.globl指示聲明這個符號就不會被鏈接器用到。
*/
.globl _start; _start:
/*
* 從_start開始的代碼會被BIOS加載到0x7c00 ,此時CS:IP爲0:0x7c00
*/
/*
* 通常在硬盤的第一個扇區開始位置存放的是與FAT/HPFS兼容的BIOS parameter block(PBP.
* BPB參數塊記錄着本分區的起 始扇區、結束扇區、文件存儲格式、硬盤介質描述符、根目錄大小、
* FAT個數、分配單元(Allocation Unit,以前也稱之爲簇)的大小等重要參數。
* 雖然我們要將這份代碼寫入MBR,但是也保留BPB的空間同時將它留作一個小的臨時緩衝區。
*/
jmp after_BPB
nop /* do I care about this ??? */
/*
* 這是供BIOS parameter block使用的空間!!!! 不要修改這個
* jump, 也不要修改任何這個預留塊之後的數據。
*/
. = _start + 4
/* 這裏將它作爲暫存空間 */
mode:
.byte 0
disk_address_packet: /*注:這裏是CHS模式的disk_address_packet參數*/
sectors:
.long 0
heads:
.long 0
cylinders:
.word 0
sector_start:
.byte 0
head_start:
.byte 0
cylinder_start:
.word 0
/* more space... */
/*STAGE1_BPBEND=0x3e=62(d),所以爲BPB預留的空間是*/
. = _start + STAGE1_BPBEND
/*
* End of BIOS parameter block.
*/
stage1_version: //當前版本3.2
.byte COMPAT_VERSION_MAJOR, COMPAT_VERSION_MINOR
boot_drive:
.byte 0xFF /*GRUB_INVALID_DRIVE*/ /* the disk to load stage2 from */
force_lba:
.byte 0
stage2_address:
.word 0x8000
stage2_sector:
.long 1
stage2_segment:
.word 0x800
after_BPB:
/* 跳過BPB後在這裏放執行的代碼 */
cli /* we're not safe here! 不響應可屏蔽中斷*/
/*
* DL是引導設備的代碼,默認不做任何事直接跳轉到.real_start
* jmp後的代碼是當有錯的BIOS沒有傳遞正確的引導設備時的一個解決辦法。
* 如果GRUB安裝到一個HDD, 檢查DL是否正確設置,如果不是則假定BIOS傳遞的是錯誤值,
* 並將DL設置爲0x80,因爲這是唯一可能的引導驅動器。
* 如果 GRUB 被安裝到一個軟盤,則僅僅做跳轉而不做任何事情。
* 另外注意在下面的AT&T指令中[jmp 1f]指令是指跳到下一個[1:]標籤的位置,相似的
* [jmp 1b]指的是跳轉到前一個[1:]標籤。
*/
boot_drive_check:
jmp 1f /*跳到下一個[1:]標籤的位置*/
testb $0x80, %dl /*HDD設備的驅動號被BIOS設置到DL*/
jnz 1f
movb $0x80, %dl
1:
/*
* ljmp to the next instruction because some bogus BIOSes
* jump to 07C0:0000 instead of 0000:7C00.
*/
ljmp $0, $ABS(real_start)
real_start:
/* 設置段寄存器DS和SS的值爲0 */
xorw %ax, %ax
movw %ax, %ds //現在DS = 0
movw %ax, %ss //現在SS = 0
/*
設置棧寄存器SP的值
stage1.h中定義#define STAGE1_STACKSEG 0x2000
棧底0x2000 - 棧頂0x0000 PUSH十六位寄存器時ESP=ESP-2
*/
movw $STAGE1_STACKSEG, %sp
sti /* CPU恢復響應可屏蔽中斷 */
/*
* 檢查BPB後的boot_drive,確認是否強制指定了一個,如果有則將boot_drive傳輸給DL。
* 通常這個值不會設置。
*/
MOV_MEM_TO_AL(ABS(boot_drive)) /* 等同於movb ABS(boot_drive), %al */
cmpb $0xFF/*GRUB_INVALID_DRIVE*/, %al
je 1f /*如果沒有強制指定boot_drive則跳到下一個[1:]否則將設置dl爲boot_drive*/
movb %al, %dl
1:
/* 首先將驅動器引用(驅動器號)保存到棧裏 */
pushw %dx
/* 在屏幕中打印提示信息:"GRUB " */
MSG(notification_string)
/* 如果驅動器是軟盤就不檢測LBA,
* LBA是非常單純的一種尋址模式﹔從0開始編號來定位區塊,第一區塊LBA=0,第二區塊
* LBA=1,依此類推。
* 這種尋址模式取代了原先操作系統必須面對存儲設備硬件構造的方式。
* #define STAGE1_BIOS_HD_FLAG 0x80
* The flag for BIOS drive number to designate a hard disk vs. a floppy.
*/
testb $STAGE1_BIOS_HD_FLAG, %dl
/*等同於if ($DL & 0x80 != 0) goto chs_mode,不是硬盤就直接跳轉到chs_mode*/
jz chs_mode
/* 確認硬盤是否支持LBA*/
/*
INT 0x13 - 0x41檢驗擴展功能是否存在
入口:
AH = 41h
BX = 55AAh
DL = 驅動器號
返回:
CF = 0
AH = 擴展功能的主版本號
AL = 內部使用
BX = AA55h
CX = API 子集支持位圖
CF = 1
AH = 錯誤碼 01h, 無效命令
這個調用檢驗對特定的驅動器是否存在擴展功能. 如果進位標誌置 1
則此驅動器不支持擴展功能.;
如果進位標誌爲 0, 同時 BX = AA55h, 則存在擴展功能. 此時 CX 的 0 位表示是否支持第一個子集, 1位表示是否支持第二個子集.
*/
movb $0x41, %ah
movw $0x55aa, %bx
int $0x13
/* 某些傻逼BIOS在用INT13,AH=41H時會把%dl污染,這裏從棧裏恢復一下dx並重新保存(前面有pushw %dx)This happens, for example, with AST BIOS 1.04.*/
popw %dx
pushw %dx
/*CF=1時此驅動器不支持擴展功能,那麼跳轉到chs_mode。 */
jc chs_mode
cmpw $0xaa55, %bx
jne chs_mode /*CF=1但是BX是錯誤值,那麼當做不支持處理。*/
/* 如果.force_lba(緊跟PBP後)不等於0,那麼跳過檢測是否支持INT13 AH=0x42功能,
否則檢測,如果檢測後支持此功能則使用chs_mode啓動。等同於:
if (.force_lba != 0) goto lba_mode;
if ($cx & 1 == 0) goto chs_mode;*/
MOV_MEM_TO_AL(ABS(force_lba)) /* movb ABS(force_lba), %al */
testb %al, %al
jnz lba_mode
andw $1, %cx //CX = API 子集支持位圖 【位0 ==> 0x42功能是否支持】
jz chs_mode
lba_mode:
/* save the total number of sectors */
/* 下面是一條廢語句,從SI指向的內存地址取值到ECX,而si的值是什麼時候設定的?MSG(notification_string)?
AT&T寄存器尋址:4(%eax) => *(4+eax),所以下面的等同於
*(DWORDPTR)(SI + 0x10) => %ecx */
movl 0x10(%si), %ecx
// 開始填充讀扇區int 13h - 0x42指令的參數。
/* 將si設置爲.disk_address_packet所在地址,見上面暫存空間*/
movw $ABS(disk_address_packet), %si
/* 設置.mode爲1 */
movb $1, -1(%si)
/*
struct disk_address_packet{
+00 byte PacketSize = 10h //此結構大小
+01 byte Reserve = 0
+02 word BlockCount = 1 //傳輸數據塊數
+04 word BufferOff = 0 // 緩存地址
+06 word BufferSeg = 0x0700 //即0700:0000
+08 qword Start = 1 //起始扇區的LBA號碼
}*/
movl ABS(stage2_sector), %ebx
/* PacketSize 和 Reserve */
movw $0x0010, (%si)
/* BlockCount = 1 */
movw $1, 2(%si)
/* 低32位Start = 1*/
movl %ebx, 8(%si)
/* BufferSeg = 00700,the segment of buffer address (0x0700)*/
movw $STAGE1_BUFFERSEG, 6(%si)
xorl %eax, %eax
/* BufferOff = 0*/
movw %ax, 4(%si)
/* 高32位Start = 0*/
movl %eax, 12(%si)
/*
* BIOS call "INT 0x13 Function 0x42" to read sectors from disk into memory
* Call with %ah = 0x42
* %dl = drive number
* %ds:%si = segment:offset of disk address packet
* Return:
* %al = 0x0 on success; err code on failure
*/
movb $0x42, %ah
int $0x13
/* LBA read is not supported, so fallback to CHS. */
jc chs_mode
/* bx = 0x7000 */
movw $STAGE1_BUFFERSEG, %bx
jmp copy_buffer
/*驅動器的CHS(Cylinder/Head/Sector,即柱面/磁頭/扇區)模式*/
chs_mode:
/*
* 從BIOS中獲知CHS參數,使用int13/8h(DL是磁盤ID,00H~7FH:軟盤;80H~0FFH:硬盤)
*
* 功能08H
* 功能描述:讀取驅動器參數
* 入口參數:AH=08H
* DL=驅動器,00H~7FH:軟盤;80H~0FFH:硬盤
* 出口參數:CF=1——操作失敗,此時AH=狀態代碼,否則:
* BL=01H — 360K
* =02H — 1.2M
* =03H — 720K
* =04H — 1.44M
* CH=柱面數的低8位
* CL的位7-6=柱面數的高2位
* CL的位5-0=扇區數
* DH=磁頭數
* DL=驅動器數
* ES:DI=磁盤驅動器參數表地址
* We do this first, so that LS-120 IDE floppies work correctly.
*/
movb $8, %ah
int $0x13
jnc final_init
/*
* 調用失敗,檢測軟盤。 STAGE1_BIOS_HD_FLAG = 80,如果是硬盤則&dl的結果必定不爲0
*/
testb $STAGE1_BIOS_HD_FLAG, %dl
jz floppy_probe
/* 不是軟盤,我們確定是有一個硬盤,所以我們搞砸了。打印錯誤信息*/
jmp hd_probe_error
final_init:
/*
CHS模式下的硬盤地址參數臨時緩衝區
struct disk_address_packet{
long sectors = 0
long heads = 0
word cylinders = 0
byte sector_start = 0
byte head_start = 0
word cylinder_start = 0
}
*/
movw $ABS(sectors), %si
/* set the mode to zero */
movb $0, -1(%si)
/* 磁頭數在int13/8H中保存在了dh中,將它保存到si指向的臨時內存中。*/
xorl %eax, %eax
movb %dh, %al
/*因爲磁頭數是以0~n-1方式排列的,所以增1後纔是真正的磁頭數*/
incw %ax
movl %eax, 4(%si)
/*CH=柱面數的低8位 CL的位7-6=柱面數的高2位 CL的位5-0=扇區數
下面這段是計算柱面數*/
xorw %dx, %dx
movb %cl, %dl
shlw $2, %dx
movb %ch, %al
movb %dh, %ah
/* 保存柱面數,同磁頭數一樣需要加一*/
incw %ax
movw %ax, 8(%si)
/*計算扇區數*/
xorw %ax, %ax
movb %dl, %al
shrb $2, %al
/* 保存扇區數 */
movl %eax, (%si)
setup_sectors:
/* (引導扇區的序號,見上面.stage2_sector=1)load logical sector start (bottom half) */
movl ABS(stage2_sector), %eax
/* %edx = 0 */
xorl %edx, %edx
/* 引導扇區號除以扇區數,DIV的商存在EAX而餘數存EDX */
divl (%si)
/* 餘數即開始扇區的序號 */
movb %dl, 10(%si)
xorl %edx, %edx /* zero %edx */
/*上一步的商再除以磁頭數,其餘數即得到開始磁頭號*/
divl 4(%si)
movb %dl, 11(%si)
/* 上一步的商即爲開始柱面號 */
movw %ax, 12(%si)
/* 柱面值是否超過柱面數,如是則報錯。這裏不知道什麼情況下會發生 */
cmpw 8(%si), %ax
jge geometry_error
/*
* 下面是組織BIOS調用的參數以及調用0x13/0x2。
* 調用BIOS "INT 0x13/0x2"從磁盤中讀取一個或者多個扇區到內存中
* 參數 %ah = 0x2
* %al = 讀取扇區數
* %ch = 所在柱面
* %cl = 開始扇區 (6-7位是所在柱面的高兩位)
* %dh = 磁頭
* %dl = drive (0x80是硬盤, 0x0是軟盤)
* %es:%bx = 緩衝區的“節:偏移”
* Return:
* %al = 0x0 on success; err code on failure
*/
/* get high bits of cylinder */
movb 13(%si), %dl
shlb $6, %dl /* shift left by 6 bits */
movb 10(%si), %cl /* get sector */
incb %cl /* normalize sector (sectors go from 1-N, not 0-(N-1) ) */
orb %dl, %cl /* composite together */
movb 12(%si), %ch /* sector+hcyl in cl, cylinder in ch */
/* restore %dx */
popw %dx
/* head number */
movb 11(%si), %dh
movw $STAGE1_BUFFERSEG, %bx /*硬盤緩衝區地址,0x7000:0000*/
movw %bx, %es /* load %es segment with disk buffer */
xorw %bx, %bx /* %bx = 0, put it at 0 in the segment */
movw $0x0201, %ax /* function 2 */
int $0x13
jc read_error
movw %es, %bx
copy_buffer:
/* bx = 0x7000, es = 0x0800*/
movw ABS(stage2_segment), %es
/*
* 我們需要保存cx和si寄存器的值,因爲
* stage2中的啓動代碼不初始化就直接使用它們
*/
pusha
pushw %ds
movw $0x100, %cx /* 現在cx = 0x0100*/
movw %bx, %ds /* 現在ds = 0x7000*/
xorw %si, %si /* 現在si = 0*/
xorw %di, %di /* 現在di = 0*/
cld
/*ds:si => es:di即0x7000:0000 => 0x0800 : 0000,共複製0x100 * 2字節(即512字節)*/
rep movsw
popw %ds
popa
/* boot stage2 ,即跳轉到我們剛剛複製的代碼*/
jmp *(stage2_address)
/* END OF MAIN LOOP */
/*
* BIOS Geometry translation error (past the end of the disk geometry!).
*/
geometry_error:
MSG(geometry_error_string)
jmp general_error
/*
* Disk probe failure.
*/
hd_probe_error:
MSG(hd_probe_error_string)
jmp general_error
/*
* Read error on the disk.
*/
read_error:
MSG(read_error_string)
general_error:
MSG(general_error_string)
/* go here when you need to stop the machine hard after an error condition */
stop: jmp stop
notification_string: .string "GRUB "
geometry_error_string: .string "Geom"
hd_probe_error_string: .string "Hard Disk"
read_error_string: .string "Read"
general_error_string: .string " Error"
/*
* message子函數: 打印%si指向的字符串
*
* WARNING: 函數將污染%si, %ax, and %bx
*/
/*
* 循環使用BIOS "int 10H 函數號0Eh" 指令逐個將字符寫到監視器。
* %ah = 0xe %al = character
* %bh = page %bl = foreground color (graphics modes)
*/
1:
movw $0x0001, %bx
movb $0xe, %ah
int $0x10 /* display a byte */
message:
lodsb
cmpb $0, %al
jne 1b /* if not end of string, jmp to display */
ret
/*
* Windows NT breaks compatibility by embedding a magic
* number here.
*/
. = _start + STAGE1_WINDOWS_NT_MAGIC
nt_magic:
.long 0
.word 0
/*
* This is where an MBR would go if on a hard disk. The code
* here isn't even referenced unless we're on a floppy. Kinda
* sneaky, huh?
*/
part_start:
. = _start + STAGE1_PARTSTART
probe_values:
.byte 36, 18, 15, 9, 0
floppy_probe:
/*
* Perform floppy probe.
*/
movw $ABS(probe_values-1), %si
probe_loop:
/* reset floppy controller INT 13h AH=0 */
xorw %ax, %ax
int $0x13
incw %si
movb (%si), %cl
/* if number of sectors is 0, display error and die */
cmpb $0, %cl
jne 1f
/*
* Floppy disk probe failure.
*/
MSG(fd_probe_error_string)
jmp general_error
fd_probe_error_string: .string "Floppy"
1:
/* perform read */
movw $STAGE1_BUFFERSEG, %bx
movw $0x201, %ax
movb $0, %ch
movb $0, %dh
int $0x13
/* if error, jump to "probe_loop" */
jc probe_loop
/* %cl is already the correct value! */
movb $1, %dh
movb $79, %ch
jmp final_init
. = _start + STAGE1_PARTEND
/* the last 2 bytes in the sector 0 contain the signature */
.word STAGE1_SIGNATURE