2010年10月25日 星期一

Grub for dos bootloader 分析



概述一下:


Grubfordos0.4.4的主題為三部分:MBR,bootloader,kernel
1:MBR對應的檔為stage1目錄下的stage1.S .S尾碼為GAS彙編原始檔案
Stage1:大小512位元組,編譯後的結構為DBR結構,這樣的優點是,無論你將GRUB安裝在MBR還是分區的DBR中,都能正常引導,如果安裝在硬碟的MBR中,那麼bios的int19號中斷會MBR中的內容讀入到記憶體0x7C00處,而stage1的功能只是為了將start.S編譯後存放在MBR 1(LBA)磁區的地方二進位檔載入記憶體的0x8000處後跳轉到該處執行,當然,在載入記憶體之前使用bios的int13號中斷,或者int 13擴展調用讀到緩衝區後在拷貝到相應的記憶體位址處;
2:bootload 對應檔start.S
由start1載入到記憶體0x8000處的代碼功能充當bootload的功能,之所以不寫在stage1中,是受硬碟每磁區存儲位元組數的限制,而MBR除引導代碼外還保存有分區表資訊,因此,MBR中可用的位元組數就更少了,而DBR中還應該保持BPB資料結構類型,故此,將loader單獨編譯一個磁區是情有可言的;s
Start的功能:
在末尾-8偏移的地方保存有stage2存放的磁區號(LBA)以及大小,start1只是將對應kernel載入記憶體位址0x8200處,因為start編譯後大小為512位元組,故此占了0x200,當stage2被載入完成後,就jmp到0x8200處執行,這時,真正的Grub代碼才開始;
Stage2: 相當於一個小型的作業系統,grub提供多引導支援,grub對檔系統的支援較為全面,而一個作業系統,最複雜而核心的部分恐怕在於檔系統的實現上,故此,分析grub,
無論是對想瞭解作業系統原理還是檔系統原理的人來說,應該有一定的幫助;

關於位址轉換:
磁區的編號和線性位址:
由於int 13的限制,三維位址C/H/S的最大值為1024/16/63,因此容量只能達到:1024*16*63*512Byte=504MB;故因此系統把所有的無聊磁區都按某種方式或規則看做是線性編號的磁區,從0到某一最大值;
磁區編號:
C/H/S : 磁區編號從1開始至63
LBA : 磁區編號從0開始順序編號
C/H/S到線性位址的轉換:
C : 當前柱面號 CS:起始柱面號
H : 當前磁頭號 HS:起始磁頭號
S : 當前磁區號 SS:起始磁區號
PH: 每柱面有多少個磁軌
PS: 每磁軌有多少個磁區
則有公式: LBA = ( C - CS)*PH*PS +(H - HS)*PS + (S - SS)
一般情況下: CS = 0 HS = 0 SS = 1 PS = 63 PH = 255
而LBA到C/H/S的轉換稍微複雜一些,各變數按上述,有如下公式:
C = LBA div (PH*PS) + CS
H = (LBA div PS) MOD PH + HS
S = LBA MOD PS + SS
如果只用DIV,則轉換公式如下:
C = LBA div (PH*PS) + CS
H = (LBA div PS) – (C-CS)*PH + HS
S = LBA – (C-CS)*PH*PS – (H-HS)*PS +SS

/*預處理包含定義
*其中STAGE1_5被自GRUB2後不使用
*用到的share.h被其他一些原始檔案使用,
*此檔使用到的宏不多,用到時給與說明
******************************/

#define ASM_FILE
#include

#ifndef STAGE1_5
#include
#endif

/*
* defines for the code go here
*/

/* Absolute addresses
This makes the assembler generate the address without support
from the linker. (ELF can't relocate 16-bit addresses!) */

/*重定位,並根據是否定義STAGE1_5選擇裝入位址*/

#ifdef STAGE1_5
# define ABS(x) (x-_start+0x2000)
#else
# define ABS(x) (x-_start+0x8000)
#endif /* STAGE1_5 */

/* 調用int 10h中斷以印表機模式在螢幕上顯示一字串*/

#define MSG(x) movw $ABS(x), %si; call message

/*****************************************
.file op 指定和目標檔關聯的原始檔案名
*****************************************/

.file "start.S"

/*****************************************
.text代碼段聲明; .code16使用實模式
*****************************************/

.text

/* 告訴GAS產生16位元指令,以便此代碼能在實模式下運行 */
.code16

.globl start, _start
start:
_start:
/*start由stage1載入到0x8000處,si仍然用於DiskAddressPacket結構
*
/* 首先保存設備類型號,stage1由bios初始化在dl中! */

pushw %dx

/* 在螢幕上列印字串:loading stage2 or loading stage1_5
*由於在輸出字元的時候使用了si寄存器,故此保存原si位址
*字串輸出完畢後恢復原字串*/

pushw %si
MSG(notification_string)
popw %si

/*** #define BOOTSEC_LISTSIZE 8" firstlist為此段末尾偏移位址*/

movw $ABS(firstlist - BOOTSEC_LISTSIZE), %di

/* LBA以0開始,故此,stage1占1磁區,start占一磁區,故stage2從2開始,並存 倒數第8個位元組處。 */
movl (%di), %ebp

/*由於firstlist -08h處保存著需要載入的磁區號和磁區的數目,故此,
* bootloop 子功能,迴圈將對應的磁區號中的資料載入相應記憶體中並執行,
*可能有人會問,為什麼不將stage1和start融合在一起呢,這是因為一般的
* 硬碟低格後,默認的磁區大小為512個位元組,也就是分開也是不得於而為之
*/

bootloop:

/* [edi]+4處為 .word (STAGE2_SIZE + 511) >> 9,這裏不考慮stage1_5的情況
* STAGE2_SIZE為grub編譯後的大小,>>9相當於除以512,
*4(%di)處存的是stage2占的磁區數目/

cmpw $0, 4(%di)

/* 如果為0說明使用stage1_5直接跳至bootit處執行 */
je bootit

setup_sectors:
/* stage1中,如果檢測支援LBA模式,此語句被執行:movb $1, -1(%si)
* 避免了相同功能的重複調用/

cmpb $0, -1(%si)

/* 結果為0,說明不支援LBA模式 */
je chs_mode

lba_mode:
/* 裝載邏輯磁區號入ebx */
movl (%di), %ebx


/* the maximum is limited to 0x7f because of Phoenix EDD
看一下int 43的調用的參數說明:
Format of disk address packet:
Offset Size Description
00h BYTE 10h (size of packet)
01h BYTE reserved (0)
02h WORD number of blocks to transfer (max 007Fh for Phoenix EDD)
04h DWORD -> transfer buffer
08h QWORD starting absolute block number
(for non-LBA devices, compute as
(Cylinder*NumHeads + SelectedHead) * SectorPerTrack +
SelectedSector - 1
--------N-134257DX1234----------------------- */

xorl %eax, %eax
movb $0x7f, %al

/* 比較應該讀的資料數否超過最大傳輸參數 */

cmpw %ax, 4(%di) /* compare against total number of sectors */

/* 如果大於,跳至本句後一個標號 1:處,否則將值付給ax寄存器 */

jg 1f

/* if less than, set to total */
movw 4(%di), %ax

1:
/* 用stage2的總大小減去ax中的值,ax的值為上面的判斷後的設置值 */

subw %ax, 4(%di)

/* 將ax的值加上起始磁區號,因為下面的代碼會將ax中保存的磁區數目迴圈讀出
* 這樣,即使要讀的資料超出7f也不用在定義其他的資料來保存剩下的磁區數
* /

addl %eax, (%di)

/* 設置磁片位址資料包結構體 */

/* the size and the reserved byte */
movw $0x0010, (%si)

/* 需要讀出的磁區數目,見到ax的用處了吧! */
movw %ax, 2(%si)

/* 絕對磁區位址,(low 32 bits) */
movl %ebx, 8(%si)

/* 緩衝區段位址設置,讀出的資料保存於此處 #define BUFFERSEG RAW_SEG (0x7000)*/

movw $BUFFERSEG, 6(%si)

/* 保存ax的值 */
pushw %ax

/* zero %eax */
xorl %eax, %eax

/* 緩衝區偏移位址設置 == 0 */
movw %ax, 4(%si)

/* 高32位絕對位址 (high 32 bits) */
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

jc read_error

movw $BUFFERSEG, %bx /*緩衝區位址保存*/
jmp copy_buffer /*拷貝讀出的資料*/

chs_mode:
/* lBA磁區號送eax寄存器 ,需要將LBA轉換為CHS位址在讀寫*/
movl (%di), %eax

/* edx填零*/
xorl %edx, %edx

/* divide by number of sectors */
divl (%si)

/* save sector start */
movb %dl, 10(%si)

xorl %edx, %edx /* zero %edx */
divl 4(%si) /* divide by number of heads */

/* save head start */
movb %dl, 11(%si)

/* save cylinder start */
movw %ax, 12(%si)

/* do we need too many cylinders? */
cmpw 8(%si), %ax
jge geometry_error

/* determine the maximum sector length of this read */
movw (%si), %ax /* get number of sectors per track/head */

/* subtract sector start */
subb 10(%si), %al

/* how many do we really want to read? */
cmpw %ax, 4(%di) /* compare against total number of sectors */


/* which is greater? */
jg 2f

/* if less than, set to total */
movw 4(%di), %ax

2:
/* subtract from total */
subw %ax, 4(%di)

/* add into logical sector start */
addl %eax, (%di)

/*
* This is the loop for taking care of BIOS geometry translation (ugh!)
*/

/* 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
pushw %dx

/* head number */
movb 11(%si), %dh

pushw %ax /* save %ax from destruction! */

/*
* BIOS call "INT 0x13 Function 0x2" to read sectors from disk into memory
* Call with %ah = 0x2
* %al = number of sectors
* %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
* Return:
* %al = 0x0 on success; err code on failure
*/

movw $BUFFERSEG, %bx
movw %bx, %es /* load %es segment with disk buffer */

xorw %bx, %bx /* %bx = 0, put it at 0 in the segment */
movb $0x2, %ah /* function 2 */
int $0x13

jc read_error

/* save source segment */
movw %es, %bx

copy_buffer:

/* load addresses for copy from disk buffer to destination */

/*附加段數據初始化。。
* blocklist_default_seg:
* #ifdef STAGE1_5
* .word 0x220
* #else
* .word 0x820 段的開始位址,*16後為 8200,其中8000 - 8200被start佔用
******************************************************************** */

movw 6(%di), %es /* load destination segment */

/* 讀了幾個磁區,吐出來。。 */
popw %ax

/* determine the next possible destination address (presuming
512 byte sectors!) */

//邏輯左移,末尾補零,移1位相當於算術*2,這裏以16進制算出下一次拷貝的起始位址;
shlw $5, %ax /* shift %ax five bits to the left */
addw %ax, 6(%di) /* add the corrected value to the destination
address for next time */

/* 環境保存,因為MSG調用int10中斷後可能會改變寄存器的值 */
pusha
pushw %ds

/* 由於開始用>>9 ,故此,兩次shl 移動的位數為9,從而得到讀出的位元組數 */
shlw $4, %ax
movw %ax, %cx

xorw %di, %di /* zero offset of destination addresses */
xorw %si, %si /* zero offset of source addresses */
movw %bx, %ds /* 讀出後保存於此movw $BUFFERSEG, %bx 緩衝區位址保存
* 這裏得分開,BUFFERSEG為0x7000 ,保存到16位的bx中取的是0x70,注意AT&T和MASM的位元組序/


cld /* 自增方向標誌*/

/* 重複拷貝,每次一個位元組,拷貝到0x8200處 */
rep /* sets a repeat */
movsb /* this runs the actual copy */

/* restore addressing regs and print a dot with correct DS
(MSG modifies SI, which is saved, and unused AX and BX) */
popw %ds
MSG(notification_step)
popa /*環境恢復*/

/*檢查資料是否已經讀完,如果沒有,繼續讀取並拷貝 */
cmpw $0, 4(%di)
jne setup_sectors

/* update position to load from */
subw $BOOTSEC_LISTSIZE, %di

/* jump to bootloop */
jmp bootloop

/* END OF MAIN LOOP */

bootit:
/* 列印\r\n後,恢復寄存器dx的值,根據是否定義了STAGE1_5跳到相應的起始位址處執行裝入的stage2的內容 */

MSG(notification_done)
popw %dx /* 保證dl寄存器的內容為boot設置的初始值 */


#ifdef STAGE1_5
ljmp $0, $0x2200
#else /* ! STAGE1_5 */
ljmp $0, $0x8200
#endif /* ! STAGE1_5 */


/*
* BIOS Geometry translation error (past the end of the disk geometry!).
*/

/*錯誤處理代碼,出錯後執行,stop : jmp stop 閉環;*/
geometry_error:
MSG(geometry_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

#ifdef STAGE1_5
notification_string: .string "Loading stage1.5"
#else
notification_string: .string "Loading stage2"
#endif

notification_step: .string "."
notification_done: .string "\r\n"

geometry_error_string: .string "Geom"
read_error_string: .string "Read"
general_error_string: .string " Error"

/*
* message: write the string pointed to by %si
*
* WARNING: trashes %si, %ax, and %bx
*/

/*
* Use BIOS "int 10H Function 0Eh" to write character in teletype mode
* %ah = 0xe %al = character
* %bh = page %bl = foreground color (graphics modes)
*/
1:
movw $0x0001, %bx
movb $0xe, %ah
int $0x10 /* display a byte */

incw %si
message:
movb (%si), %al
cmpb $0, %al
jne 1b /* if not end of string, jmp to display */
ret
lastlist:

/*
* This area is an empty space between the main body of code below which
* grows up (fixed after compilation, but between releases it may change
* in size easily), and the lists of sectors to read, which grows down
* from a fixed top location.
*/

.word 0
.word 0

. = _start + 0x200 - BOOTSEC_LISTSIZE

/* fill the first data listing with the default */
blocklist_default_start:
.long 2 /* this is the sector start parameter, in logical
sectors from the start of the disk, sector 0 */
blocklist_default_len:
/* this is the number of sectors to read */
#ifdef STAGE1_5
.word 0 /* the command "install" will fill this up */
#else
.word (STAGE2_SIZE + 511) >> 9
#endif
blocklist_default_seg:
#ifdef STAGE1_5
.word 0x220
#else
.word 0x820 /* this is the segment of the starting address
to load the data into */
#endif

firstlist: /* this label has to be after the list data!!! */

由於是開源軟體,以及上傳的大小限制,源代碼就不上來了,大家google一下吧!

沒有留言:

張貼留言