进入保护模式并执行远jmp后出现三重故障

时间:2018-03-31 14:37:58

标签: assembly x86 nasm bootloader osdev

我正在写一个爱好操作系统内核。每次内核进入保护模式并跳转到其保护模式部分时,Bochs会将故障三倍并给我这个:

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

这是我的内核代码,应该从2000h:0000h加载并使用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:

这是我的引导程序:

; 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:

我对保护模式缺乏经验,因此对此有所了解。有人可以帮我吗?

1 个答案:

答案 0 :(得分:-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