2010年10月25日 星期一

GRUB FOR DOS 的總體框架

文章出處:http://www.linuxeden.com/blog/?uid-19847-action-viewspace-itemid-5490
GRUB FOR DOS 的總體框架
bendany 建議我寫一寫 GRUB4DOS 的大致結構,以便準備投入開發 GRUB4DOS 的朋友們能夠對此有一個快速的瞭解。我試試吧,就寫了這篇短文。文章有什麼不妥的,或者需要補充的,希望大家提出來,我再加以完善。幾年前寫過兩三篇 GRUB4DOS 的技術文章,雖然某些內容有些陳舊,但似乎也還可以用。有需要的朋友,可以在各大搜索引擎中搜索grub for dos 或者 grub.exe 或者 grldr 等字眼,應該能夠找到那些文章。這次不重複那些文章的內容了,主要面向準備繼續開發 Grub4dos 的朋友們。
[@more@]
  GRUB for DOS 是 GNU GRUB 的功能擴展,是以 GNU GRUB 為基礎的。有關 GNU GRUB 的技術資訊,可以由 Linux 下的 info grub 命令獲得。這裏主要說說 GRUB for DOS 的特有資訊,並且只是粗略的、輪廓性的介紹。詳細的資訊還是要通過源代碼才能得到。在關鍵的地方,源代碼中都有比較詳細的注釋。
1。啟動過程,啟動方式
  GNU GRUB 的啟動過程大致是這樣的:512 位元組的 stage1,放在 MBR 或者軟碟第一磁區,它從 BIOS 那裏獲得控制,然後它負責查找 stage1.5 或者 stage2。其實,stage1 在被放置到 MBR 或者軟碟第一磁區的時候,已經把 stage1.5 或者 stage2 的物理磁區位置記錄到 stage1 的某個區域中了,這通常稱為硬編碼。這些都不重要,重要的是,stage1 和 stage1.5 都不是 GRUB 的主體,它們都是為了把 stage2 裝入到記憶體中,除此之外,它們沒有別的用處。
  stage2 又分兩部分:第一部分是 512 位元組的開頭,它像 stage1 一樣,裏面含有 stage2 的物理磁區位置列表,整個磁區沒有別的用處,僅僅只是為了尋找 stage2 的後續部分(也就是從第二磁區開始一直到檔結尾)。這第二部分才是 GRUB 的主程序體,它叫做 pre_stage2。也就是說,stage2 = 512 位元組的頭部 + pre_stage2
stage1 和 stage2 中都含有磁區絕對定位資訊,這些資訊,是在將 GNU GRUB 安裝到 MBR 或者軟碟第一磁區時,由 setup 命令計算之後寫入的。
  說了這麼多,其實只是為了說明一點:pre_stage2 才是對我們的 GRUB FOR DOS 有用的。所以,你在程式中會看到 pre_stage2_start 這個標號。
  GRUB FOR DOS 主要有兩大類啟動方式。一類是從作業系統中啟動(grub.exe),一類是由 BIOS 引導啟動(GRLDR)。
  GRLDR 也可以由 NTLDR 啟動,不過這其實也類似於從 BIOS 啟動,因為由 NTLDR 所賦予的啟動環境,沒有破壞 BIOS 中斷向量。
  GRUB.EXE 可以從 DOS/Windows9x 下運行,也可以經由 kexec 而從 Linux 下運行,或者也可以經由 LILO 或者 syslinux 這類引導器而啟動運行。這又分為兩種情況:在 DOS/Windows9x 下,GRUB.EXE 是被作為 DOS 的可執行檔或者可以作為 CONFIG.SYS 裏的設備驅動檔而運行。在其他情況下,GRUB.EXE 是作為 LINUX 內核格式而被啟動運行的。
2。檔結構
  GRLDR 的檔結構相對來說比較單一,源代碼文件是 grldrstart.S。它的第一磁區,是啟動代碼,如果這是在 MBR 上,它負責裝入位於硬碟第一磁軌上的其餘磁區。如果是經由 NTLDR 啟動,那麼 NTLDR 會裝入 GRLDR 的開頭 16 個磁區。這些磁區中的代碼,負責查找各個硬碟各個分區根目錄下的 grldr 檔,並裝載整個 grldr 檔到記憶體中。緊接著 GRLDR 開頭的 16 磁區,就是 pre_stage2 了。GRLDR 開頭的 16 磁區代碼,負責把 pre_stage2 放到 0000:8200 處,並把控制交給 pre_stage2。在 GNU GRUB 中,0000:8000 處放置的是 stage2,由於我們不用 stage2 的第一磁區,所以,我們直接把 pre_stage2 放置在 0000:8200 了。
  bootlace.com 可以用來將 grldr 的啟動代碼寫入到 MBR 或者軟碟的第一磁區上。其實,寫入到 MBR 上的,不完全就是 GRLDR 開頭的 16 磁區,而是 GRLDR.MBR 檔的內容(只有第一磁區的若干個控制位元組會被改動,以及硬碟分區表會被加入到第一磁區,而第二磁區可以放置硬碟上原來的 MBR 作為備份,其餘磁區都原封不動地被拷貝到第一磁軌的相應磁區上)。bootlace.com 是雙重可執行檔格式:它可以在 DOS 下運行(作為 .com 可執行檔),也可以在 Linux 下運行(作為 ELF 可執行檔)。GRLDR.MBR 檔將來可以增大到 63 個磁區那麼長,這是一個磁軌長度的最大上限。但是 GRLDR 檔的開頭部分卻只能有 16 個磁區那麼長,因為 NTLDR 只能裝入這麼長,它不能為我們直接裝入全部的 GRLDR 檔。GRLDR.MBR 的源代碼文件是 mbrstart.S。bootlace.com 的源代碼文件是 bootlacestart.S。
  GRUB.EXE 是三重檔格式。它的結構比較複雜。首次看源代碼的時候,可以先忽略它作為 DOS 設備驅動程式檔格式的那些代碼,因為那可能比較難以理解。而作為 DOS 的 EXE 格式以及作為 LINUX 的 bzImage 格式,都是比較容易理解的,因為這方面的資料很容易找到。源代碼文件是 dosstart.S。像 GRLDR 的情況那樣,GRUB.EXE 也是由開頭的啟動代碼以及 pre_stage2 這兩部分構成的。
  pre_stage2 的源代碼文件是 asm.S,它會調用其他 C 語言程式檔。這些 C 檔和 asm.S 一起編譯生成 pre_stage2 檔。builtins.c 檔含有各種命令的代碼,包括 map 命令。如果要為 GRUB 增添拷貝檔或者創建檔的命令,則需要修改 fsys_*.c 檔,它們是各種檔系統的驅動程式檔。
3。記憶體的使用
在物理位址 0x800 到 0x17FF 這 4K,放置嵌入到 GRUB.EXE 命令行的那些命令。只有從 CONFIG.SYS 中用 device=grub.exe --config-file="GRUB_COMMANDS" 的方式,才可以嵌入接近 4K 的 GRUB 命令,其他方式都受具體環境的限制。比如,DOS 命令行不超過 127 個字元。GRUB.EXE 作為 Linux 內核格式,它也只能接受最多 512 位元組的命令行。
  物理位址 2M 處放置備份的 640K 的 DOS 常規記憶體,以便 quit 命令可以恢復 DOS 的現場。
--> 閱讀更多...

從啟動到run grub

文章出處:http://bbs.tongji.net/thread-258437-1-1.html
從啟動到run grub
下面借用了 劉 吟(劉老大) OS啟動的第一步的Ppt
Os 啟動第一步
1.Bios
Cpu 的初始化
► 主機加電後,啟動時鐘發生器,在匯流排上產生POWERGOOD信號,CPU收到RESET信號,進入初始化過程
CPU轉入8086實模式。
DS = ES = FS = GS = SS = 0
CS = 0xFFFF
IP = 0XFFF0
進入BIOS加電自檢過程(Power On Self Test)
BIOS初始化
關中斷,進行所有的POST檢測
將中斷向量表的起始位址設為0x0000H
0x0000H~0x03FFH中存放了256個中斷
建立了實模式下的中斷向量表
BIOS的啟動程式調用INT 19h中斷
將控制權轉移給Boot Loader
► INT 19h中斷的功能
INT 19h按照BIOS中的啟動設備順序查詢每個啟動設備在軟碟的啟動磁區或者硬碟的MBR中有Boot Loader,那麼這個磁區的最後兩個位元組必然為0xAA55。BIOS將這個磁區(512個位元組)讀入記憶體的0000:7C00開始的位置,然後跳轉到記憶體0000:7C00的地方開始執行。如果在所有的啟動設備上都找不到Boot Loader,那麼就調用INT 18h,將控制權交給BIOS ROM Basic,鎖定機器,並且在螢幕上顯示NO BOOT DEVICE AVAILABLE
► 結論
Boot Loader應當存放在啟動設備的第一個磁區中,對於硬碟是MBR,對於軟碟是啟動磁區安裝了Boot Loader的啟動磁區的最後兩個位元組必須為0xAA55 BIOS在POST過程結束以後,調用INT 19h中斷,將Boot Loader讀入記憶體0000:7C00處。然後釋放控制權,跳轉到0000:7C00開始執行Boot Loader的代碼。
2.最簡單的Boot Loader:FDISK /MBR
硬碟的分區表結構
a) 第一個磁區為MBR
b) 一個硬碟上最多有4個主分區
c) 第一個主分區一般從cylinder 0, head 1, sector 1開始
d) cylinder 0, head 0, sector 2~n一般來說保留不用
e) 其餘的主分區一般從cylinder x, head 0, sector 1開始

Master Boot Record
f) 位於硬碟的cylinder 0, head 0, sector 1的位置
g) 大小為512位元組
h) 其中存放了4個主分區的入口,每個入口占16個位元組(這就是為什麼一個磁片最多只能有4個主分區的原因)
i) 最後兩位元為啟動標誌,如果MBR中有Boot Loader的話,則為0xAA55
j) 留給Boot Loader的空間為512-16x4-2=446位元組
MBR中的Boot Loader的功能
k) 初始化,將自身搬移到0000:0600的位置
l) 在主分區入口表中尋找活動的分區
m) 調用INT 13h AH=02將活動的分區的啟動磁區讀入記憶體0000:7C00處
n) 跳轉到0000:7C00處執行活動分區的啟動磁區中的代碼
初始化,將自身搬移到0000:0600的位置
0000:7C00 CLI 關中斷
0000:7C01 XOR AX,AX
0000:7C03 MOV SS,AX 將堆疊段(SS)設為0
0000:7C05 MOV SP,7C00 將棧頂指針(SP)設置為7C00
0000:7C08 MOV SI,SP 將SI也設為7C00
0000:7C0A PUSH AX
0000:7C0B POP ES 將ES設為0000
0000:7C0C PUSH AX
0000:7C0D POP DS 將DS設為0000
0000:7C0E STI 開中斷
0000:7C0F CLD
0000:7C10 MOV DI,0600 將DI設為0600
0000:7C13 MOV CX,0100 準備移動256個字(512位元組)
0000:7C16 REPNZ
0000:7C17 MOVSW 將MBR從0000:7c00移動到0000:0600
0000:7C18 JMP 0000:061D 開始搜索主分區入口表
► 在主分區入口表中尋找活動的分區

0000:061D MOV SI,07BE SI指向分區表入口(在總共512byte的主引導記錄中,MBR的引導程式占了其中的前446個位元組(偏移0H~偏移1BDH),隨後的64個位元組(偏移1BEH~偏移1FDH)為DPT(Disk PartitionTable,硬碟分區表),最後的兩個位元組“55 AA”(偏移1FEH~偏移1FFH)是分區有效結束標誌)
0000:0620 MOV BL,04 一共有4個表項
0000:0622 CMP BYTE PTR [SI],80 是否為活動分區
0000:0625 JZ FOUND_ACTIVE 找到了一個活動表項
0000:0627 CMP BYTE PTR [SI],00 是否為非活動分區
0000:062A JNZ NOT_ACTIVE 不可識別的分區標識
0000:062C ADD SI,+10 指向下一個表項(+16)
0000:062F DEC BL 迴圈標誌減一
0000:0631 JNZ 0000:061D 繼續迴圈
0000:0633 INT 18 未找到可啟動的分區,轉到ROM Basic

► 調用INT13 AH = 02h讀取啟動磁區

0000:0635 MOV DX,[SI] 設置INT 13調用中Head和Driver的值
0000:0637 MOV CX,[SI+02] 設置INT 13調用中Cylinder的值
0000:063A MOV BP,SI 保存活動分區入口表項位址

0000:065D MOV DI,0005 設置讀取的重試次數
0000:0660 MOV BX,7C00 將啟動磁區讀取到0000:7C00(ES:BX)
0000:0663 MOV AX,0201 準備調用INT 13讀取一個磁區AL = 01
0000:0666 PUSH DI 保存重試次數DI
0000:0667 INT 13 調用INT 13讀取一個磁區到0000:7c00
0000:0669 POP DI 恢復重試次數DI
0000:066A JNB INT13OK 如果讀取成功,則跳轉至啟動代碼
0000:066C XOR AX,AX 準備INT 13 AH = 0重定磁片
0000:066E INT 13 調用INT 13重定磁片
0000:0670 DEC DI 重試次數減一
0000:0671 JNZ 0000:0660 進行下一次重試

► 運行啟動磁區中的代碼

0000:067B MOV DI,7DFE 指向啟動磁區中的啟動標識
0000:067E CMP WORD PTR [DI],AA55 檢查啟動表識是否為0xAA55
0000:0682 JNZ DISPLAY_MSG 如果不是,則保錯
0000:0684 MOV SI,BP 恢復SI,指向活動分區入口表項
0000:0686 JMP 0000:7C00 跳轉至啟動磁區的代碼

► 結論
Boot Loader放在可啟動設備的第一個磁區中Boot Loader的大小受磁區大小和其他附加資訊的限制。在MBR中,為446位元組Boot Loader在從BIOS中接手CPU的控制權時,位於記憶體位址0000:7C00處FDISK /MBR產生的Boot Loader不具備啟動OS的能力,其本質上是一個Chain Loader,用於引導一個有啟動OS能力的Boot Loader。
3.如何引導OS:DOS Boot Loader
► Boot Sector的結構
Boot Sector位於每個分區的第一個磁區,Boot Sector的第一個部分是一個跳轉指令和一個NOP,以跳轉實際的Boot Loader的代碼中,BIOS Parameter Block中存放了和這個分區相關的一系列參數BPB之後就是實際的Boot Loader的代碼最後是一個可啟動分區標識0xAA55
► 使用Format /s對Boot Sector做的修改,在0x000h處寫入BPB,在0x03Eh處寫入DOS Boot Loader的代碼,在0x1FEh處寫入0xAA55標識。
► 此外,Format /s還要初始化FAT表,將IO.SYS和MSDOS.SYS寫入,佔據FAT表前兩個表項。
► DOS Boot Loader的功能初始化;計算根目錄所在的FAT表的磁區號讀取根目錄的第一個磁區到0000:0500檢查前兩個表項是否為IO.SYS和MSDOS.SYS將IO.SYS的前3個磁區讀入0000:0700或者0070:0000的位置中在寄存器中保留一些資訊,然後跳轉到0070:0000處執行作業系統代碼DOS Boot Loader和MBR中的Boot Loader的最大區別在於對於檔系統的理解。
► MBR中的Boot Loader不理解檔系統,所以無法啟動特定的OS
► DOS Boot Loader提供了對於FAT檔系統的支援,所以能夠啟動在FAT檔系統上的DOS
► DOS Boot Loader知道OS的內核檔的位置,其主要的工作就是將內核檔讀入記憶體,然後將控制權轉交給OS
4.硬碟定址方式:CHS vs LBA
► 最早期的CHS定址,最早期的磁片定址是通過Cylinder/Head/Sector進行的,INT 13h中給出的CHS參數直接指定了資料的物理位置例:INT 13h AH = 02。
► AH = 02 AL = 讀入的磁區數目
► CH = Cylinder低8位 CL = Cylinder高兩位
► DH = Head DL = 操作的磁片(80h for C:)
 限制:
► Cylinder <= 1024
► Head <= 16
► Sector <= 63
► 總容量 < 1024 x 16 x 63 x 512B = 528MB
► L-CHS & P-CHS;為了解決直接定址的問題,現代的HD中,CHS只有邏輯上的意義,不表達物理上的實際位置。L-CHS用在支援CHS Translation的BOIS的INT 13 AH = 0xh的調用中。
► Cylinder <= 1024
► Head <= 256
► Sector <= 63
► 總容量 <= 1024 x 256 x 63 x 512B = 8GB,P-CHS用在HD的介面上
► Cylinder <= 65535
► Head <= 16
► Sector <= 63
► 總容量 <= 65535 x 16 x 63 x 512B = 136GB
► LBA:Large Block Addressing
► LBA的出現是為了解決CHS模式位址受限的問題
► LBA提供了一種線性的定址方式:Cylinder 0,Head 0,Sector 1相當於LBA地址0。往後每增加一個磁區,LBA位址加一
► LBA位址和CHS位址可以通過如下公式轉換
► LBA = ((cylinder * heads_per_cylinder + heads) * sectors_per_track )+sector-1
► 新型的BOIS提供了INT 13h AH = 4xh的擴展磁片調用以支援LBA模式
► LBA:Large Block Addressing;INT 13h AH = 42h
► AH = 42H DL = 操作的磁片(80h for C:)
► DS:SI = Disk Address Packet;Disk Address Packet的格式
► 結論:定址方式和Boot Loader的關係;BIOS在POST過程以後調用INT 19h,使用的是CHS定址;如果一個OS自身的Boot Loader使用的是CHS定址模式,那麼在老式BIOS上不能啟動528MB以後的分區,在新式BIOS上不能啟動8G以後的分區(LILO的問題);只有支持LBA的Boot Loader才能支持啟動8G以後分區上的OS
5.支持多OS引導的Boot Loader
► 支持多OS引導的Boot Loader是:
 一段在啟動時被BIOS INT 19調用的代碼
 能夠理解系統中安裝的多個不同的OS
 用戶可以選擇啟動特定的OS
 程式根據用戶選擇,通過直接載入或者Chain Load的方法,啟動選中的OS
► 支援多OS引導的Boot Loader需要:
 自身足夠的小,或者支持多階段載入,以能夠安裝在MBR或者Boot Sector中
 能夠理解多種檔系統的格式,以便能夠找到存放在不同檔系統下的系統內核
 能夠理解多種檔格式(ELF/a.out),以便能夠正確的載入不同的系統內核
 支援CHS和LBA兩種磁片訪問模式,以便能夠正確的啟動8G以後的分區
 支持Chain Loader,以便通過特定的Boot Loader載入OS
► 支持多OS引導的Boot Loader在執行32位OS的內核代碼前,需要構建的機器狀態:
 CS必須是一個32位的可讀可執行的代碼段,偏移為0,上限為0xFFFFFFFF
 DS, ES, FS, GS和SS必須為32位的可讀寫段,偏移為0,上限為0xFFFFFFFF
 20號地址線必須在32位位址空間中可用(初始固定為0)
 分頁機制必須被關閉
 處理器中斷標記被關閉
 EAX的值為0x2BADB002
 EBX中存放了一個32位的位址,指向由Boot Loader填充的一系列啟動資訊
引自:Multiboot Specification
6.實例分析:Grub
► Grub是:
 GRand Unified Bootloader的縮寫
 是一個靈活而強大的Boot Loader
 其能夠理解多種不同的檔系統和可執行檔格式,從而能夠引導多種OS
 通過將Boot Loader所需要的功能封裝成一套腳本語言,從而能夠按照特定的方式引導OS
► Grub的I/O
 支援CHS和LBA兩種磁片訪問模式
 (device[,part-num][,bsd-subpart-letter])的方式訪問設備:(hd0), (hd1, 0), (hd0, a), (hd0, 1, a)
 檔訪問
► 通過路徑形式訪問:/boot/grub/menu.lst
► 通過磁區形式訪問:0+1,200+1,300+300
► Grub的腳本:
 root指定一個啟動的設備
 kernel指定作業系統的內核
 boot正式啟動一個OS
 makeactive啟動一個分區
 chainloader調用啟動設備上的Boot Loader
► 啟動Linux
 root (hd0,0)
 kernel /vmlinuz root=/dev/hda1
 boot
► 啟動Windows
 root (hd0,0)
 chainloader +1
 makeactive
 boot
► Grub的組成
 Stage1
► Grub的第一部分,安裝在MBR或者Boot Sector中
► 用於引導Stage2或者Stage1.5
 Stage2
► Grub的核心影像,用於提供Grub的主要功能
 Stage1.5
► Stage1與Stage2之間的橋樑,安裝在0磁軌上第一個磁區之後
► Stage1不理解檔系統,但是Stage1.5可以
► Stage1.5最終調用Stage2
 nbgrub/pxegrub
► Grub的網路啟動模組
► Stage1的結構
 為了保持和FAT/HPFS BIOS的相容性,所以保存BPB
 在BPB之後的Stage1配置資料區,在安裝的時候被填寫
 在資料區之後,才是代碼段
 最後是0xAA55啟動磁區標誌

► Stage1的流程
 在Stage1的配置資料區中存放了Stage2所在的磁片號、LBA位址以及Stage2的載入地址
 Stage1不需要理解任何的檔系統,只需要根據給出的磁區號,讀入Stage2的第一個磁區即可
 Stage1相當於前面分析的MBR中的Boot Loader
► Stage2的第一部分Start.S
 Start.S存放在Stage2檔的第一個磁區裏面
 Stage2剩餘部分的LBA位址和記憶體的載入位址是放在Start.S的firstlist和lastlist之間的,這個資料段位於Start.S代碼的尾部,在安裝的時候被寫入,稱為Block List
 Block List以全0項結尾
► Stage2的第一部分Start.S
 在Start.S的代碼開始執行的時候,DS:SI所指向的記憶體位址的內容是Stage1中準備好的,用於為INT 13h調用準備參數
 Start.S的功能就是根據Block List,將Stage2剩餘的部分讀入記憶體,然後跳轉到0x8200h處執行Stage2的功能代碼
► Stage2的第二部分ASM.S
 在ASM.S中定義了一系列的函數的實現,包括Grub得主入口函數main
 在main函數中,完成了如下的工作:
► DS = ES = SS = 0
► 建立實模式/BIOS棧,esp = 0x2000 - 0x10,向低位址方向增長
► 轉入保護模式
► 建立並清空保護模式棧
► 調用cmain,進入Grub的C代碼中(Stage2.c)
► 在cmain中,完成了如下的工作:
 設法打開/boot/grub/menu.lst這個配置檔
 根據配置檔,構建用戶功能表
 如果功能表構建成功,則調用run_menu
 如果功能表構建失敗,則調用enter_cmdline
► 問題:檔系統
 在這個時候,Grub已經開始訪問檔系統
 Grub如何對付不同的檔系統?
► Grub中的檔系統層:Disk_IO.C
 Grub中為每一個檔系統提供了一個抽象層
 檔系統用fsys_entry描述(filesys.h)
struct fsys_entry
{
char *name;
int (*mount_func) (void);
int (*read_func) (char *buf, int len);
int (*dir_func) (char *dirname);
void (*close_func) (void);
int (*embed_func) (int *start_sector, int needed_sectors);
};
 總體變數fsys_table包含了Grub支援所有檔系統,通過fsys_table和fsys_type,從而可以以統一的方式訪問不同的檔系統
 Grub中的命令處理:buildin
 Grub支持的每個命令,均有一個buildin和其對應
 這些buildin被定義在buildins.c中
 分析以下3個命令:
 chainloader
 kernel
 boot

► chainloader
 檢查--force標記
 調用grub_open打開檔,這裏的檔用block list表示(+1)
 調用grub_read讀入一個磁區到0000:7C00的位置
 檢查啟動磁區標誌0xAA55
► kernel
 檢查--type和--no_mem_option標誌
 調用load_image,讀入指定的內核檔
► load_image中處理了ELF和A.OUT的各種變形
► load_image通過對於內核檔的分析,識別出被啟動的OS的種類(通過內核檔的Magic Number)
► load_image針對不同種類的OS的內核提供了特定的載入代碼(這裏的代碼異常複雜,牽涉到了不同的OS的實現細節,未作分析)
► 最終填充mbi (MultiBoot Information)結構
► boot
 通過執行chainloader或者kernel以後,當前需要啟動的內核的類型已經確定了
 在執行boot的時候,根據確定的內核類型,每一種內核均有一種啟動的方法
► 對於linux,最後調用了stop函數實現控制權的轉移
► 對於chainloader,最後也是調用了stop函數實現控制權的轉移
► boot
 通過執行chainloader或者kernel以後,當前需要啟動的內核的類型已經確定了
 在執行boot的時候,根據確定的內核類型,每一種內核均有一種啟動的方法
► 對於linux,最後調用了stop函數實現控制權的轉移
► 對於chainloader,最後也是調用了stop函數實現控制權的轉移
--> 閱讀更多...

2010年10月21日 星期四

GBUB4DOS一開始一定是先處理MBR:stage1.S

stage1.s源代碼分析(很詳盡): 文章出處:http://www.civilnet.cn/blog/browse.php?operation=display&type=blog&entryno=85
Stage1.s原始檔案是用古老的at&t彙編編寫而成,是大名鼎鼎的unix家族作業系統引導程式GRUB中的第一個檔。它編譯後產生的二進位碼正好是512位元組(故意的,也是必須的),剛好填充滿硬碟初始的一個磁區,也即0柱面、0磁軌、1磁區。人們稱之為MBR——主引導記錄。它的作用是載入stage2檔(GRLDR)。閱讀本段代碼,gemfield建議你首先具備以下能力:cpu寄存器、BIOS中斷、PC架構、at&t彙編、GRUB背景知識。幸運的,青島之光論壇(bbs.civilnet.cn)的嵌入系統版塊裏都或多或少包含了這些介紹。並且可以從青島之光論壇上查找stage1.s的源代碼,此處不一一羅列了。
程式剛開始處的巨集定義使用了和gcc相同的規範,定義的3個巨集變數在後面用到的地方再由gemfield詳細闡述。在定義了一個總體變數_start後,程式的真正入口就到了。事實上,在二進位碼中,開始部分的代碼是eb48,其中eb就是jmp的機器碼,在標號_start後,緊跟著的就是這個jmp指令,跳轉到after_BPB處。Jmp後的nop指令,恐怕永遠也不會執行了。注意,剛開機時cpu會調用int19h將第一個磁區的內容調到記憶體位址為0x0000:0x7coo處,你要問為什麼是這個地址或者為什麼會發生這樣的調用,原因大抵和usb為什麼是2根資料線是一樣的。
.=_start + 4是一個讓人困惑的語句,其實這個dot是一個特殊的標號,在as彙編規範中,就代表當前的地址。從開始處的_start處填充空間至_start+4處,相當於4個位元組的空間。但是,從_start開始後的jmp nop 和jmp的參數已經佔用了3個位元組的空間,相當於在它們的後面再用0填充1個位元組的空間即可。
後面緊跟的是一系列稱之為彙編directive的“虛擬指令”。這一部分是對磁片等一些參數進行設置。像起始的磁區、磁軌和柱面以及它們的起始地址、還有stage1的版本號、boot_drive變數、force_lba變數、stage2的位址、磁區、段等參數,這在後面的代碼中涉及的時候再由gemfield闡述,到時候gemfield會稱這部分為初始化參數部分,切記。但在這一系列的參數設置中,還有個相似的語句,就是.=_start+STAGE1_BPBEND,照樣是從上一條指令處填充0直至到達_start+0x3e處。
在jmp之後,清中斷允許位,然後陳列80ca這個二進位碼。80ca就相當於orb $ox80,%dl,意思是給dl寄存器賦值80,要知道,在開機初始, BIOS載入完啟動代碼會把%dl寄存器設置成啟動盤號(boot drive number):
***************************************************************
DL = 00h 1st floppy disk ( "drive A:" )
DL = 01h 2nd floppy disk ( "drive B:" )
DL = 80h 1st hard disk
DL = 81h 2nd hard disk
***************************************************************
硬碟的代號是80,所以上面代表的是stage1裝到硬碟上的情形,如果是軟碟的話,就是orb $0x00,%dl,很顯然,軟盤機代號是0x00。關於boot_drive_mask這一部分,包含的ljmp $0,ABS(real_start)指令的意思是,跳轉到cs:ip = 0x0000:$ABS(real_start)這個地方執行指令。程式的開頭部分定義了ABS這個巨集,在此處就相當於real_start-_start+0x7c00。如果是“正常的”int19h中斷,這句就是廢話。因為物理位址是(Segment value * 16) + Offset value,正常情況下MBR被載入到cs:ip = 0x0000:0x7c00上,而有些糟糕的BIOS會將其載入到07c0:0000上,其實這兩個代表的物理位址是完全一樣的(你可以用上上行的公式計算)。有些人從來就不考慮這種事實,那就是大多數人常常把segment值設為0,這樣引導代碼就可以假定任何段寄存器都是0從而只對付ip裏的偏移量。所以,在grub裏,加上這麼一個長轉移,就防止了這類糟糕的BIOS帶來的大麻煩。 接著進入real_start了,ax清零,ds賦值0,ss賦值0,將STAGE1_STACKSEG(0x2000)賦值給sp,這樣就設置了實模式下的堆疊段位址(棧頂位置)ss:sp = 0x0000:0x2000。接著置中斷允許位,然後檢查是否設置了啟動的磁片。先用MOV_MEM_TO_AL宏將boot_drive量存到al中,然後與0xff進行比較,用的是cmpb $0xff,%al ;je 1f。cmpb指令是將兩個運算元進行相減,對標誌位元的影響同sub指令,但是不保存結果。其中,此處用到的是zf標誌位元(因為是je指令),這樣,當運算元相等(即相減為零時)zf被置1。所以,cmpb和je一起使用時,就是指,當運算元相等時,跳轉至je制定的標號。所以,在這裏,若boot_drive等於0xff,則使用BIOS傳遞過來的默認的驅動器進行啟動;如果不是,movb %al,%dl,將boot_drive的值保存至dl中,表示由boot_drive的值確定啟動設備。不管怎麼樣,現在開始正式啟動了……驅動器號資訊壓棧、輸出資訊“GRUB”,注意,在螢幕上輸出資訊時調用了MSG巨集。下面分析一下這個宏,#define MSG(X) movw $ABS(x),%si ;call message 輸出GRUB字樣時,變數是notification_string,相當於將notification_string位址上的16位元內容送入si寄存器,然後調用message函數,而message函數使用了int10中斷來在螢幕上顯示字元。涉及到串操作指令。message函數:lodsb,從%si指向的源位址中逐一讀取一個字元,送入al中,然後檢查al是否為零,如果為零,表示字元已經傳輸完成了(.string虛擬指令會在指定的字串後加入一個位元組的0),此時調用ret返回。而若不為零,表明字元還未傳輸完,此時跳轉到int 10h“中斷前夕”,用int 10h 的oeh子功能在螢幕上以telemode模式寫字元,其中,ah是子功能號,al是字元,bh是頁,bl是前背景色(在圖形模式下)。所以這裏movw $0x0001,%bx ; movb $0x0e,%ah ;int $0x10(顯示一個字元)就ok了。
在螢幕上顯示完GRUB後,要來決定是進入chs模式還是lba模式(也就是看硬碟是否支援LBA模式,因為兩種模式對硬碟的讀寫等操作有很不一樣的地方),但在這之前,你得首先判斷這裏是硬碟而不是軟碟或者根本就沒有盤(言下之意就是,如果不是硬碟,判斷LBA或者CHS模式就沒有意義了),所以,在判斷硬碟是否支援LBA時,先判斷是不是硬碟。這裏用testb $STAGE1_BIOS_HD_FLAG,%dl來判斷,dl寄存器裏裝載的是磁片號,有三大類情況:硬碟(0x80、0x81)、軟碟(0x00、0x01)、無效的盤(0xff)。而前面的宏就是0x80,所以通過testb和jz指令判斷,如果dl中不是80或81(也就是不是硬碟),就跳轉到chs_mode函數下面。另外,如果此處判斷出是硬碟的話,再接著判斷是否支援LBA,使用的工具就是BIOS的int 13h中斷。 通過 BIOS 調用 INT 0x13 來確定是否支援擴展,LBA 擴展功能分兩個子集 , 如下 : 第一個子集提供了訪問大硬碟所必須的功能 , 包括: ****************************************************************
1.檢查擴展是否存在 : ah = 41h , bx = 0x55aa , dl = drive( 0x80 ~ 0xff )
2.擴展讀 : ah = 42h
3.擴展寫 : ah = 43h
4.校驗磁區 : ah = 44h
5.擴展定位 : ah = 47h
6.取得驅動器參數 : ah = 48h
****************************************************************
第二個子集提供了對軟體控制驅動器鎖定和彈出的支援 ,包括: ****************************************************************
1.檢查擴展 : ah = 41h
2.鎖定/解鎖驅動器 : ah = 45h
3.彈出驅動器 : ah = 46h
4.取得驅動器參數 : ah = 48h
5.取得擴展驅動器改變狀態: ah = 49h ****************************************************************
下面開始具體檢測 , 首先檢測擴展是否存在。此時寄存器的值和 BIOS 調用分別是:AH = 0x41,BX = 0x55AA,DL = driver( 0x80 ~ 0xFF ),然後INT 13H,看返回結果:如果支持CF= 0;否則 CF = 1;CF = 0 (支持LBA) 時的寄存器值代表含義: ****************************************************************
ah:擴展功能的主版本號( major version of extensions )
al:內部使用( internal use )
bx :AA55h ( magic number )
cx:Bits Description
0 extended disk access functions
1 removable drive controller functions supported
2 enhanced disk drive (EDD) functions (AH=48h,AH=4Eh) supported.
Extended drive parameter table is valid
3~15 reserved (0) CF = 1 (不支持LBA) 時的寄存器值 :
ah = 0x01 ( invalid function )
****************************************************************
現在stage1.s使用movb $0x41, %ah;movw $0x55aa, %bx;int $0x13; jc chs_mode來進行上述判斷。如果不支援LBA,則cf就是1,跳轉到chs_mode函數運行。有的bios的int 13h中斷會影響到dl,所以此處用pop和push指令將其保護起來。然而cf不等於1也不表示就支持LBA了,還得再判斷bx是不是aa55h,使用cmpb $0xaa55,%bx ;jne chs_mode再判斷一次,如果bx裏存的不是預期的返回值,同樣不支持lba,也要進入chs_mode函數。這裏有個強制LBA模式要注意下,就是說,當cf是1,bx也是aa55,那麼可以不用在判斷就進入強制LBA模式,代碼是這樣寫的,使用MOV_MEM_TO_AL巨集將force_lba變數值傳遞到al,判斷是否為0。不為零強行進入lba_mode函數。然後判斷cx,如果cx為0的話表明不支持擴展第一子集,這時也進入chs_mode函數。所以總結進入chs_mode的情況,如下: *****************************************************************
第一、 磁片號非80h或81h,進入chs_mode
第二、 int13h,41h子功能,返回cf為0,進入進入chs_mode
第三、 int13h,41h子功能,返回bx不為aa55,進入chs_mode
第四、 如果沒有設置強制LBA,而且也不支持擴展第一子集,進入chs_mode
第五、 其他情況,進入lba模式。 *****************************************************************
那我們就先來分析進入chs模式的代碼,你看,我們是以以上種種情況的發生而進入chs模式的,所以進入chs模式時,再來進行一些檢測,來確定具體的情況。首先就是int13h的08功能號的使用。使用08功能可以檢測chs模式中硬碟的參數,保存在各寄存器裏: *****************************************************************
DL:本機軟碟驅動器的數目
DH:最大磁頭號(或說磁面數目)。0表示有1個磁面,1表示有2個磁面
CH:存放10位元磁軌柱面數的低8位(高2位在CL的D7、D6中)。1表示有1個柱面,2表示有2個柱面,依次類推。
CL:0~5位元存放每磁軌的磁區數目。6和7位元表示10位元磁軌柱面數的高2位。
AX=0
BH=0
BL表示驅動器類型:
1=360K 5.25
2=1.2M 5.25
3=720K 3.5
4=1.44M 3.5
ES:SI 指向軟碟參數表
******************************************************************
如果成功返回參數,則進入final_init函數;但是如果調用失敗,進位元標誌CF=1,AH存放錯誤資訊碼。表明不支援硬碟的chs模式(前面也判斷了不支持lba),那就要考慮是不是軟碟了。再使用testb和jz指令,若dl是00或01,則認為是軟碟,就跳轉到floppy_probe函數執行(後文討論此函數)。但是若連軟碟也不是,只好準備報錯了。跳轉到hd_probe_error函數,這個函數調用MSG函數連同general_error函數一道輸出“hard disk error”的字元。
好了,現在我們回來。剛開始經過一些列的判斷,我們進入了LBA模式。然後,代碼做了以下工作,movl 0x10(%si),%ecx,這個代碼就是個廢話,ecx寄存器被置入了一個無意義的值;然後將標號disk_address_packet處的位址賦給si,再接著將[si-1]記憶體處置1(也就是mode被置1,表示LBA擴展讀;如果是0,就是CHS定址讀)、將stage2的磁區數賦予ebx、在[si]和[si+1]處存放10和00(movw $0x0010,(si))、在[si+2]和[si+3]處存放01和00、在[si+4]和[si+5]處存放00和00、在[si+6]和[si+7]處存放0x00和0x70(這是stage1_bufferseg的值)、在[si+8][si+9][si+A][si+B]處存放0x01/0x00/0x00/0x00、在[si+c][si+d][si+e][si+f]處存放0x00/0x00/0x00/0x00。設置完畢後,開始調用int 13h的42功能中斷。如果出錯,就跳轉到chs_mode處。那麼中斷執行成功呢?
由si及其偏移量指向的記憶體保存著磁片參數塊,如下: ******************************************************************
偏移量 大小 位數 描述
00h BYTE 8 資料塊的大小 (10h or 18h)
01h BYTE 8 保留,必須為0
02h WORD 16 傳輸資料塊數,傳輸完成後保存傳輸的塊數
04h DWORD 32 傳輸時的資料緩存位址
08h QWORD 64 起始絕對磁區號(即起始磁區的LBA號碼) ******************************************************************
所以,通過int13h(42)中斷的作用,硬碟上第二個磁區上的內容就被讀到由si偏移量為4h、5h、6h、7h確定的記憶體區域上了,此處是0x7000:0x0000。執行成功,將bx賦值0x7000,然後跳至copy_buffer子函數處。
LBA已完,gemfield在閱讀copy_buffer前再回頭看當初程式跳至chs_mode後是怎麼運行的。上文中已經指出了,到達chs_mode後經過條件判斷,一共產生了三種情況,第一是進入硬碟的chs子函數(final_init);第二是進入軟碟副程式(floppy_probe);第三種情況是進入報錯子函數,在螢幕上輸出一系列錯誤。那就由gemfield從第一種情況開始吧。程式運行到final_init後,將磁區數保存到si、設置mode為0、eax清零為存放磁頭數做準備、將dh中存放的磁頭數保存到al中、使用incw %ax指令(因為磁頭數是以0~n-1方式排列的,所以增1後才是真正的磁頭數)、將磁頭數保存至[si+4][si+5][si+6][si+7]記憶體位址上、清dx為存放磁區數做準備、cl中的0~5位元存放的是磁區數,所以dx邏輯左移2位元後在dh中出現的兩位就是柱面數的高2位,並且把這2位移到ah中,而ch存放的柱面數低8為移至al中,這樣ax裏就是柱面數了,這裏因為同樣的道理要進行incw %ax操作,並且把真正的柱面數放到位址為[si+8][si+9]的記憶體上、然後用同樣的移動方法產生真正的磁區數並保存在位址為[si][si+1][si+2][si+3]的記憶體上。
然後在使用int 13h(0x02)功能前要進行必備的參數設置:eax存放stage2的磁區編號(stage2_sector,默認為1)、清edx寄存器、然後通過(stage2磁區數)/(磁區數)獲得引導磁區數。注意對於div指令來說,eax恒定存放被除數,div後面的寄存器存放的是除數。餘數在edx中存放,第一個餘數(磁區數)放到位址為[si+10]的記憶體上並將edx清零、再用(上一步除法的商) /(磁頭數)得到的餘數為磁頭數,存放在[si+11]記憶體位址上。商為柱面數並存放在eax中並同時保存至[si+12][si+13]記憶體位址上。然後將之前中斷獲得的柱面數與此處stage2所占柱面數相比較,如果stage2柱面數大,那麼明顯錯誤,程式將跳至geometry_error處。
現在,將[si+13]的內容賦值給dl(柱面數的高2位)並且左移6位元、將磁區數放到cl中再增1、然後通過orb %dl,%cl和movb 12(%si),%dh指令達到這麼一種情況,即:cl中存放的是磁區數和柱面數的高2位,ch中存放的是柱面數的低8位、然後恢復驅動器號(popw %dx)、然後將磁頭數放置到dh中,然後將0x7000賦值給es並將bx清零,賦值0x0201給ax(獲得中斷功能號),參數現在設置完畢,開始調用int 13h中斷: ******************************************************************
%al = number of sectors(需要讀的磁區數)
%ah= 0x02(功能號)
%ch = cylinder(起始柱面數)
%cl = sector (bits 6-7 are high bits of "cylinder")
%dh = head
%dl = drive (0x80 for hard disk, 0x0 for floppy disk)
%es:%bx = segment:offset of buffer ******************************************************************
調用中斷後,將0柱面、0磁軌、2磁區的內容讀到0x7000:0x0000記憶體處。然後程式跳轉至copy_buffer處,和LBA殊途同歸呀。
我們看看copy_buffer做了什麼。將0x8000賦值給es、給cx賦值0x100、給ds賦值0x7000、si和di清零、方向標誌DF置零,然後使用rep和movsw指令將ds:si處連續的512位元組內容傳輸到es:di指定的記憶體位址(0x8000:0x0000)。其中,rep指令的含義就是重複執行後一句指令,沒執行一次。cx減1,直至cx為0。這也是前面cx賦值0x100(256)的原因。movsw每次傳輸一個字,256次就是512位元組。然後popw %ds; popa還原寄存器。
接著,程式跳轉到0x8000處繼續執行,到此就開始執行新的模組了,stage1的任務也已經結束了。代碼中*(stage2_address)的星號是at&t彙編的規範:絕對跳轉/調用(相對於與程式計數器有關的跳轉/調用)運算元前面要加星號"*"。
然而,前面所述的chs模式中的第二種情況——軟盤機情況將會帶領gemfield進入floppy_probe子函數,此處要使用int 13h(0x00功能號)來進行軟盤機的復位。成功的話cf=0;然後準備調用int 13h(功能號是0x02),這和chs中的int 13h,ah=0x02是一樣的。所以,先來為中斷準備必須的參數:軟盤機復位後,將[si]處的值賦給cl(cl是起始磁區數),我們知道,由於迴圈,我們給了cl 4次機會,因為迴圈中有incw %si指令,所以si中的值是遞增的,從probe_values開始,在每一次的機會中依次給cl賦予了0x24、0x12、0x0f、0x09這幾種值,當然,試完後還不對的話就要執行報錯函數了。
像以前那樣,依次準備好bx、ah、al、ch、cl、dh的值後,就要int 13h了。成功後,dh賦值1、ch賦值0x4f,dh 設置為 79 , 表示柱面最大值為 79(80柱:0~79),dh 設置為 1 , 表示磁頭數最大值為 1(2頭:0~1),然後跳轉至 final_init,在上文中關於final_init的分析 , 我們知道保存時會把柱面和磁頭分別加 1 , 磁區不變,因此 , 在軟碟載入時 , 將設置 Cylinder : Head : Sector = 80 : 2 : start_sector。最終就跳轉至final_init函數處執行了。
gemfield的本文中,依然要注意的還有為了相容性而設置的windows nt魔術頭標識的偏移、part_start作為標識的分區表起始位址的標記的偏移、以及引導磁區結束標誌0xaa55。
總的說來,在gemfield這篇稍顯淩亂的文章裏,主要介紹了stage1.s的使命,簡介來說,就是開機時首先被BIOS INT19H裝載到記憶體0x7c00處,然後判斷chs和lba模式,然後使用int13h中斷將磁片上第二磁區的內容讀到0x7000處,然後通過子函數copy_buffer再將其調到0x8000的位置上,這個第二磁區的內容就是以後gemfield的嵌入系統版塊中將要介紹的start.s模組。
--> 閱讀更多...

2010年10月20日 星期三

GRUB2太龐大,所以先轉戰GRUB4DOS

圖一
圖二
下載http://nufans.net/grub4dos/current_release/ 之grub4dos-0.4.4-2009-10-16-src.zip,在我的ubuntu910 下./configure結果產生錯誤如下:
checking whether objcopy works for absolute addresses... no
configure: error: GRUB requires a working absolute objcopy; upgrade your binutils
結果查了一下我的binutils版本已經是最新的。隨後又到其他連結download其它版本的source code,編譯一樣產生相同錯誤。所以本來想放棄,後來想說反正我的virtualbox裡頭還有ubuntu710版本,其gcc也相對比較舊。果不其然原來是binutils太新了,就這樣可以正常編譯成功了。後來我又試了其他版本的binutils 其objcopy版本只要大於GNU objcopy (GNU Binutils for Ubuntu) 2.18.0,則執行./configure就會出錯。
它的使用方法網路上已經很多了在此就不贅述。還是來研究它的source code,比較實際。
首先進入source code目錄,並下make clean及make distclean,接著再作一次./configure並產生Makefile。查看目錄結構如圖一,圖二為其他一起產生的子目錄Makefile;今天我們先說明這個Makefile如何產生的吧!其實這個在之前已經說明過,而且又查獲此處講解autoconf及automake,說明的太詳細了,所以直接參考它就可以了。
--> 閱讀更多...

2010年10月3日 星期日

Bochs install for smp



Bochs安裝在windows時從http://sourceforge.net/projects/bochs/files/bochs/2.4.5/下載下來的Bochs-2.4.5.exe 雖然可以直接使用,而且還很好用,在windows下若要使用debug可以呼叫bochsdbg.exe,正常使用狀況一般都呼叫bochs.exe;並針對配置檔:bochsrc.bxrc作修改便可以使用。但是今天我的須求必須能夠執行在smp的狀況下;然而使用Bochs-2.4.5.exe 所安裝的版本是不支援smp的,因此必須下載source code,自己編譯及安裝,由於sourceforge提供了bochs-2.4.5-msvc-src.zip for windows vs2008的source code,下載下來安裝總是失敗,我想應該是什麼選項沒有去打開所造成的;這部分等我以後編譯成功再來說明。也因為沒有辦法在windows下使用smp並作debug,因此我便下載bochs-2.4.5.tar.gz ,並到linux下去build;以下是building and install的過程說明:
要在linux下安裝bochs首先必須先安裝幾個套件:g++、libc6-dev、build-essential、xorg-dev、libgtk2.0-dev;若是你使用ubuntu的Synaptic可以很快速的將這些套件安裝完成;若你是使用red hat linux,則請自行找到相對應的rpm檔。
或安裝build-essential
我們知道編譯不過是幾個指令便可完成:./configure及make和make install。然而我們必須清楚到底我們要安裝哪些功能;
因此我們可以先下./configure --help從畫面上便可得知可設定的選項有哪些。所以我為了能夠順利編譯for SMP功能的bochs;所下指令如下:
(1)./configure --enable-smp --enable-x2apic --enable-debugger --enable-disasm --enable-vmx=2 --enable-configurable-msrs --enable-x86-64 --enable-a20-pin --enable-acpi --enable-pci --enable-gdb-stub
(2)make
(3)sudo make install
註:--enable-gdb-stub--enable-debugger是互斥的
install完成時,在/usr/local/bin會有三個執行檔:bochs bxcommit bximage;但和windows狀況下不一樣的是少了bochsdbg;其實應該說debugger的功能也整合到bochs執行檔了。
接下來是如何使用bochs;首先是建立一個image檔,這部分可參考很多地方,請自行搞定;接著修改bochsrc配置檔:由於我的例子是使用floppy來boot,若是有使用ide boot請自行修正;
###############################################################
# bochsrc begin.
###############################################################
# how much memory the emulated machine will have
megs: 32cpu: count=2, ips=10000000# filename of ROM images
romimage: file=$BXSHARE/BIOS-bochs-latestvgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest
# what disk images will be used
floppya: 1_44=MultiCore.img, status=inserted

# hard disk
# ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
# ata0-master: type=disk, path="hd10meg.img", cylinders=306, heads=4, spt=17

# choose the boot disk.
boot: a

# default config interface is textconfig.
#config_interface: textconfig
#config_interface: wx

#display_library: x
# other choices: win32 sdl wx carbon amigaos beos macintosh nogui rfb term svga

# where do we send log messages?
log: bochsout.txt
############################################################### bochsrc end.
###############################################################
有了cpu: count=2, ips=10000000,這一行,便可產生雙核心了。
只要到含有bochsrc檔的目錄下執行以下指令便可開始進行debug了!
--> 閱讀更多...