在保护模式下无法访问 32 位
Can't Access 32 Bit in Protected Mode
在开发小内核的过程中,我在使用 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
部分。
有谁知道为什么这不起作用?
; 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 采用明确的绝对 segment:offset 表达式;你不能只写 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 节“模式切换”。如果您还没有阅读本手册,现在是个好时机。)
正如@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
}
};
在开发小内核的过程中,我在使用 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
部分。
有谁知道为什么这不起作用?
; 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 采用明确的绝对 segment:offset 表达式;你不能只写 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 节“模式切换”。如果您还没有阅读本手册,现在是个好时机。)
正如@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
}
};