WRITING
A
BOOTSECTOR
(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