Search code examples
assemblyx86operating-systemnasmbootloader

Loading GDT from Late Bootloader


I have an OS I'm working on currently and making a 2-stage bootloader called early bootloader (DfltBoot) and late bootloader (AdvBoot) and the early one does it's job and loads the late one and jumps to it's entry then starts executing code from there. Now that the late bootloader has been loaded, I want to switch into 32-bit Protected Mode (and 64-bit later but that's not important for now) and here's some of the code from both early and late bootloader. I won't put most of early bootloader's code, because it just does FAT12 loading stuff and loads the late bootloader into memory then starts executing it however I'm still gonna put the jump part to the late bootloader.

DfltBoot.asm (Early Bootloader/Boot Sector)

.ReadFinish: ; Gets jumped to whenever ADVBOOT.BIN get's loaded into memory
    mov [EBR_DriveNumber], dl ; Save the boot drive

    ; Set the segments again
    mov ax, ADVBOOT_LoadSegment
    mov ds, ax
    mov es, ax

    jmp ADVBOOT_LoadSegment:ADVBOOT_LoadOffset ; Segment = 0x2000 Offset=0x0
    jmp WaitKeyAndReboot ; Shouldn't get executed, late bootloader won't return.

The above code of the early bootloader works just fine, the late bootloader does get jumped to, because I'm still in 16-bit Real Mode I can print chars so I did those in the late bootloader and it does print the character, so the reading and the jumping part in the early bootloader should be working just fine.

The problems arise within some late code in the late bootloader tho, here is the full code of the late bootloader:

AdvBoot.asm (Late Bootloader/The actual bootloader)

org 0x0
bits 16

Start:
    cli
    lgdt [GDT_Descriptor]

    ; Set the 32-bit flag in control register 0
    mov eax, cr0
    or al, 1
    mov cr0, eax

    ;;; Problematic part
    jmp GDT_CodeSegment:ProtectedModeStart ; Far jump to flush the pipeline

bits 32

ProtectedModeStart:
    ; NOTE: This label doesn't get jumped to, I tried putting a jmp $ here
    ; and QEMU just crashed on me. If I put it before the far jump, it does
    ; actually stop and not crash, so I'd assume the far jump is the problem.
    mov ax, GDT_DataSegment
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov ebp, 0x90000
    mov esp, ebp

    mov edx, 0xb8000
    mov ah, 0x0f
    mov al, 'B'
    mov [edx], ax
    jmp $
    
    %include "Boot/GDT.asm"

As said by the comments as well, the far jump part, when executed crashes the entire computer and restarts and the cycle repeats. I'm not sure if it's relevant in this case but in case it is here's my GDT:

GDT.asm

GDT_Start:

GDT_Null:
    dd 0x0
    dd 0x0

GDT_Code:
    dw 0xFFFF
    dw 0
    db 0
    db 10011010b
    db 11001111b
    db 0

GDT_Data:
    dw 0xFFFF
    dw 0
    db 0
    db 10010010b
    db 11001111b
    db 0

GDT_End:

GDT_Descriptor:
    dw GDT_End - GDT_Start - 1
    dd GDT_Start

GDT_CodeSegment equ GDT_Code - GDT_Start
GDT_DataSegment equ GDT_Data - GDT_Start

I don't get what the problem is even about, is it segments, GDT or something else.


Solution

  • There are three crucial parts that need to use the proper addresses.

    1. The lgdt [GDT_Descriptor]. Since you load ds with ADVBOOT_LoadSegment and you have org 0x0 this is actually fine.
    2. The jmp GDT_CodeSegment:ProtectedModeStart. This needs an offset relative to the protected mode segment base which is zero. Thus you need to adjust it by the load segment such as jmp dword GDT_CodeSegment:ADVBOOT_LoadSegment*16+ProtectedModeStart. Note you need the dword because the offset does not fit in 16 bits. You could also set the base address of the segment but that is not recommended.
    3. The dd GDT_Start. This needs to be a linear address. Similarly to the previous point, you should adjust it like dd GDT_Start+ADVBOOT_LoadSegment*16

    All of these assume ADVBOOT_LoadOffset is zero. With these changes your code seems to be working (after I filled in the missing pieces since you did not provide a reproducible example).