1

I am writing a hobby OS kernel. Everytime the kernel goes into protected mode and jumps to its protected mode portion, Bochs would triple fault and give me this:

00014918914i[BIOS  ] Booting from 0000:7c00
00016345509e[CPU0  ] jump_protected: gate type 0 unsupported
00016345509e[CPU0  ] interrupt(): gate descriptor is not valid sys seg (vector=0
x0d)
00016345509e[CPU0  ] interrupt(): gate descriptor is not valid sys seg (vector=0
x08)
00016345509i[CPU0  ] CPU is in protected mode (active)
00016345509i[CPU0  ] CS.mode = 16 bit
00016345509i[CPU0  ] SS.mode = 16 bit
00016345509i[CPU0  ] EFER   = 0x00000000
00016345509i[CPU0  ] | EAX=60000011  EBX=00000002  ECX=00090011  EDX=00000000
00016345509i[CPU0  ] | ESP=00001000  EBP=00000000  ESI=000e01e7  EDI=00000200
00016345509i[CPU0  ] | IOPL=0 id vip vif ac vm RF nt of df if tf sf zf af PF cf
00016345509i[CPU0  ] | SEG sltr(index|ti|rpl)     base    limit G D
00016345509i[CPU0  ] |  CS:2000( 0004| 0|  0) 00020000 0000ffff 0 0
00016345509i[CPU0  ] |  DS:2000( 0005| 0|  0) 00020000 0000ffff 0 0
00016345509i[CPU0  ] |  SS:09e0( 0005| 0|  0) 00009e00 0000ffff 0 0
00016345509i[CPU0  ] |  ES:2000( 0005| 0|  0) 00020000 0000ffff 0 0
00016345509i[CPU0  ] |  FS:0000( 0005| 0|  0) 00000000 0000ffff 0 0
00016345509i[CPU0  ] |  GS:0000( 0005| 0|  0) 00000000 0000ffff 0 0
00016345509i[CPU0  ] | EIP=000003da (000003da)
00016345509i[CPU0  ] | CR0=0x60000011 CR2=0x00000000
00016345509i[CPU0  ] | CR3=0x00000000 CR4=0x00000000
00016345509i[CPU0  ] 0x00000000000003da>> jmpf 0x0008:03e2 : EAE2030800
00016345509p[CPU0  ] >>PANIC<< exception(): 3rd (13) exception with no resolutio
n

This is my kernel code, supposed to be loaded from 2000h:0000h and assembled using NASM:

; Puck Kernel
; Version 1.00.001a

; ----------------------
; REAL MODE PORTION
; ----------------------
[bits 16]
jmp main_16

msgStart            db  "Puck Kernel in Real Mode", 0dh, 0ah
                    db  "Version 1.00.001a", 0dh, 0ah
                    db  "Coded by Weedboi6969#4098, named by his friend <Ushiwaka>#6536", 0dh, 0ah
                    db  "<C> Copyleft 2018 Weedboi6969#4098. All wrongs reserved.", 0dh, 0ah
                    db  "THIS IS RUNNING IN REAL MODE!", 0dh, 0ah
                    db  0dh, 0ah
                    db  0
endl                db  0dh, 0ah, 0
msgA20Status        db  "A20 line status                     : ", 0
msgA20EnableBIOS    db  "Enable A20 using INT 15h            : ", 0
msgA20EnableKBC     db  "Enable A20 using keyboard controller: ", 0 
msgA20EnableFast    db  "Enable A20 using FAST A20           : ", 0
msgA20Failed        db  "ERROR: Cannot enable A20 line! System halted.", 0  
msgDisableINT       db  "Disable maskable interrupt          : ", 0
msgDisableNMI       db  "Disable non-maskable interrupt      : ", 0
msgEnterPMode       db  "Entering protected mode...", 0
msgSuccess          db  "SUCCESS!", 0dh, 0ah, 0
msgFailed           db  "FAILED!", 0dh, 0ah, 0
msgEnabled          db  "ENABLED!", 0dh, 0ah, 0
msgDisabled         db  "DISABLED!", 0dh, 0ah, 0

; GLOBAL DESCRIPTOR TABLE
; =======================
gdtPointer:
dw gdtEnd - gdtStart - 1
dd gdtStart

gdtStart:
dq 0 ; reserved
CODESEGMENT     equ $ - gdtStart
gdtCode: ; allow full access to memory as code (read-only, executable)
dw 0xffff       ; Limit 0:15
dw 0x0000       ; Base 0:15
db 0x00         ; Base 16:23
db 10011010b    ; Access Byte: ring 0, executable, can only be executed from ring 0, readable
db 11001111b    ; Limit 16:19 + Flags: 32-bit protected mode, page granularity
db 0x00         ; Base 24:31
DATASEGMENT     equ $ - gdtStart
gdtData: ; allow full access to memory as data (writable, non-executable)
dw 0xffff       ; Limit 0:15
dw 0x0000       ; Base 0:15
db 0x00         ; Base 16:23
db 10010010b    ; Access Byte: ring 0, non-executable, segment grows up, writable
db 11001111b    ; Limit 16:19 + Flags: 32-bit protected mode, page granularity
db 0x00         ; Base 24:31
gdtEnd:

; REAL MODE PROCEDURES
; ====================
printString_16:
pusha
mov ah, 0eh
.next:
lodsb
cmp al, 0
jz .done
int 10h
jmp .next
.done:
popa
ret

a20Check:
pushf
push ds
push es
push di
push si

cli

xor ax, ax
mov es, ax

not ax
mov ds, ax

mov di, 500h
mov si, 510h

mov al, byte [es:di]
push ax

mov al, byte [ds:si]
push ax

mov byte [es:di], 00h
mov byte [ds:si], 0ffh

cmp byte [es:di], 0ffh

pop ax
mov byte [ds:si], al

pop ax
mov byte [es:di], al

mov ax, 0
je .exit

mov ax, 1
.exit:
pop si
pop di
pop es
pop ds
popf

ret

a20CheckTimeout:
pusha
xor ax, ax
int 1ah
mov bp, dx
add bp, 54 ; 3 seconds timeout
.wait:
call a20Check
push ax
xor ax, ax
int 1ah
pop ax
cmp dx, bp
jae .done
.done:
popa
ret

a20Enable_KBC:
cli

call .wait
mov al, 0adh
out 64h, al

call .wait
mov al, 0d0h
out 64h, al

call .wait2
in al, 60h
push ax

call .wait
mov al, 0d1h
out 64h, al

call .wait
pop ax
or al, 2
out 60h, al

call .wait
mov al, 0aeh
out 64h, al

call .wait
sti

call a20CheckTimeout
ret
.wait:
in al, 64h
test al, 2
jnz .wait
ret
.wait2:
in al, 64h
test al, 1
jz .wait2
ret

a20Enable_Fast:
in al, 92h
test al, 2
jnz .done
or al, 2
and al, 0feh
out 92h, al
.done:
call a20CheckTimeout
ret

a20Enable_BIOS:
mov ax, 2403h
int 15h
jb .done
cmp ah, 0
jnz .done

mov ax, 2402h
int 15h
jb .done
cmp ah, 0
jnz .done

cmp al, 1
jz .done

mov ax, 2401h
int 15h
.done:
call a20Check
ret

nmiDisable:
push ax
in al, 70h
or al, 80h
out 70h, al
pop ax
ret

; REAL MODE MAIN PROGRAM
; ======================
main_16:
mov ax, cs
mov ds, ax
mov es, ax

mov ax, 2
int 10h

mov si, msgStart
call printString_16

mov si, msgA20Status
call printString_16
call a20Check
cmp ax, 1
je a20AlreadyEnabled
mov si, msgDisabled
call printString_16

mov si, msgA20EnableBIOS
call printString_16
call a20Enable_BIOS
cmp ax, 1
je a20EnabledByProc
mov si, msgFailed
call printString_16

mov si, msgA20EnableKBC
call printString_16
call a20Enable_KBC
cmp ax, 1
je a20EnabledByProc
mov si, msgFailed
call printString_16

mov si, msgA20EnableFast
call printString_16
call a20Enable_Fast
cmp ax, 1
je a20EnabledByProc
mov si, msgFailed
call printString_16

mov si, msgA20Failed
call printString_16
jmp $ ; friendly halt because Bochs sometimes hates hlt

a20AlreadyEnabled:
mov si, msgEnabled
call printString_16
jmp a20Enabled
a20EnabledByProc:
mov si, msgSuccess
call printString_16
a20Enabled:
mov si, msgDisableINT
call printString_16
cli
mov si, msgSuccess
call printString_16

mov si, msgDisableNMI
call printString_16
call nmiDisable
mov si, msgSuccess
call printString_16

mov si, msgEnterPMode
call printString_16

lgdt [gdtPointer]
mov eax, cr0
or al, 1
mov cr0, eax

jmp CODESEGMENT:main

jmp $

; ----------------------
; PROTECTED MODE PORTION
; ----------------------
[bits 32]
main:

This is my bootloader:

; Puck Bootloader (PRML)
; Version 1.00.001 (started on Mar 29 2018)
; Based on MikeOS bootloader

BITS 16

jmp short start
nop

; Description table
bpbOEMLabel                 db  "PILOT   "
bpbBytesPerSector           dw  512
bpbSectorsPerCluster        db  1
bpbReservedForBoot          dw  1
bpbNumberOfFATs             db  2
bpbRootDirEntries           dw  224
bpbLogicalSectors           dw  2880
bpbMediumByte               db  0F0h
bpbSectorsPerFAT            dw  9
bpbSectorsPerTrack          dw  18
bpbSides                    dw  2
bpbHiddenSectors            dd  0
bpbLargeSectors             dd  0
bpbDriveNo                  dw  0
bpbSignature                db  41
bpbVolumeID                 dd  13371337h
bpbVolumeLabel              db  "PILOTLOADER"
bpbFileSystem               db  "FAT12   "

start:
mov ax, 7c0h
add ax, 544
cli
mov ss, ax
mov sp, 4096
sti

mov ax, 7c0h
mov ds, ax

; cmp dl, 0
; je dlNoChange
; mov [bootdev], dl
; mov ah, 8
; int 13h
; jc fatalError
; and cx, 3fh
; mov [bpbSectorsPerTrack], cx
; movzx dx, dh
; inc dx
; mov [bpbSides], dx

; dlNoChange:
; mov eax, 0

mov ax, ds
mov es, ax
mov bx, buffer

mov ax, 19
call lbaConvert

mov ah, 2
mov al, 14

pusha
readRootDir:
popa
pusha

stc
int 13h

jnc searchDir
call resetFloppy
jnc readRootDir

jmp fatalError

searchDir:
popa
mov ax, ds
mov es, ax
mov di, buffer

mov cx, word [bpbRootDirEntries]
readRootEntry: ; will be used in 2nd stage
xchg cx, dx
mov si, loaderName
mov cx, 11
repe cmpsb
je foundFile

add di, 32

xchg dx, cx
loop readRootEntry

jmp noLoader
foundFile:
push ds
pop es
mov ax, word [es:di+0fh]
mov word [cluster], ax

mov ax, 1
call lbaConvert

mov di, buffer
mov bx, di

mov ah, 2
mov al, 9
pusha
readFAT:
popa
pusha

stc
int 13h

jnc readFAT_done
call resetFloppy
jnc readFAT
fatalError:
mov si, msgFatal
call printString
xor ax, ax
int 16h
xor ax, ax
int 19h
hlt ; just in case
msgFatal    db  "Fatal error, press any key to reboot...", 0
noLoader:
mov si, msgLoader
call printString
xor ax, ax
int 16h
xor ax, ax
int 19h
hlt ; just in case
msgLoader   db  "Loader not found", 0

readFAT_done:
popa
mov ax, 2000h
mov es, ax
xor bx, bx

mov ah, 2
mov al, 1

push ax
loadFileSector:
mov ax, word [cluster]
add ax, 31
call lbaConvert

mov ax, 2000h
mov es, ax
mov bx, word [pointer]

pop ax
push ax

stc
int 13h

jnc calculateNextCluster

call resetFloppy
jmp loadFileSector

calculateNextCluster:
push ax
pop ax
mov ax, [cluster]
mov dx, 0
mov bx, 3
mul bx
mov bx, 2
div bx
mov si, buffer
add si, ax
mov ax, word [ds:si]

or dx, dx
jz .even
.odd:
shr ax, 4
jmp short .nextCluster
.even:
and ax, 0fffh
.nextCluster:
mov word [cluster], ax
cmp ax, 0ff8h
jae endLoad

add word [pointer], 512
jmp loadFileSector

endLoad:
pop ax
; mov si, msgLoad
; call printString
; xor ax, ax
; int 16h
mov dl, byte [bootdev] ; give 2nd stage information about boot device
jmp 2000h:0000h ; jump to 2nd stage
; msgLoad   db  "2nd stage loaded!", 0

printString:
pusha
mov ah, 0eh
.next:
lodsb
cmp al, 0
je .done
int 10h
jmp short .next
.done:
popa
ret

resetFloppy:
push ax
push dx
mov ax, 0
mov dl, byte [bootdev]
stc
int 13h
pop dx
pop ax
ret

lbaConvert:
push bx
push ax

mov bx, ax

xor dx, dx
div word [bpbSectorsPerTrack]
inc dl
mov cl, dl
mov ax, bx

xor dx, dx
div word [bpbSectorsPerTrack]
xor dx, dx
div word [bpbSides]
mov dh, dl
mov ch, al

pop ax
pop bx

mov dl, byte [bootdev]
ret

loaderName  db  "KERNEL     "
; msgTest       db "Booting...", 0

bootdev     db  0
cluster     db  0
pointer     db  0


times 510-($-$$) db 0
dw 0aa55h

buffer:

I am somewhat inexperienced with protected mode and therefore knows just a bit about it. Can someone help me with that?

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • 3
    Use bochs debugger to verify GDT. Your real mode segment values seem wrong and so you load wrong GDT, but can't really tell since this is not a [MCVE]. – Jester Mar 31 '18 at 14:50
  • 2
    As Jester points out this isn't an MCVE. It would help if you posted your bootloader as well. However be aware that you need linear addresses insideyour GDT pointer not realmode offset. `gdtPointer` has `gdtStart` as the base of the GDT. Problem is that is relative to a real mode segment starting at 0x2000:0x0000. 0x2000:0x0000 is linear address 0x2000<<4+0x0000=0x20000. That needs to be added to `gdtStart`. You also need to add 0x20000 to your far jump offset when taking you into protected mode. – Michael Petch Mar 31 '18 at 15:32
  • 1
    I noticed something and it is probably related to Jester's comment about the segments. You say the kernel is loaded at `2000h:0000h` and then show us the code and the boxhs output. In the bochs output it says _CS_ is set to 0x0800 (`CS:0800( 0004| 0| 0)`). My question is - did you load that code at 0x0800:0x0000 or 0x2000:0x0000? The BOCHs output doesn't suggest you are running from 0x2000:0x0000 at all. – Michael Petch Mar 31 '18 at 16:40
  • @MichaelPetch In one point during debugging, I have changed the bootloader code a bit so that my kernel is loaded from 0800:0000. I have edited the Bochs output. – Nguyen Thanh Vinh Apr 01 '18 at 01:46
  • @MichaelPetch Your suggestion to add 20000h to gdtStart and the far jump offset gives me this: `write_virtual_checks(): write beyond limit, r/w` – Nguyen Thanh Vinh Apr 01 '18 at 03:04
  • Your label `main` doesn't have any code to prevent it from wandering memory executing semi-random data as code. Add a `jmp $` after `main:` to at least create an infinite loop. Add 0x20000 to the gdtStart and modify the far jmp to be `jmp dword CODESEGMENT:main+0x20000` . It is important to override with `dword` to allow a full 32-bit offset to be used. – Michael Petch Apr 01 '18 at 06:37
  • Thanks! My code works! – Nguyen Thanh Vinh Apr 01 '18 at 13:57

1 Answers1

-1

Your GDT has no problem. The problem is about what we will do after lgdt.

There are two solutions for you

Don't use segments in your kernel

This is my kernel code, supposed to be loaded from 2000h:0000h and assembled using NASM

The address where your kernel is loaded is literally 0x20000, but you decide to use segment registers to store this base as 0x2000, which is good for a real mode OS but not quite for protected mode because the symbol main has an offset of zero. You may change the address where your kernel is loaded to somewhere that can be expressed in 16 bit integer (e.g. 0x1000), and then change your kernel source like this:

[ORG 0x1000]    ; The generic offset to the address
                ; of the label (e.g. main -> main+0x1000)
; Your code...

The disadvantage of this way is that your bootloader at 0x7C00 may be overrided by the kernel as if the size grows too much, so you need to move the boot loader to somewhere else in order to avoid this

Add the base address of your kernel during the far jump

From your kernel source

gdtCode: ; allow full access to memory as code (read-only, executable)
dw 0xffff       ; Limit 0:15
dw 0x0000       ; Base 0:15
db 0x00         ; Base 16:23
db 10011010b    ; Access Byte: ring 0, executable, can only be executed from ring 0, readable
db 11001111b    ; Limit 16:19 + Flags: 32-bit protected mode, page granularity
db 0x00         ; Base 24:31

The declaration of kernel code segment states that the base address is zero while your kernel is loaded at 0x20000. So you can either change the base of your GDT or simply change your code of far jump:

jmp CODESEGMENT:main+0x20000

NOTE: As we know, this portion of code may be referred by other people who might not see the cli much before the switch, so please put this line before lgdt

; NOTE: This operation needs you to have interrupt flag clear
TravorLZH
  • 302
  • 1
  • 9
  • He already has a CLI a few lines above the LGDT here: `call printString_16` `cli` `mov si, msgSuccess` . Interrupts are already off - this wasn't the cause of his issues. See comments under the question (and the duplicate question) for what is wrong, and how to fix it. – Michael Petch Aug 04 '18 at 04:38
  • @MichaelPetch have changed the answer now, but still, the `cli` you found in the code is not dedicated to switching to protected mode. It may be good to improve the readability of the code if `cli` is added before switching. – TravorLZH Aug 04 '18 at 07:16
  • If you know interrupts are already disabled, put a comment if the `cli` is far away. In assembly language, you don't use redundant instructions just for the benefit of humans; there's no compiler to optimize them away. – Peter Cordes Aug 04 '18 at 07:22
  • @PeterCordes Yes you're right. In real mode, we don't want any redundant code (or bytes) because the addressing space of RAM in real mode is too little for us. – TravorLZH Aug 04 '18 at 07:38
  • Any time you're writing asm by hand, really. If every byte and/or clock cycle didn't matter, you'd write in C or whatever and let a compiler do the code-gen. – Peter Cordes Aug 04 '18 at 07:42