GRUB整體分析
總體上我們可以把GRUB看成一個微型的作業系統,他有Shell,支援Script,有檔案系統……我們可以把Stage1和Stage1.5看成一個 引導程式,而Stage2則是一個作業系統,只不過這個作業系統是專門用來引導其他動作系統的作業系統,為此,Stage2支援像kernel, initrd,chainloader等等為此目的而設定的內部“命令”。
3.1 GRUB引導作業系統的兩種方式
3.1.1 直接引導方式
GRUB同時支援 Linux, FreeBSD, NetBSD 和OpenBSD。如果想要啟動其他的作業系統,你必須使用鏈式啟動方式來啟動他們[6]。
通常,GRUB直接引導作業系統的步驟如下:
(1) 通過'root'指令來設定GRUB的主裝置指向作業系統映像檔案所儲存的地方。
(2) 通過'kernel'命令來載入該作業系統的核心映像。
(3) 如果需要模組,通過'module'命令來載入模組。
(4) 運行命令'boot'。
Linux, FreeBSD, NetBSD 和 OpenBSD使用相同的方式啟動。你可以通過'kernel'命令來裝載核心映像,然後運行'boot'命令。如果核心需要一些參數的話,只要在'kernal'命令以後追加就可以了。
3.1.2 鏈式引導方式
如果你要啟動一個不被GRUB直接支援的作業系統(例如: Windows 95),可以通過鏈式引導啟動一個作業系統。通常來說,那個引導程式和所要啟動的作業系統是安裝在一個分區中的。
主要步驟如下:
(1) 通過'rootnoverify'命令設定GRUB的主裝置指向一個扇區。
grub> rootnoverify (hd0,0)
(2) 通過'makeactive'命令來設定在扇區上的'active’標誌位。
grub> makeactive
(3) 通過'chainloader'命令來載入引導程式。
grub> chainloader +1
'+1'表明GRUB需要從起始分區讀一個扇區。
(4) 運行命令'boot'。
3.2 GRUB引導作業系統的簡要流程分析
3.2.1 從電腦啟動到GRUB啟動作業系統
(1) BIOS執行INT 0x19,載入MBR至0x7c00並跳轉執行。如果你安裝GRUB到MBR,GRUB的安裝程式會把Stage1(512B)拷貝到MBR。視stage2的大小,安裝程式會在Stage1中嵌入Stage1_5或者Stage2的磁碟位置資訊。
(2) Stage1開始執行,它在進行直接載入Stage1_5或者Stage2並跳轉執行。不論是哪種情況,這一步的結果都是Stage2開始運行了。
(3) Stage2這個小型的作業系統終於開始正式運行了!它會把系統切入保護模式,設定好C運行環境(主要是BSS)。他會先找Config檔案(就是我們的 Menulist),如果沒有的話就執行一個Shell,等待我們輸入命令。然後Grub的工作就是輸入命令-解析命令-執行命令的迴圈,當然 Stage2本身是為載入其他動作系統而存在的,所以如果情況允許,在他執行Boot命令以後就會把控制權轉交出去。
3.2.2 GRUB的主要啟動模組
GRUB 包含如下幾個啟動模組:兩個必須的情境檔案,一個叫"Stage 1.5"的可選的情境檔案以及2個網路啟動的映像檔案。首先對他們有一個大致的瞭解。
Stage1
這是一個基本必須的用來啟動GRUB的映像檔案。通常,這個檔案是被裝載到MBR或者開機磁區所在的分區。由於PC的開機磁區的大小為512位元組,所以這個映像檔案編譯以後也必須為512位元組。
Stage1的全部的工作是從本地磁碟把Stage 2或者Stage 1.5裝載進來。由於對stage1大小的限制,它通過分程式表的形式來編碼Stage 2或者Stage 1.5的位置,所以在stage1是不能識別任何檔案系統的。
Stage2
這是GRUB的核心映像。它幾乎做了除啟動它本身以外的所有事情。通常,它被存放為某一種檔案系統下,但並非是必須的。
e2fs_stage1_5
fat_stage1_5
ffs_stage1_5
jfs_stage1_5
minix_stage1_5
reiserfs_stage1_5
vstafs_stage1_5
xfs_stage1_5
這些檔案被稱為stage 1.5,它存在的目的是做為stage1與stage2之間的橋樑,也就是說,stage1載入stage1.5,然後stage1.5載入stage2。
stage1與stage1.5之間的區別是,前者是不識別任何檔案系統的但後者識別檔案系統(例如 'e2fs_stage1_5' 識別 ext2fs)。所以你可以安全的移動stage2的位置,即使是在GRUB安裝完以後。
nbgrub
這是一個網路啟動的映像檔案,被類似於乙太網路啟動裝載器所使用。它很類似於stage2,但它還要建立網路,然後通過網路來載入設定檔[7]。
pxegrub
這是另一個網路啟動的映像檔案。
除了格式以外,它和'nbgrub'是一致的。
4 STAGE1模組分析
Stage1模組是整個引導程式的引導模組,是從開機過渡到GRUB的第一個模組。Stage1的代碼檔案,是源碼目錄下Stage1/Stage1.S,彙編後便成了一個512位元組的Img,被寫在硬碟的0面0道第1扇區,作為硬碟的主開機磁區。
4.1 Stage1.h檔案分析
在此檔案中主要是定義了一些在Stage1.S檔案中使用到的一些常量。
關於這些常量的分析如下:
/* 定義了grub的版本號碼,在stage1中可以識別他們.*/
#define COMPAT_VERSION_MAJOR 3
#define COMPAT_VERSION_MINOR 2
#define COMPAT_VERSION ((COMPAT_VERSION_MINOR << 8) /
| COMPAT_VERSION_MAJOR)
/* MBR最後兩個位元組的標誌*/
#define STAGE1_SIGNATURE 0xaa55
/* BPB (BIOS 參數區塊BIOS Parameter Block)的結束標記的位移,他含有對磁碟機的低級參數的說明. */
#define STAGE1_BPBEND 0x3e
/* 主要版本號的標記的位移*/
#define STAGE1_VER_MAJ_OFFS 0x3e
/* Stage1開機磁碟機的標記的位移*/
#define STAGE1_BOOT_DRIVE 0x40
/* 強迫使用LBA方式的標記的位移*/
#define STAGE1_FORCE_LBA 0x41
/* Stage2地址標記的位移*/
#define STAGE1_STAGE2_ADDRESS 0x42
/* STAGE2扇區的標記的位移*/
#define STAGE1_STAGE2_SECTOR 0x44
/* STAGE2_段的標記的位移*/
#define STAGE1_STAGE2_SEGMENT 0x48
/* 使用Windows NT的魔術頭標識的位移*/
#define STAGE1_WINDOWS_NT_MAGIC 0x1b8
/* 分區表起始地址的標記的位移*/
#define STAGE1_PARTSTART 0x1be
/* 分區表結束位址的標記的位移*/
#define STAGE1_PARTEND 0x1fe
/* Stage1堆棧段的起始地址*/
#define STAGE1_STACKSEG 0x2000
/* 磁碟緩衝段。磁碟緩衝必須是32K長而且不能跨越64K的邊界。*/
#define STAGE1_BUFFERSEG 0x7000
/* 磁碟機參數的地址*/
#define STAGE1_DRP_ADDR 0x7f00
/* 磁碟機參數的大小*/
#define STAGE1_DRP_SIZE 0x42
/*在BOIS中磁碟片的磁碟機代號標誌*/
#define STAGE1_BIOS_HD_FLAG 0x80
4.2 Stage1.s檔案分析
首先在這個檔案的開始部分定義了一些宏。
#define ABS(x) (x-_start+0x7c00)
這個宏計算了直接地址。由於MBR是被載入到0x7c00的位置,所以通過計算可以直接得到x參數的直接地址。這樣就可以不依賴於Linker程式。
#define MSG(x) movw $ABS(x), %si;
這個宏用於處理對字串的載入和響應。
然後程式從_start程式入口開始執行,此入口在記憶體中的位置為CS:IP 0:0x7c00。隨後對一系列的變數進行了初始化。設定了起始的扇區、磁軌和柱面,並設定了他們的起始位置。同時還設定了stage1的版本號碼。通過設 置boot_drive變數,來設定從那個盤來載入stage2。如果此變數設定成0xff則從預設的開機磁碟機中來載入stage2。然後指定了 stage2的起始地址是0x8000,起始段是0x800,起始的扇區號是1。也就是說stage2起始位置是被存放在0柱面,0磁軌,第2扇區上的 [8]。
程式從real_start入口開始真正執行。首先設定了資料區段以及堆棧段的位移為0,然後設定stage1的堆棧的起始地址為 STAGE1_STACKSEG即0x2000。隨後開啟中斷。然後檢查是否設定了啟動的磁碟。即boot_drive變數是否為0xff,如果非 0xff則儲存設定的磁碟號到dl寄存器中,並壓入堆棧儲存。同時在螢幕上顯示GRUB字樣。然後檢查此啟動磁碟是否是磁碟片,如果是磁碟片則直接跳轉到 CHS模式不用檢測是否支援LBA模式。然後檢測所啟動的磁碟是否支援LBA模式。接著程式分成兩塊,一塊是LBA模式,一塊是CHS模式。
LBA英文全名為Logical Block Addressing,中文名稱為邏輯區塊定址[9]。LBA所指的是一種磁碟裝置的定址技術,它是利用邏輯映對的方式來指定磁碟機的扇區,目前個人 電腦所使用的傳輸介面中,增強型 IDE (Enhanced IDE) 和 SCSI 均使用邏輯區塊定址方式。傳統的硬碟定址技術是採取實體定址(physical mapping、physical addressing)的方式,以磁碟上的實際結構,直接作為資料區塊地址的結構。但由於初期在設計實體定址方式時,硬碟容量只有5、10、20 MB等等小容量機種,所以設計出來的最大的定址能力,只能到1024個磁柱(cylinder)、16個磁頭(head)、63個扇區(sector)。 以每個扇區(sector)512位元組(bytes)計算,實體定址的方式最多隻能使用512×63×1024×16=528482304位元組 (528MB)的硬碟空間。但是由於磁性儲存技術不斷的提升,硬碟容量大幅增加的情況之下,這樣的限制讓使用者必須將硬碟畫分為多個區塊,使用上非常的不 方便。
因此硬體廠商研究出了LBA邏輯定址方式,也就是電腦系統並沒有將資料存放地點的相關記錄,應對到硬碟上資料實際存放的位置。而是由IDE控制電路和 BIOS負責轉換定址(mapping)資料的記錄位置表。經過轉換後的記錄方式,是將第1個磁柱上的第1條磁軌的第1個扇區編號為0,第二個扇區編號為 1,以此類推……,假設1條磁軌有2000個扇區,那麼第2000個扇區的編號就是1999。第2條磁軌上的第1個扇區就是2000,如此一直線性排列下 去。以邏輯區塊的方式來定址的硬碟,最多可達16383磁柱,最大磁頭數為16個,每軌扇區有63區,扇區大小為512位元組,所支援之硬碟空間為512× 63×16383×16=8455200768位元組(8.4GB)[10]。
在ATA的介面規格中,定義了使用28位來定址,因此計算出來,它可以支援到224×512=137GB的容量。不過不幸的,BIOS並無法配合,它使用 24位來定址(也就是LBA模式)。所以根本之道,就是改變BIOS對中斷13h的支援,因此後來的BIOS就設計了加強版的中斷13h。一口氣使用了 64位來對硬碟做定址,因此可以支援到264×512=9.4TB,相當於3萬億倍的8.4GB[11]。
如果是LBA模式下讀取,首先對先前定義的磁碟的一些參數進行了定義,為以後調用INT 13做準備。使用INT13的0x42功能,把磁碟內容讀到記憶體中。設定ah為0x42為功能號,設定dl寄存器來設定磁碟號,si為記錄磁碟一系列資訊 的地址位移量,磁碟資訊中包括了要讀入的柱面號、磁軌號以及扇區號。程式然後調用BOIS INT 13中斷將啟動磁碟上的第二扇區上的內容讀到記憶體中的STAGE1_BUFFERSEG處,在Stage1.h中定義STAGE1_BUFFERSEG為 0x7000。即將第二扇區上的內容讀到記憶體中的0x7000處。讀入成功的話跳轉到COPY_BUFFER處,如果讀取失敗則嘗試使用CHS模式讀入。
與LBA模式不同的是,調用BIOS INT 0x13中斷中的0x2號功能,設定ah寄存器為0x2,al為扇區數,cl的位6,位7和ch組合為磁軌號,cl的0-5位為扇區號,dh為磁頭號, dl為磁碟機代號(其中0x80為硬碟,0x0為軟碟機)。es:bx為資料緩衝區的地址。但所起的功能與前面提到的LBA模式是類似的,也是將第二扇區中的 內容讀到記憶體中的0x7000處,作為緩衝。然後跳轉到COPY_BUFFER處。
最後調用COPY_BUFFER將剛剛讀入的扇區轉移到stage2_address。即轉移到0x8000處。
4.3 Stage1模組功能綜述
由於對Stage1檔案容量的限制,所以Stgae1所做的工作相對來說比較有限。它首先被BOIS裝載到記憶體中的0x7c00處,然後通過調用BOIS INT13中斷,把第開機磁碟機中第二扇區上的內容讀到記憶體中的0x7000處,然後通過調用COPY_BUFFER將其轉移到了記憶體中0x8000的位置上。這個被讀入的第二扇區上的內容,就是下面將要分析的Start.s功能模組。
5 START模組分析
從上一章節的分析中我們看到,Stage1的是完成了一個MBR所需要完成的任務,但GRUB並沒有直接就通過Stage1直接載入GRUB的核心,而是 通過Stage1載入了另一個模組到0x8000處。根據對原始碼的分析,發現被載入的這個模組就是下面需要分析的第二個模組,即Start.S模組。
5.1 Start.s 模組功能分析
在程式的開始部分,仍然是對程式定義了一些宏。
#ifdef STAGE1_5
# define ABS(x) (x-_start+0x2000)
#else
# define ABS(x) (x-_start+0x8000)
#endif
可以發現,如果定義了STAGE1_5則程式的起始地址是0x2000,而如果沒有定義STAGE1_5程式起始的地址正好是0x8000。所以我判斷, 在Stage1後載入記憶體的程式部分就是Start.s所編譯以後的512位元組的映象檔案。關於STAGE1_5的部分暫時先不進行分析,這裡暫且跳過。
宏 “#define MSG(x) movw $ABS(x), %si;”的作用是在螢幕上顯示字串。
接著就是程式的入口_start。由於是緊接著Stage1被載入記憶體中的,所以它的起始地址就是0x8000,並且它仍然將使用Stage1模組留下來 的寄存器以及變數等資訊。如果設定STAGE1_5變數則在螢幕上顯示“Loading stage1.5”,如果沒有設定這個變數則顯示“Loading stage2”。然後讀入需要讀入的扇區的數目。接著進入一個bootloop的迴圈,如果需要讀入的扇區不為0,則繼續迴圈,直到當需要讀入的扇區數目 為0時,迴圈結束。在這個迴圈中,使用與Stage1中的方法相同,判斷了磁碟機磁碟所支援的讀寫入模式,根據不同的磁碟所支援的不同模式,跳轉到相應的部 分去讀取磁碟上的扇區到記憶體中去。如果磁碟支援的是LBA模式則跳轉到lba_mode部分讀取相應的扇區,如果磁碟不支援LBA模式,則跳轉到 chs_mode部分,通過CHS模式來讀入把磁碟中的扇區讀入到記憶體中。首先是把讀到的扇區讀到記憶體中的0x7000處緩衝起來,然後通過調用 copy_buffer子程式,把緩衝中的內容複寫到目標地址,即0x8200開始的地方。與Stage1中的一樣,在Start.s中也有一個記錄地址 的資料結構,不同的是在Stage1中只有一項,而Start.S記錄的是一個地址的鏈表,稱為Blocklist,該鏈表的結點都記錄了一個連續 sectors的集合。
lastlist:
.word 0
.word 0
. = _start + 0x200 - BOOTSEC_LISTSIZE
/*加0x200是由於Start.s編譯完以後也是一個512位元組的映象檔案。*/
/* 初始化了第一個資料列表*/
blocklist_default_start:
.long 2 /* 記錄了從第3個扇區開始*/
blocklist_default_len:
/* 這個參數記錄了需要讀取多少個扇區 */
#ifdef STAGE1_5
.word 0 /* 如果設定了STAGE1_5標誌,則不讀入*/
#else
.word (STAGE2_SIZE + 511) >> 9 /*讀入Stage2所佔的所有扇區*/
#endif
blocklist_default_seg:
#ifdef STAGE1_5
.word 0x220 /*如果設定STAGE1_5則從0x220開始讀入*/
#else
.word 0x820 /*如果沒有設定STAGE1_5則從0x820開始讀入*/
#endif
firstlist:
當把所有需要讀入的扇區都讀入以後,程式進入bootit子程式塊。然後程式進行跳轉,如果設定了STAGE1_5標誌,則跳轉到0x2200執行,如果沒有設定STAGE1_5標誌,則跳轉到0x8200處繼續執行。
5.2 Start 模組功能綜述
通過對Start.s檔案的分析,我們可以看到。Start模組主要是做了一件事情,就是把Stage2或者Stage1_5模組從磁碟裝載到記憶體中。如 果是直接裝載Stage2的話,是裝載在記憶體的0x8200處,如果裝載Stage1_5的話,是裝載在記憶體的0x2200處。