|
[ / main / writing / boot_sector ] |
Writing a Boot Sector |
(c)1997 Jeff Weeks and Code X software
|
|
Writting your own boot sector is probably actually easier then you think. All you really need to know is how the Intel processor boots up. A valid boot sector has the code 0xAA55 at an offset of 510, and is located in the very first sector of the disk. Therefore, the BIOS simply checks drive 0 (A:) for this code. If not found, it then checks drive 128 (C:). If a valid boot sector is found, it is loaded into memory at location 0:07C0h. |
|
|
So, all you have to do is write a boot sector, assemble it into a plain binary file (their is no format or header to a boot sector), and write it to the first sector of your disk. The best way to do that would be to either use nasm (The netwide assembler can produce plain binary files) or assemble into a DOS .EXE and remove the first 512 bytes. You can also write your own program to write the bootsector to sector 1 of the disk using BIOS INT 13h AH=02h. |
|
|
Pretty simple eh? Well, in case you're still a little confused, here's a little bootsector from PolyOS that simply switches to protected mode, after checking that you have a 386+ computer. Actually, it even loads in the PolyFS superblock and checks if it's valid, but that's about it. Soon it'll load in the kernel and jump to it. The bootesctor was written with Nasm.
; PolyOS boot loader code (c)1997 Jeff Weeks of Code X Software ; ------------------------------------------------------------------------ ; This little bit of assembly is the boot loader for my operating system. ; ------------------------------------------------------------------------ [BITS 16] ; the bios starts out in 16-bit real mode [ORG 0] ; ------------------------------------------------------------------------ ; SECTOR ONE: THE BOOT LOADER ; ------------------------------------------------------------------------ ; This sector detects your processor. If a 386 is found, it loads the ; kernel from the disk and executes it (atleast it will in the future :). ; ------------------------------------------------------------------------ jmp start ; skip over our data and functions ; ------------------------------------- ; Data used in the boot-loading process ; ------------------------------------------------------------------------ bootdrv db 0 bootmsg db 'Booting PolyOS (c)1997 Cipher of Code X',13,10,0 loadmsg db 'Loading kernel',13,10,0 jumpmsg db 'Jumping to kernel',13,10,0 rebootmsg db 'Press any key to reboot',13,10,0 ; these are used in the processor identification processormsg db 'Checking for 386+ processor: ',0 need386 db 'Sorry... 386+ required!',13,10,0 found386 db 'Excellent!',13,10,0 ; these are used when entering protected mode a20msg db 'Setting A20 address line',13,10,0 pmodemsg db 'Setting CR0 -> Entering PMode',13,10,0 ; Here's the locations of my IDT and GDT. Remember, Intel's are ; little endian processors, therefore, these are in reversed order. ; Also note that lidt and lgdt accept a 32-bit address and 16-bit ; limit, therefore, these are 48-bit variables. pIDT dw 7FFh ; limit of 256 IDT slots dd 0000h ; starting at 0000 pGDT dw 17FFh ; limit of 768 GDT slots dd 0800h ; starting at 0800h (after IDT) ; ------------------------------------------ ; Functions used in the boot-loading process ; ------------------------------------------------------------------------ detect_cpu: mov si, processormsg ; tell the user what we're doing call message ; test if 8088/8086 is present (flag bits 12-15 will be set) pushf ; save the flags original value xor ah,ah ; ah = 0 push ax ; copy ax into the flags popf ; with bits 12-15 clear pushf ; Read flags back into ax pop ax and ah,0f0h ; check if bits 12-15 are set cmp ah,0f0h je no386 ; no 386 detected (8088/8086 present) ; check for a 286 (bits 12-15 are clear) mov ah,0f0h ; set bits 12-15 push ax ; copy ax onto the flags popf pushf ; copy the flags into ax pop ax and ah,0f0h ; check if bits 12-15 are clear jz no386 ; no 386 detected (80286 present) popf ; pop the original flags back mov si, found386 call message ret ; no 8088/8086 or 286, so ateast 386 no386: mov si,need386 ; tell the user the problem call message jmp reboot ; and reboot when key pressed ; ------------------------------------------------------------------ message: ; Dump ds:si to screen. lodsb ; load byte at ds:si into al or al,al ; test if character is 0 (end) jz done mov ah,0eh ; put character mov bx,0007 ; attribute int 0x10 ; call BIOS jmp message done: ret ; ------------------------------------------------------------------ getkey: mov ah, 0 ; wait for key int 016h ret ; ------------------------------------------------------------------ reboot: mov si, rebootmsg ; be polite, and say we're rebooting call message call getkey ; and even wait for a key :) db 0EAh ; machine language to jump to FFFF:0000 (reboot) dw 0000h dw 0FFFFh ; no ret required; we're rebooting! (Hey, I just saved a byte :) ; ------------------------------------------- ; The actual code of our boot loading process ; ------------------------------------------------------------------------ start: mov ax,0x7c0 ; BIOS puts us at 0:07C0h, so set DS accordinly mov ds,ax ; Therefore, we don't have to add 07C0h to all our data mov [bootdrv], dl ; quickly save what drive we booted from cli ; clear interrupts while we setup a stack mov ax,0x9000 ; this seems to be the typical place for a stack mov ss,ax mov sp,0xffff ; let's use the whole segment. Why not? We can :) sti ; put our interrupts back on ; Interestingly enough, apparently the processor will disable ; interupts itself when you directly access the stack segment! ; Atleast it does in protected mode, I'm not sure about real mode. mov si,bootmsg ; display our startup message call message call detect_cpu ; check if we've got a 386 .386 ; use 386 instructions from now on (I don't want to manually include ; operand-size(66h) or address-size(67h) prefixes... it's annoying :) mov si,loadmsg ; tell the user we're loading the kernel call message call getkey read_me: ; first, reset the disk controller xor ax, ax int 0x13 jc reboot ; reboot on error ; then load in the PolyFS superblock mov ax,0x09000 ; superblock goes to 9000:0000 (above stack) mov es,ax xor bx,bx ; I could condense a few of these high/low 8-bit movs into one 16-bit ; mov, but, for simplicity, I'll leave it as is, unless necessary. mov ax,0x0202 ; load one block (two sectors) mov ch,0 ; cylinder = 0 mov cl,3 ; sector = 2 (starts at sector 1 not 0) mov dh,0 ; head = 0 = side one mov dl,[bootdrv] ; disk = what we booted from int 0x13 ; read it jc read_me ; if there's an error then we'll try again. ; Often there is not error but requires a few ; tries. Ofcourse, this may end up as an ; infinite loop... but only on a bad disk... ; Check if we have a valid super block (BTW: ES still equals 0x9000) mov di, 0 ; offset of PolyFS magic signature mov si, polymagic ; offset of PolyFS magic to check for (in ds) cmpsw ; compare ES:[DI] with DS:[SI] jnz reboot ; reboot on error (otherwise, we've got a PolyFS) ; Ideally, we'd load the kernel right here mov si, a20msg ; tell the user we're setting the A20 line call message ; set A20 line cli ; no more interuptions! :) xor cx, cx clear_buf: in al, 64h ; get input from keyboard status port test al, 02h ; test the buffer full flag loopnz clear_buf ; loop until buffer is empty mov al, 0D1h ; keyboard: write to output port out 64h, al ; output command to keyboard clear_buf2: in al, 64h ; wait 'till buffer is empty again test al, 02h loopnz clear_buf2 mov al, 0dfh ; keyboard: set A20 out 60h, al ; send it to the keyboard controller mov cx, 14h wait_kbc: ; this is approx. a 25uS delay to wait out 0edh, ax ; for the kb controler to execute our loop wait_kbc ; command. ; the A20 line is on now. Let's load in our ITD and GDT tables... ; Ideally, there will actually be data in their locations (by loading ; the kernel) lidt [pIDT] lgdt [pGDT] ; now let's enter pmode... mov si, pmodemsg call message call getkey mov eax, cr0 ; load the control register in or al, 1 ; set bit 1: pmode bit mov cr0, eax ; copy it back to the control register jmp $+2 ; and clear the prefetch queue nop nop ; jump to the kernel that we've loaded in... ; For now, we'll actually just reboot (this really doesn't ; work in protected mode, but it does reboot :) db 0xEA dw 0x0000 dw 0xFFFF ; The boot sector is supposed to have to have 0xAA55 at the end of ; the sector (the word at 510 bytes) to be loaded by the BIOS... times 510-($-$$) db 0 dw 0xAA55 |
|
|