在保护模式下无法访问 32 位
Posted
技术标签:
【中文标题】在保护模式下无法访问 32 位【英文标题】:Can't Access 32 Bit in Protected Mode 【发布时间】:2021-10-12 18:21:26 【问题描述】:在开发小内核的过程中,我在使用 APIC 启动应用处理器时偶然发现了一个奇怪的问题。
正如OSDev 和英特尔手册所述,处理器首先进入实模式,我的目标是让它在保护模式下运行。在设置了一个小堆栈并启用“A20”-Line 并跳转到我的 32 位代码后,出于理智目的,我尝试使用 xor eax, eax
清除 eax
。
令我惊讶的是,只有eax
的低位字被清除,但高位字保持不变。
有趣的是,如果我只是做一个xor ax, ax
,而不是xor eax, eax
,那么寄存器就会被完全清除。
下面是我使用 APIC 引导应用处理器的代码:
; Extern reference to the GDTR
section .data
extern g_gdtr
; Serves as a temporary stack used for flushing the cpu-pipeline
SMP_BOOT_STACK_SIZE equ 64
smp_boot_stack:
dq 0
dq 0
dq 0
dq 0
section .text
global __smp_ap_rm_entry
align 0x1000
[bits 16]
; Real-Mode Entry point for other processor cores (AP's)
; after a INIT-SIPI-SIPI was issued
__smp_ap_rm_entry:
cli
xor ax, ax
mov ds, ax
mov ss, ax
lea bp, [ds:smp_boot_stack + SMP_BOOT_STACK_SIZE - 1]
mov sp, bp
; Enable A20 line
sti
in al, 0x92
or al, 2
out 0x92, al
cli
lgdt [ds:g_gdtr]
; Enable Protected Mode
mov eax, cr0
or eax, 0x1
mov cr0, eax
; Far jump
push 0x8
push __smp_ap_pm_entry
retf
align 4
[bits 32]
__smp_ap_pm_entry:
mov ax, word 0x10
; Doing this two times is somehow necessary (wtf?)
mov es, ax
mov es, ax
mov ss, ax
mov fs, ax
mov gs, ax
mov ds, ax
xor eax, eax
int 3 ; To check for changed values in qemu
jmp $
此外,我还尝试将 32 位值分配到寄存器中,例如mov eax, 0xDEADBEEF
,但只剩下 BEEF
部分。
有人知道为什么这不起作用吗?
【问题讨论】:
再次检查您的bits 32
是否有效且机器码是否正确。 xor ax, ax
工作而不是 xor eax, eax
通常意味着您的汇编程序发出 16 位代码。
如果您处于 32 位模式,为什么 CS 描述符显示为 CS64
?
@sj95126 我认为我没有正确理解您的答案。你在哪里遇到的?
在您发布的 QEMU 输出图像中,以CS =0008
开头的行。 32 位代码段应显示为 CS32
而不是 CS64
。你确定你的 GDT 是正确的吗?
@sj95126 非常感谢您的提示,这确实是一直存在的问题!
【参考方案1】:
正如@sj95126 所暗示的,我似乎加载了错误的 GDT。 使用 32 位段描述符创建和加载 GDT 后,问题得到解决。
如果有人感兴趣,下面是我用于从实模式切换到保护模式的新 GDT 的代码:
struct SegmentDescriptor32_s
u16 SegmentLimitLow;
u16 BaseAddressLow;
union
struct
u32 BaseAddressMiddle : 8;
u32 Type : 4;
u32 DescriptorType : 1;
u32 DescriptorPrivilegeLevel : 2;
u32 Present : 1;
u32 SegmentLimitHigh : 4;
u32 System : 1;
u32 LongMode : 1;
u32 DefaultBig : 1;
u32 Granularity : 1;
u32 BaseAddressHigh : 8;
;
u32 Flags;
;
__attribute__((packed));
__attribute__((aligned(0x1000)))
static struct SegmentDescriptor32_s smp_ap_gdt[] =
/* Null-Selector */
.SegmentLimitLow = 0x0,
.BaseAddressLow = 0x0,
.Flags = 0x0
,
/* Flat Code */
.SegmentLimitLow = 0xFFFF,
.BaseAddressLow = 0x0000,
.Flags = 0x00CF9A00
,
/* Flat Data */
.SegmentLimitLow = 0xFFFF,
.BaseAddressLow = 0x0000,
.Flags = 0x008F9200,
,
/* TSS */
.SegmentLimitLow = 0x68,
.BaseAddressLow = 0x0000,
.Flags = 0x00CF8900
;
【讨论】:
【参考方案2】:; Enable Protected Mode
mov eax, cr0
or eax, 0x1
mov cr0, eax
; Far jump
push 0x8
push __smp_ap_pm_entry
retf
当架构手册说改变 PE 位的 MOV CR0 必须紧跟 FAR JMP 时,它们的意思是特定指令 FAR JMP(操作码 EA 或 FF,取决于您想要的方式来表达操作数),他们的意思是它必须是下一条指令。如果你不这样做,后果是不可预测的。我怀疑你的模拟器在执行 FAR JMP 之前实际上并没有翻转开关,所以你仍然处于 16 位实模式,机器指令 31 c0
仍然意味着 xor ax,ax
而不是 xor eax, eax
。
FAR JMP 采用显式的绝对段:偏移量表达式;你不能只写jmp far __smp_ap_pm_entry
。我不知道你到底需要写什么。
(请参阅Intel® 64 and IA-32 Architectures Software Developer's Manual Combined Volumes 3A, 3B, 3C, and 3D: System Programming Guide 的第 9.9 节,“模式切换”。如果您还没有阅读本手册,那么现在是个好时机。)
【讨论】:
PE 位在 mov 指令上立即翻转。完成后,您将处于 16 位保护模式。在 JMP(或 RETF)之后,您处于 32 位保护模式(如果 GDT 具有该选择器的 32 位 CS)。用 mov 翻转位后的指令结果是定义和可预测的。您在 mov 之后处于 16 位保护模式,但 CS 中没有显式更改。如果mov
之后的指令是在实模式和 16 位保护模式下以相同方式解码的指令,则不需要立即通过 FAR JMP 或等效指令更改 PE 位。
解码的问题当然是指令预取队列不知道 PE 位已更改,mov
本身并没有刷新它。因此架构手册建议以下指令是刷新指令预取队列的东西(如 JMP)。它也不必是 FAR JMP。任何清除 IPFQ 的指令就足够了,包括但不限于近和远 JMP 和 CALL。
@MichaelPetch 我在这里引用了架构手册! “立即在 [MOV CR0] 之后,执行 FAR JMP 或 FAR CALL...如果两者之间存在 其他指令,则可能会发生随机故障”(强调我的)许可 lcall 和 ljmp 但就是这样。
您可能会在同一架构手册中注意到代码列表,他们使用的不是远 jmp。因为我无法访问手机上的架构手册,但有一个代码列表使用保护模式开关启动 ROM 示例。主要问题解决到 IPFQ以上是关于在保护模式下无法访问 32 位的主要内容,如果未能解决你的问题,请参考以下文章