Dalex
Dalex

Reputation: 61

Can't Access 32 Bit in Protected Mode

During the development of a small kernel, I stumbled across a strange problem when booting up application processors using the APIC.

As stated on OSDev and the Intel-Manual, the processor first enters Real-Mode, and my goal is to get it to operate in Protected-Mode. After setting up a small stack and enabling the "A20"-Line and far-jumping to my 32-bit code, i tried to clear eax using xor eax, eax for sanity purposes.

To my suprise, only the lower word of eax got cleared, but the high word remained unchanged.

Running the kernel in QEMU

Funnily, if I simply do an xor ax, ax, instead of xor eax, eax, the register is cleared entirely.

Below is my code for bootstrapping an application processor using the 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 $

Moreover, I have also tried to assign a 32-bit value into a register, e.g. mov eax, 0xDEADBEEF, but only the BEEF part remains.

Does anyone have any idea why this is not working?

Upvotes: 0

Views: 347

Answers (2)

zwol
zwol

Reputation: 140445

; Enable Protected Mode
mov eax, cr0
or eax, 0x1
mov cr0, eax

; Far jump
push 0x8
push __smp_ap_pm_entry
retf

When the architecture manual says a MOV CR0 that alters the PE bit must be immediately followed by a FAR JMP, they mean the specific instruction FAR JMP (opcode EA or FF, depending on how you want to express the operand), and they mean it must be the very next instruction. If you don't do this the effects are unpredictable. I suspect your emulator doesn't actually flip the switch until it executes a FAR JMP, so you're still in 16-bit real mode and the machine instruction 31 c0 still means xor ax,ax rather than xor eax, eax.

FAR JMP takes an explicit, absolute segment:offset expression; you can't just write jmp far __smp_ap_pm_entry. I don't know what exactly you will need to write.

(See section 9.9, "Mode Switching", of the Intel® 64 and IA-32 Architectures Software Developer's Manual Combined Volumes 3A, 3B, 3C, and 3D: System Programming Guide. If you haven't already read this manual, now would be an excellent time.)

Upvotes: 1

Dalex
Dalex

Reputation: 61

As @sj95126 hinted, it seemed like I had loaded the wrong GDT. After creating and loading a GDT with 32-bit Segment-Descriptors, the problem was resolved.

If anyone is interested, below is the code for my new GDT used for switching from Real-Mode to Protected-Mode:

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
    }
};

Upvotes: 2

Related Questions