【问题标题】:Triple fault after entering protected mode and doing a far jmp [duplicate]进入保护模式并执行远 jmp 后出现三重故障 [重复]
【发布时间】:2018-03-31 14:37:57
【问题描述】:

我正在编写一个爱好 OS 内核。每次内核进入保护模式并跳转到其保护模式部分时,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:

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

【问题讨论】:

  • 使用 bochs 调试器来验证 GDT。您的实模式段值似乎错误,因此您加载了错误的 GDT,但由于这不是 minimal reproducible example,因此无法真正分辨。
  • Jester 指出这不是 MCVE。如果您也发布了引导加载程序,这将有所帮助。但是请注意,您需要 GDT 指针内的线性地址而不是实模式偏移量。 gdtPointer 具有 gdtStart 作为 GDT 的基础。问题是这与从 0x2000:0x0000 开始的实模式段有关。 0x2000:0x0000 是线性地址 0x2000gdtStart。当您进入保护模式时,您还需要将 0x20000 添加到您的远跳偏移量。
  • 我注意到了一些东西,它可能与 Jester 对片段的评论有关。你说内核在2000h:0000h 加载,然后向我们展示代码和boxhs 输出。在 bochs 输出中,它说 CS 设置为 0x0800 (CS:0800( 0004| 0| 0))。我的问题是 - 您是在 0x0800:0x0000 还是 0x2000:0x0000 加载该代码? BOCH 的输出根本不建议您从 0x2000:0x0000 运行。
  • @MichaelPetch 在调试过程中,我稍微更改了引导加载程序代码,以便我的内核从 0800:0000 加载。我已经编辑了 Bochs 输出。
  • @MichaelPetch 您建议将 20000h 添加到 gdtStart 和远跳偏移量给我这个:write_virtual_checks(): write beyond limit, r/w

标签: assembly x86 nasm bootloader osdev


【解决方案1】:

您的 GDT 没有问题。问题是我们在lgdt之后要做什么。

有两种解决方案适合您

不要在内核中使用段

这是我的内核代码,应该从 2000h:0000h 加载并使用 NASM 组装

内核加载的地址实际上是0x20000,但您决定使用段寄存器将这个基地址存储为0x2000,这对于实模式操作系统来说是很好的,但对于保护模式来说却不是很好,因为符号@ 987654324@ 的偏移量为零。您可以将内核加载的地址更改为可以用 16 位整数表示的位置(例如 0x1000),然后像这样更改内核源代码:

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

这种方式的缺点是你在0x7C00的引导加载程序可能会被内核覆盖,好像大小增长太多了,所以你需要将引导加载程序移动到其他地方以避免这种情况

在远跳转时添加内核的基地址

来自您的内核源代码

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

内核代码段的声明声明基地址为零,而您的内核在0x20000 加载。所以你可以改变你的 GDT 的基础,或者只是改变你的跳远代码:

jmp CODESEGMENT:main+0x20000

注意:正如我们所知,这部分代码可能会被其他人在切换之前可能看不到cli,所以请将此行放在lgdt之前

; NOTE: This operation needs you to have interrupt flag clear

【讨论】:

  • 他已经在 LGDT 上方几行有一个 CLI:call printString_16climov si, msgSuccess。中断已经关闭 - 这不是他问题的原因。请参阅问题(和重复问题)下的 cmets,了解问题所在以及如何解决。
  • @MichaelPetch 现在已经改变了答案,但是,您在代码中找到的cli 并不专用于切换到保护模式。如果在切换前加上cli,可能会很好的提高代码的可读性。
  • 如果您知道中断已被禁用,如果cli 很远,请发表评论。在汇编语言中,您不会为了人类的利益而使用冗余指令;没有编译器可以优化它们。
  • @PeterCordes 是的,你是对的。在实模式下,我们不需要任何冗余代码(或字节),因为实模式下 RAM 的寻址空间对我们来说太小了。
  • 任何时候你都在手写 asm,真的。如果每个字节和/或时钟周期都无关紧要,您可以用 C 或其他语言编写,然后让编译器进行代码生成。
猜你喜欢
  • 2018-02-18
  • 1970-01-01
  • 2017-09-12
  • 2017-01-27
  • 2016-11-19
  • 2021-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多