assemblyx86kernelosdevprotected-mode

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?


Solution

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