Assembly 进入保护模式并执行远jmp后出现三重故障
我正在写一个爱好操作系统内核。每次内核进入保护模式并跳转到其保护模式部分时,Bochs都会出现三重故障,并给出以下信息:Assembly 进入保护模式并执行远jmp后出现三重故障,assembly,x86,nasm,bootloader,osdev,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
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
我对保护模式有点缺乏经验,因此对它只了解一点。有人能帮我吗?你的GDT没有问题。问题在于lgdt之后我们将做什么 有两种解决方案 不要在内核中使用段 这是我的内核代码,应该从2000h:0000h加载并使用NASM组装 内核加载的地址实际上是0x20000,但您决定使用段寄存器将该基址存储为0x2000,这对于实模式操作系统很好,但对于保护模式则不太好,因为符号main的偏移量为零。您可以更改内核加载到可以用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之前
使用bochs调试器验证GDT。您的实际模式段值似乎是错误的,因此您加载了错误的GDT,但无法真正分辨,因为这不是一个。正如Jester指出的,这不是一个MCVE。如果你也发布你的引导加载程序,那会有帮助的。但是,请注意,您需要GDT指针中的线性地址,而不是realmode offset。gdtPointer将gdtStart作为GDT的基础。问题是,它与从0x2000:0x0000开始的实模式段有关。0x2000:0x0000是线性地址0x2000I注意到了一些东西,它可能与Jester关于片段的评论有关。您说内核在2000h:0000h加载,然后向我们显示代码和boxhs输出。在bochs输出中,它表示CS设置为0x0800 CS:0800 0004 | 0 | 0。我的问题是-您是否在0x0800:0x0000或0x2000:0x0000处加载了该代码?BOCHs输出根本不表明您是从0x2000:0x0000运行的。@MichaelPetch在调试期间的某一点上,我稍微更改了引导加载程序代码,以便从0800:0000加载内核。我已经编辑了Bochs输出。@MichaelPetch您关于向gdtStart添加20000h的建议,而远跳偏移量给了我以下信息:write_virtual_checks:write over limit,r/wHe已经有一个CLI在LGDT上方几行:call printString_16 CLI mov si,msgSuccess。中断已经停止了——这不是他的问题的原因。请参阅问题和重复问题下的注释,了解错误所在以及如何修复。@MichaelPetch现在已更改了答案,但您在代码中找到的cli并不专用于切换到保护模式。如果在切换之前添加cli,则可能有助于提高代码的可读性。如果您知道中断已被禁用,请在cli距离较远时添加注释。在汇编语言中,您不会为了人类的利益而使用冗余指令;没有编译器来优化它们。@PeterCordes是的,你是对的。在实模式下,我们不需要任何冗余的代码或字节,因为实模式下RAM的寻址空间对我们来说太小了。如果每个字节和/或时钟周期都无关紧要,那么您可以使用C或其他语言编写代码,并让编译器进行代码生成。
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
jmp CODESEGMENT:main+0x20000
; NOTE: This operation needs you to have interrupt flag clear