在保护模式下无法访问 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
    }
};