在保护模式下无法访问 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 位的主要内容,如果未能解决你的问题,请参考以下文章

实模式/保护模式

小5聊Winform窗体遍历进程提示拒绝访问以及32位无法访问64位模块解决方法

无法访问受保护的成员[重复]

关于32位程序的内存

内网不同网段的电脑无法访问linux网站服务器下的网页?

内核保护模式之分段机制