Code: Select all
[BITS 16]
[ORG 0x7C00]
jmp start ;Some Compaq machines won't boot if first instruction isn't a JMP
;Should put a dummy BPB (BIOS Paramater Block) here so that DOS/Windows can format the disk
;later (DOS/Windows tends to wet it's pants otherwise)
start:
; mov [drive_id], dl ** DS not set yet, so this could write anywhere **
mov ax, cs
mov ds, ax
mov [drive_id], dl ;** Shifted from above **
mov es, ax
; mov fs, ax ;Segment register unused so no need to initialize
mov ax, 0x1D0
cli ;Possibly unnecessary, but a good habit (in case IRQ occurs after SS set but before SP set)
mov ss, ax
mov sp, 0x200 ;ss:sp = 0x01D0:0x0200 = 0x00002000
sti
mov si, init_message
call PutStr
call enable_A20
call read
cli
lgdt [gdt_desc]
mov eax, cr0
or eax, 1
mov cr0, eax
jmp 0x08:.32_bit
[BITS 32]
.32_bit
mov eax, 10h
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
;OK, at this point, what does "SS:ESP" contain? The highest 16-bits of ESP
;aren't set to anything and could contain trash. SS_base = 0x00000000 so if
;ESP isn't trash it'd point to 0x00000000+0x00000200 or 0x00000200 (but
;SS:ESP could point to 0x12340200 for all you know).
mov dl, 0x04
; mov ecx, 0B81E0h Assemblers are good - try:
mov ecx,0B8000 + 6 * (40 * 2) ;So you can figure out where the magic number came from later
mov esi, enabled_32
call PutStr_32
jmp 0x08:0x7e00
.hang
jmp .hang
PutStr_32:
cld ;Should set direction *somewhere* before using string instructions
.next_char
lodsb
or al,al
jz .end
mov byte [ds:ecx], al ;Try using "lodsb" with "stosw" (with ah = attribute)
inc ecx
mov byte [ds:ecx], dl
inc ecx
jmp .next_char
.end
ret
[BITS 16]
enable_A20:
;Unfortunately, different motherboards use different methods to enable the
;A20 gate. Try BIOS function first ("mov ax,0x2401; int 0x15"). Also, consider
;testing if A20 actually is enabled correctly instead of assuming it is, by
;testing if the value at 0x0000:0x0000 (or 0x00000000) remains the same as the
;value at 0xFFFF:0x0010 (or 0x00100000).
cli
call waitkeyclear
mov al, 0xd1
out 0x64, al
call waitkeyclear
mov al, 0xdf
out 0x60, al
call waitkeyclear
mov cx, 0x10
.waisttime ;Hehe, you mean "waste" not "waist"
nop
nop
xor ax, ax
loop .waisttime
mov al, 0xd0
out 0x64, al
call waitkeyfull
in al, 0x60
test al, 2
jnz .A20_on ;Why not do a "jz halt" instead?
; mov al, 'A' ** unused **
call halt ;WTF - see comments below
.A20_on
sti
mov si, A20_success
call PutStr
sti
ret
waitkeyclear:
xor al, al
in al, 0x64
test al, 2
jnz waitkeyclear
ret
waitkeyfull:
xor cx, cx
in al, 0x64
test al, 1
jz waitkeyfull
ret
;OK, what is this "halt" code meant to do? It looks like it prints a string, then
;waits until an IRQ occurs (could be anywhere from 0 ms to 55 ms), then falls through
;to the "PutStr" routine which prints the string again (starting from the byte after
;the first string's terminator.
halt:
sti
mov si, A20_fail
call PutStr
.die:
hlt
jmp .die ;I'm guessing you missed this!
PutStr:
cld ;Should set direction *somewhere* before using string instructions
mov ah,0x0E
mov bh,0x00
mov bl,0x07
.nextchar
lodsb
or al,al
jz .return
int 0x10
jmp .nextchar
.return
ret
read:
sti
xor ax, ax
mov di, 5
.track1
mov al, 1
mov dh, 0
mov dl, [drive_id]
mov ch, 0
mov cl, 2
mov bx, 0x7e00
mov es, bx
mov bx, 0x0000
int 0x13
dec di
; jz .err Should see if it worked before deciding it didn't
; jc .track1
; jmp .success
jnc .success
dec di
jnz .track1
;WTF? If it didn't load correctly you display a different string and then
;continue as if it has loaded correctly???
.err
mov si, Read_fail
call PutStr
jmp .end ;Should probably lock up here instead (note: with interrupt enabled the BIOS will turn floppy motor off).
.success
mov si, Read_success
call PutStr
.end
mov dx,0x3F2
mov al,0x0C
out dx,al
ret
align 4 ;Align your GDT!
gdt:
gdt_null:
; dd 0 ;These values are unused by the CPU, so let's use them!
; dd 0
dw 0 ;First 2 bytes of (unused) NULL descriptor
gdt_desc: ;From below
dw gdt_end - gdt - 1 ;GDT limit (also part of NULL descriptor)
dd gdt ;GDT base address (also part of NULL descriptor)
;I should charge you $20 for adding the comments below... ;-)
gdt_code:
dw 0FFFFh ;Limit (low word) = 0xFFFF
dw 0 ;Base (low word) = 0x0000
db 0 ;Base (middle byte) = 0x00
db 10011010b ;Present, DPL=0, non-system, type = "code execute/read"
db 11001111b ;4KB limit granularity, 32-bit default size, limit high nibble = 0xF
db 0 ;Base (high byte) = 0x00
gdt_data:
dw 0FFFFh ;Limit (low word) = 0xFFFF
dw 0 ;Base (low word) = 0x0000
db 0 ;Base (middle byte) = 0x00
db 10010010b ;Present, DPL=0, non-system, type = "data read/write"
db 11001111b ;4KB limit granularity, 32-bit default size, limit high nibble = 0xF
db 0 ;Base (high byte) = 0x00
gdt_end:
;gdt_desc: ;Shifted above
; dw gdt_end - gdt - 1
; dd gdt
enabled_32 db '32bit mode enabled', 0
drive_id dw ''
A20_success db 'A20 Enabled', 13, 10, 0
A20_fail db 'A20 Enabling failed', 13, 10, 0
Read_success db 'Floppy read successfully', 13, 10, 0
Read_fail db 'Floppy read failure', 13, 10, 0
init_message db 'Welcome to SOS V 0.0.1', 13, 10, 0
times 510-($-$$) db 0
dw 0AA55h
Honestly, no - I can't be sure that any of the problems I noticed would have caused a "stack fault in kernel mode".
To be more honest, IIRC the error messages in VMware are fairly crappy, so "stack fault in kernel mode" could mean anything (not even sure if it's general protection fault or not, or which instruction caused the problem). Try running this through Bochs to get real debugging information so you can find out what's wrong!
I'll try not to mention any of the design flaws (like what's going to happen when the boot code becomes larger than 512 bytes, or the kernel becomes larger than about 600 KB), or the severe lack of comments...