note: the actual listing and offset may be different for some versions of grub. for this paper, i'm using grub (GNU GRUB 0.96) which came as a source package when i installed x86 stage 3 gentoo.1
please read grub's stage1.S source for all you intelligent people out there. it all began when i became curious as to how bootsectors work. at the time i first extracted my disk's master boot record, i didn't know that it was grub's bootsector ( stage1) i was playing with.
get the boot sector from my disk
# dd if=/dev/hda of=grub_stage1.img bs=512 count=1
disassemble it with correct load address (0000:7C00h) and correct synchronization point.
$ ndisasm -o 31744 -s 31818 grub_stage1.img > grub_stage1.dasm
; sun 02/26/06
00007C00 EB48 jmp short 0x7c4a 00007C02 90 nop
things started off with a 2 byte short jmp instruction to offset 0x7C4A followed by a one byte nop. the first three bytes therefore are EB4890. i was wondering why many bytes after it were just nulls. this part of the boot sector is known as the BPB or the Bios Parameter Block and it is filled with useful data that examines the MBR as a normal Volume Boot Record. i figured i'd go back to this block later so i followed the jmp and landed on a cli instruction.
the cli or "clear interrupt flag's" purpose is to disable hardware interrupts while the environment is being setup. (updating of the segment registers)
00007C4A FA cli ; disable interrupt 00007C4B 80CA80 or dl,0x80 ; boot drive byte 00007C4E EA537C0000 jmp 0x0:0x7c53 ; jmp to next inst 00007C53 31C0 xor ax,ax ; zero ax 00007C55 8ED8 mov ds,ax ; zero data segment 00007C57 8ED0 mov ss,ax ; zero stack segment 00007C59 BC0020 mov sp,0x2000 ; sp = 8 byte boundary 00007C5C FB sti ; enable interrup
from what i've been reading, the or instruction at address 0x7C4B is supposed to correct the boot drive byte that is passed by "buggy bioses". setting it to 0x80 means that we are going to boot from our first hard drive. this is used by the int 13h instruction later on. at this point, it would be a good time to read about the int 13h interrupt call. at this point, code, data, and stack are all initialized to the same segment. then the stack pointer is placed offset 0x2000 , so the new stack starts at address 0000:2000h now.
after all these, a sti or "set interrupt flag" is issued enabling all hardware interrupts again. we move futher...
00007C5D A0407C mov al,[0x7c40] ; get boot drive 00007C60 3CFF cmp al,0xff ; is it 255 00007C62 7402 jz 0x7c66 ; yes! skip next inst 00007C64 88C2 mov dl,al ; no! copy boot drive 00007C66 52 push dx ; save boot drive 00007C67 BE797D mov si,0x7d79 ; point to string 00007C6A E83401 call 0x7da1 ; display it
the first instruction compares the value at address 0x7C40 which is inside the Bios Parameter Block by the way with 0xff (255) and skips two bytes to the instruction at 0x7C66 (push dx) if it matches. address 0x7C40 contains the boot drive byte. remember that at this point, dl is already 0x80. the value at that address happens to be 0xff and this sets the zero flag e ffectively skipping the instruction mov dl, al which copies the boot drive byte in address 0x7C40 to dl. it then sets up a the source index to point to the string "GRUB" and transfers control to a function that displays the string. outlined below.
00007D9A BB0100 mov bx,0x1 ; bh = page number 00007D9D B40E mov ah,0xe ; ah = write 00007D9F CD10 int 0x10 ; call bios 00007DA1 AC lodsb ; fill al 00007DA2 3C00 cmp al,0x0 ; al is null? 00007DA4 75F4 jnz 0x7d9a ; no. loop. 00007DA6 C3 ret ; return
this is basically a function that prints a string one letter at a time until it reaches a null byte terminator.
; tue 02/28/06
continue execution starting at the instruction at address 00007C6D.
open a a browser window for http://lrs.fmi.uni-passau.de/support/doc/interrupt-57/INT-13.HTM read about functions ah = 08h, 41h and 42h. now! from here on, it gets dirty and ugly.
; decide whether to probe for LBA or use CHS instead.
00007C6D F6C280 test dl,0x80 ; are we booting from HD?
00007C70 7454 jz 0x7cc6 ; nope. from floppy
; probe for LBA
00007C72 B441 mov ah,0x41 ; yes! function 41h
00007C74 BBAA55 mov bx,0x55aa ; by definition
00007C77 CD13 int 0x13 ; IMB/MS installation check
; CF set on error (extensions not supported
; 21h = 2.1 / EDD-1.1
; 30h = EDD-3.0
; al = internal use
; cx = API subset support bitmap
; dh = extension version
00007C79 5A pop dx ; restore boot drive byte
00007C7A 52 push dx ; save a copy of it aswell
00007C7B 7249 jc 0x7cc6 ; carry flag set. failed!
; get disk geometry.
00007C7D 81FB55AA cmp bx,0xaa55 ; IBM/MS installed?
00007C81 7543 jnz 0x7cc6 ; no. get disk geometry.
00007C83 A0417C mov al,[0x7c41] ; yes! force LBA mode byte.
00007C86 84C0 test al,al ; LBA mode byte is set?
00007C88 7505 jnz 0x7c8f ; yes!
00007C8A 83E101 and cx,byte +0x1 ; removable drive controller funcs supported
00007C8D 7437 jz 0x7cc6 ; no. get disk geometry.
wed 03/01/06
the code block above essentially determines how to refer to sectors of the disk (hard/floppy). whether to use LBA mode (hard/floppy) access or to use CHS mode (only for floppies) access. CHS is a shorthand term for cylinder:head:sector. the mbr, or grub stage one for this matter is located on 0:0:1. that is, cylinder zero, head zero, sector one of the first hard drive. lba (large block addressing) mode requires the use of a space in the bios parameter block known as the disk address packet.
taken from grub 0.96 stage1.S
-- snip --
. = _start + 4
/* scratch space */
mode:
.byte 0
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... */
. = _start + STAGE1_BPBEND
/*
* End of BIOS parameter block.
*/so it has a one byte ?marker? for lba mode. then the disk address packet proper, starts at byte 5 (0000:7C05h). and it holds a dword buffer for sectors , another dword buffer for heads, a word buffer for cylinders, a byte that determines the starting sector, another byte for the starting head, and lastly, a word buffer for the starting cynlinder. now that i have something to work with, i went back to the disassembly.
; lba mode 00007C8F 668B4C10 mov ecx,[si+0x10] ; max sectors = 1st cylinder 00007C93 BE057C mov si,0x7c05 ; si = addr of disk Packet 00007C96 C644FF01 mov byte [si-0x1],0x1 ; zero mode byte 00007C9A 668B1E447C mov ebx,[0x7c44] ; ebx = location of stage 2 code 00007C9F C7041000 mov word [si],0x10 ; 0x10 = size of packet 00007CA3 C744020100 mov word [si+0x2],0x1 ; 1 block transfer 00007CA8 66895C08 mov [si+0x8],ebx ; starting absolute block number 00007CAC C744060070 mov word [si+0x6],0x700 ; segment of buffer address 00007CB1 6631C0 xor eax,eax ; null eax 00007CB4 894404 mov [si+0x4],ax ; null transfer buffer 00007CB7 6689440C mov [si+0xc],eax ; i have no idea what this is for! ; at this point, dl holds drive number (0x80) ; es:si points to the start of the disk address packet (see mov si, 0x7c05 above) ; read this to understand more about the disk address packet and "extended read". 00007CBB B442 mov ah,0x42 ; extended read function 00007CBD CD13 int 0x13 ; call bios 00007CBF 7205 jc 0x7cc6 ; fall back to CHS mode 00007CC1 BB0070 mov bx,0x7000 ; buffer segment 00007CC4 EB7D jmp short 0x7d43 ; skip chs mode code blocklba is being used therefore it uses the extended read (ah=0x42) instead of the u sual read functions. the block above basically modifies the disk address packet for "extended read" with the format outlined below:
Format of disk address packet: Offset Size Description (Table 0249) 00h BYTE 10h (size of packet) 01h BYTE reserved (0) 02h WORD number of blocks to
sat 03/04/06
now, grub further processes the CHS information so that it can determine the starting head, cylinder and sector of stage 2 offset 0x7c44 is a dword value containing the absolute address of the sector where stage 2 is located. in my case, i installed grub on the master boot record so it has a value of 01 00 00 00 (little endian).
00007D00 66A1447C mov eax,[0x7c44] ; stage 2 sector 00007D04 6631D2 xor edx,edx ; null edx 00007D07 66F734 div dword [si] ; divide by number of sectors 00007D0A 88540A mov [si+0xa],dl ; save start of sector 00007D0D 6631D2 xor edx,edx ; zero edx 00007D10 66F77404 div dword [si+0x4] ; div by number of heads 00007D14 88540B mov [si+0xb],dl ; save start of head 00007D17 89440C mov [si+0xc],ax ; cylinder start 00007D1A 3B4408 cmp ax,[si+0x8] ; too many? 00007D1D 7D3C jnl 0x7d5b ; geometry errorthen it translate those starting addresses to match the format below:
AH = 02h AL = number of sectors to read (must be nonzero) CH = low eight bits of cylinder number CL = sector number 1-63 (bits 0-5) high two bits of cylinder (bits 6-7, hard disk only) DH = head number DL = drive number (bit 7 set for hard disk) ES:BX -gt; data bufferfinally, it calls the bios to read the number of sectors located on the CHS address given into memory. it copies the instructions at the sai d memory to a buffer inside the bios parameter block and then finally transfers control to the segment. this is how grub stage 2 get's loaded and executed.
; [si+0xa] starting sector ; [si+0xb] starting head ; [si+0xc] starting cylinder ; now it's the bios geometry translation 00007D1F 8A540D mov dl,[si+0xd] ; dl = bits 6-7th 00007D22 C0E206 shl dl,0x6 ; dh = high bits 00007D25 8A4C0A mov cl,[si+0xa] ; cl = sector 00007D28 FEC1 inc cl ; inc sector number 00007D2A 08D1 or cl,dl ; zero out bit 6-7 00007D2C 8A6C0C mov ch,[si+0xc] ; ch = cylinder 00007D2F 5A pop dx ; restore boot drive byte 00007D30 8A740B mov dh,[si+0xb] ; dh = head number 00007D33 BB0070 mov bx,0x7000 ; buffer 00007D36 8EC3 mov es,bx ; es points to buffer segment 00007D38 31DB xor bx,bx ; start at zero 00007D3A B80102 mov ax,0x201 ; function 2, 1 sector 00007D3D CD13 int 0x13 ; read sector 00007D3F 722A jc 0x7d6b ; error! carry flag set. 00007D41 8CC3 mov bx,es ; bx is start of buffer 00007D43 8E06487C mov es,[0x7c48] ; es is start of stage2 segment 00007D47 60 pusha ; save all registers 00007D48 1E push ds ; save data segment 00007D49 B90001 mov cx,0x100 ; iteration 00007D56 61 popa ; restore registers 00007D57 FF26427C jmp near [0x7c42] ; transfer control to stage 2the following block contains the initialization and function calls for displaying error messages. it first points register si to the start of a string, and calls the function at address 0x7da1 to print the message, letter by letter.
; error messages control block 00007D5B BE7F7D mov si,0x7d7f ; display geometry error 00007D5E E84000 call 0x7da1 ; 00007D61 EB0E jmp short 0x7d71 ; 00007D63 BE847D mov si,0x7d84 ; display hard disk error 00007D66 E83800 call 0x7da1 ; 00007D69 EB06 jmp short 0x7d71 ; 00007D6B BE8E7D mov si,0x7d8e ; display read error 00007D6E E83000 call 0x7da1 ; 00007D71 BE937D mov si,0x7d93 ; general error msg 00007D74 E82A00 call 0x7da1 ; 00007D77 EBFE jmp short 0x7d77 ; stop! ; string definitions 00007D79 47 inc di 00007D7A 52 push dx 00007D7B 55 push bp 00007D7C 42 inc dx 00007D7D 2000 and [bx+si],al 00007D7F 47 inc di 00007D80 656F gs outsw 00007D82 6D insw 00007D83 004861 add [bx+si+0x61],cl 00007D86 7264 jc 0x7dec 00007D88 204469 and [si+0x69],al 00007D8B 736B jnc 0x7df8 00007D8D 005265 add [bp+si+0x65],dl 00007D90 61 popa 00007D91 640020 add [fs:bx+si],ah 00007D94 45 inc bp 00007D95 7272 jc 0x7e09 00007D97 6F outsw 00007D98 7200 jc 0x7d9a ; character printing function 00007D9A BB0100 mov bx,0x1 00007D9D B40E mov ah,0xe 00007D9F CD10 int 0x10 00007DA1 AC lodsb 00007DA2 3C00 cmp al,0x0 00007DA4 75F4 jnz 0x7d9a 00007DA6 C3 reti should also point out that there is a standard agreed upon location where the partition table is defined. it starts at offset 0x1be and ends at 0x1fd (4 16 byte parti tion table entries). a partition table entry is sixteen bytes (0x10) wide, and it defines a single primary partition which is also an active (bootable) partition. the f irst byte of an active partition table is 80. this byte is loaded into the DL register before int 13h is called.
000001B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ................ 000001C0 01 00 83 0F FF FF 3F 00 00 00 B1 0D 54 02 00 0F ......?.....T... 000001D0 FF FF 83 0F FF FF F0 0D 54 02 F0 08 2A 01 00 0F ........T...*... 000001E0 FF FF 05 0F FF FF E0 16 7E 03 90 9E 2A 01 00 00 ........~...*... 000001F0 00 00 00 00 00 00 00 00 00 00 00 00 ..............finally, for closure, the boot sector word signature at offset 510.
00007DFE 55 push bp 00007DFF AA stosb
resoucres :
interrupt jump table
wikipedia entry for grub
gnu grub home
the mbr