我是根据尼克·布伦德尔的书来做这件事的。我写了一个MBR程序,它首先运行在实模式下,程序中的一些指令会将CPU切换到保护模式。 首先我这样设置 GDT:
gdt_start:
gdt_null:
dd 0x0
dd 0x0
gdt_code:
dw 0xffff
dw 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
然后CPU运行以下指令:
cli
lgdt [gdt_descriptor]
mov eax,cr0
or eax,0x1
mov cr0,eax ;this will set the cpu to protected-mode
;jmp $ ;I use this instrction to find where is wrong
jmp CODE_SEG:init_pm
jmp $
[bits 32]
init_pm:
jmp $
mov ax,10
jmp $
mov ds,eax
mov ss,eax
jmp $
mov es,ax
mov fs,ax
mov gs,ax
mov ebp,0x90000
mov esp,ebp
call BEGIN_PM
指令jmp CODE_SEG:init_pm
会导致CPU崩溃并重启。如果我将其更改为jmp init_pm
,则以下指令mov ax,10
将导致CPU崩溃并重新启动。而且书上说切换操作需要跳远。
可以帮我做一下切换操作吗?
最佳答案
您的代码中存在几个问题:
- 您的
gdt_code
描述符中缺少基数高位字的低字节。只需在dw 0x0
之后添加db 0x0
即可。 - Frank Kotler 写道,
gdt_descriptor
必须包含线性地址。是的,确实如此,但这并不是唯一需要线性地址的地方。您可以在任何代码之前使用ORG
指令,或手动将 origin 添加到此字段。 - 指令
lgdt
仍然使用ds
寄存器进行seg:off
地址计算。在org
下编写代码时,应将其设置为零。 - 远跳转到保护模式也需要线性地址作为偏移量。请记住,此跳转是在兼容模式下执行的(因为它是保护模式切换后执行的第一条指令)。它使用两个字节作为段选择器(冒号之前),仅使用两个字节作为偏移量(冒号之后)。这意味着您不应尝试跳转到高于
0xFFFF
的地址。再次检查代码的来源。 - 你所说的会导致cpu崩溃并重启的
mov ax, 10
指令,使用了错误的值!我们需要的选择器位于偏移量 16 处,即十六进制的 0x10。由于您定义了DATA_SEG equ gdt_data - gdt_start
,您确实应该编写mov ax, DATA_SEG
。
关于assembly - 我无法将 cpu 从实模式切换到保护模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18568711/