【问题标题】:Error After Switching to Protected Mode and Making a Far Jump切换到保护模式并进行远跳后出错
【发布时间】:2017-01-27 08:46:01
【问题描述】:

我正在使用 FASM(平面汇编程序)编写引导加载程序。我在 16 位模式下成功,但在切换到 32 位模式时遇到错误。我查看了类似问题的答案(GPF after far jump to protected mode 上的实际上相同的问题),但该解决方案并不能解决我的问题。

这是我的引导加载程序 -

org 0x7c00

jmp main

include 'bios.asm'
include 'print32.asm'
include 'gdt.asm'

main:

mov bp,0x9000
mov sp,bp

mov bx, bootMsg
call print_string

lgdt [gdt_descriptor]
cli
mov eax, cr0
or eax, 0x1
mov cr0, eax
jmp CODE_SEG:init_pm   ;**The error seems to occurs here

jmp $

bits = 32

init_pm:
    mov ax,DATA_SEG
    mov ds,ax
    mov ss,ax
    mov es,ax

    mov ebp, 0x90000
    mov esp, ebp
    jmp BEGIN_PM

BEGIN_PM:
    mov ebx, pmMsg
    call print_string32
    jmp $

pmMsg:
    db "Sucessfully switched to the 32-bit protected mode....",0

bootMsg:
    db "Booted in 16-bit Real Mode mode....",0

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

这是 GDT -

gdt_start:
gdt_null:
    dd 0x0
    dd 0x0

gdt_code:
    dw 0xffff
    dw 0x0
    db 0x0
    db 10011010b
    db 11001111b
    db 0x0

gdt_data:
    dw 0xffff
    dw 0x0
    db 0x0
    db 10010010b
    db 11001111b
    db 0x0
gdt_end:

gdt_descriptor:
    dw gdt_end - gdt_start - 1
    dd gdt_start

CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start 

她是 Bochs 控制台输出 -

00478171069i[BIOS  ] Booting from 0000:7c00
00478195765e[CPU0  ] write_virtual_checks(): write beyond limit, r/w
00478195765e[CPU0  ] interrupt(): gate descriptor is not valid sys seg (vector=0x0d)
00478195765e[CPU0  ] interrupt(): gate descriptor is not valid sys seg (vector=0x08)
00478195765i[CPU0  ] CPU is in protected mode (active)
00478195765i[CPU0  ] CS.mode = 32 bit
00478195765i[CPU0  ] SS.mode = 32 bit
00478195765i[CPU0  ] EFER   = 0x00000000
00478195765i[CPU0  ] | EAX=d88e0010  EBX=00007d77  ECX=00090000  EDX=00000000
00478195765i[CPU0  ] | ESP=00009000  EBP=00000000  ESI=000e0000  EDI=0000ffac
00478195765i[CPU0  ] | IOPL=0 id vip vif ac vm RF nt of df if tf sf zf af PF cf
00478195765i[CPU0  ] | SEG sltr(index|ti|rpl)     base    limit G D
00478195765i[CPU0  ] |  CS:0008( 0001| 0|  0) 00000000 ffffffff 1 1
00478195765i[CPU0  ] |  DS:0000( 0005| 0|  0) 00000000 0000ffff 0 0
00478195765i[CPU0  ] |  SS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00478195765i[CPU0  ] |  ES:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00478195765i[CPU0  ] |  FS:0000( 0005| 0|  0) 00000000 0000ffff 0 0
00478195765i[CPU0  ] |  GS:0000( 0005| 0|  0) 00000000 0000ffff 0 0
00478195765i[CPU0  ] | EIP=00007d2f (00007d2f)
00478195765i[CPU0  ] | CR0=0x60000011 CR2=0x00000000
00478195765i[CPU0  ] | CR3=0x00000000 CR4=0x00000000
00478195765i[CPU0  ] 0x0000000000007d2f>> or dword ptr ds:[eax], eax : 0900
00478195765e[CPU0  ] exception(): 3rd (13) exception with no resolution, shutdown status is 00h, resetting

谁能帮我解决这个问题?这一直困扰着我很久..

编辑-

这是 print32 代码-

use32

VIDEO_MEM equ 0xb8000
W_O_B equ 0x0f

print_string32:
    pusha
    mov edx,VIDEO_MEM

print_string32_loop:
    mov al, [ebx]
    mov ah, W_O_B
    cmp al,0
    je print_string32_end
    mov [edx],ax
    inc ebx
    add edx,2
    jmp print_string32_loop

print_string32_end:
    popa
    ret

以及引导加载程序的更改代码 -

org 0x7c00

mov bp,0x9000
mov sp,bp

mov bx, bootMsg
call print_string

cli
lgdt [gdt_descriptor]
mov eax, cr0
or eax, 0x1
mov cr0, eax
jmp 0x8:init_pm

jmp $

use32

init_pm:
    mov ax, 0x10
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov ebp,0x90000
    mov esp,0x90000

    jmp BEGIN_PM

jmp $

include 'bios.asm'
include 'gdt.asm'
include 'print32.asm'

use32

BEGIN_PM:
    mov ebx, pmMsg
    call print_string32
    jmp $

pmMsg:
    db "Sucessfully switched to the 32-bit protected mode....",0

bootMsg:
    db "Booted in 16-bit Real Mode mode....",0

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

【问题讨论】:

  • 您是否尝试过使用 Bochs 内部调试器逐步检查指令以找出哪一个故障?我可能读错了,但您似乎将其设为保护模式,然后在地址 0x7d2f 处的 dword ptr ds:[eax], eax 之类的指令中失败。 EAX 似乎包含垃圾并被用作指针,并且 DS 似乎设置为 0 的段选择器,我原本预计它是 0x10。
  • 与您的问题无关,但mov bp,0x9000 mov sp,bp 不是设置实模式堆栈指针的合适方法。您应该设置 SS 后跟 SP。如果您希望堆栈位于 0x0000:0x9000 或物理地址 0x9000,请执行 xor ax, ax mov ss, ax mov sp, 0x9000
  • 我没有使用任何调试器,但是当 jmp CODE_SEG:init_pm 不存在时,系统会在没有任何错误的情况下停止,但 bochs 不会通知切换到保护模式。
  • 坦率地说or dword ptr ds:[eax], eax 似乎是一个无意义的指令。几乎暗示您可能一直在执行数据作为指令
  • 否 @RossRidge:我现在绝对肯定问题出在他们显示的代码中,这是因为他们没有正确设置 32 位指令生成。我认为发生的事情是init_pm解码错误之后的代码,奇怪的dword ptr ds:[eax], eax指令是从这些指令的部分解码mov ebp, 0x90000mov esp, ebp

标签: assembly x86 bootloader fasm bochs


【解决方案1】:

TL;DR:修复将 bits = 32 更改为 use32

让 FASM 为在 32 位模式下运行的处理器生成指令。 第 1.1.4 节输出格式中的FASM Documentation 状态:

默认情况下,当源文件中没有格式指令时,平面汇编程序只是将生成的指令代码输出到输出中,这样就创建了平面二进制文件。 默认它会生成 16 位代码,但您始终可以使用 use16 或将其转换为 16 位或 32 位模式 use32 指令。

您在将NASM 代码转换为FASM 时使用了bits = 32,而FASM 不接受bits 32。 bits=32 将名为 bits 的常量值设置为值 32。它不会告诉 FASM 生成供 32 位模式下的处理器使用的指令。虽然bits = 32 组装没有错误,但它并没有达到您的预期。

通过不使用 use32,您告诉 FASM 在 init_pm 之后生成代码,其中的指令使用 32 位地址和在 16 位实模式下工作的操作数,而不是使用在 32 位保护模式下工作的 32 位地址和操作数。


虽然我无法测试您的代码,但我将在尝试了解您发布的代码可能发生的情况时进行这些观察。首先 BOCHS 转储这些行:

[CPU0  ] 0x0000000000007d2f>> or dword ptr ds:[eax], eax : 0900
[CPU0  ] exception(): 3rd (13) exception with no resolution, shutdown status is 00h, resetting

这表示在地址 0x7d2f 处遇到指令 or dword ptr ds:[eax], eax(其编码为 0900)并生成异常 13(一般保护错误)。

BOCHS 状态转储中的某些内容表明您处于保护模式:

CPU is in protected mode (active)

另外还有一个迹象表明jmp CODE_SEG:init_pm 正确执行。这是因为在您发生错误时 BOCHS 转储了 CS:0008,这意味着 CS 被设置为值 0008 (= CODE_SEG)。 DS 选择器为 0,这是不寻常的,因为在 JMP 之后您将其设置为 DATA_SEG (0x10) 但 ES 和 SS 段选择器设置为 0x10。这一切都表明init_pm 代码已被执行,但不知何故它最终没有按预期执行。

此时我意识到您已经编写了bits = 32,它有效地将一个常量设置为值 32。它没有告诉 FASM 生成将针对将要执行的 CPU 的代码在 32 位模式下。

考虑到这一点,我决定接受指令并让汇编程序将它们编码为 16 位模式:

init_pm:
    mov ax,DATA_SEG
    mov ds,ax
    mov ss,ax
    mov es,ax

    mov ebp, 0x90000

当我使用 -b32 选项(强制 NDISASM 解码为 32 位目标)将我的测试代码与 NDISASM 转储时,它将其解码为:

00000000  B810008ED8        mov eax,0xd88e0010
00000005  8ED0              mov ss,eax
00000007  8EC0              mov es,eax
00000009  66BD0000          mov bp,0x0
0000000D  0900              or [eax],eax
0000000F  6689EC            mov sp,bp

错误解码首先导致mov eax, 0xd88e0010。这解释了为什么在您的 BOCHS 转储中有 EAX=d88e0010 。 EAX 的低 16 位已移至 ES,因此 ES=0x0010 与您的 BOCHS 输出 ES:0010 匹配。类似的事情适用于 SS 被设置。 BP 设置为 0,这在 BOCHS 输出 BP:0000 中得到确认。该指令导致故障和崩溃:

0000000D  0900              or [eax],eax

or [eax],eax 与 or ds:[eax],eax 相同。 [eax] 隐式引用 DS 。将此指令与 BOCHS 输出进行比较:

0x0000000000007d2f>> or dword ptr ds:[eax], eax : 0900

啊哈,这就是这个不寻常的指令的来源(你可以忽略DWORD PTR)。错误解码的指令尝试使用指向 NULL (0x0000) 描述符的 DS。这会导致处理器故障,以及 BOCHS 和状态转储报告的后续错误。


正如我在 cmets 中所述,在 BOCHS 中使用内部调试器非常有价值,尤其是在调试引导加载程序和内核时。如果您在调试器中逐个执行引导加载程序指令,您可能会发现您的 FAR JMP 到 init_pm 的工作正常。然后您会观察到正在执行的意外指令最终导致processor fault。

【讨论】:

  • 抱歉回复晚了,谢谢!!有用。现在没有显示错误。但是出现了另一个问题 - Bochs 现在没有输出任何到保护模式的切换,并且 print32 没有打印任何东西。你能帮我找出问题所在吗?我已经编辑了代码并发布了 print32.asm 文件。
  • @AneeshSharma 。您的新问题应该是一个新问题,并且可能通过使用调试器来解决,但是至于为什么您的打印不起作用,我看不到您实际更新视频内存的位置。好像你可能在je print_string32_end之后丢失了mov [edx], ax。
  • 我犯了一个愚蠢的错误。我添加了,但它仍然不起作用。
  • 是的,它确实打印出来了
  • @AneeshSharma 现在你有了use32,你确定bios.asm中的16位代码有use16吗?
猜你喜欢
  • 2012-10-10
  • 1970-01-01
  • 2022-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-16
  • 1970-01-01
  • 2020-06-08
相关资源
最近更新 更多