in the name of zero

My GRUB Foray
GRUB boot sector (stage1)
~ a paper by Stephanie ~


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 block
lba 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 error
then 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 buffer
finally, 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 2 
the 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                ret
i 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