Search code examples
assemblynasmx86-64bootloaderprotected-mode

Boot loader makes different results after lgdt in different virtual machines


I'm writing a simple boot loader. The boot loader is compiled from two assembly files: boot.asm, protected_start.asm. boot.asm loads protected_start to 0x10000, sets GDTR, enters protected mode and jump to 0x10000. So the encoding is set to 16-bit in boot.asm, 32-bit in protected_start.asm.

I use QEMU, VMware and NASM on Windows. Both virtual machines have 128MB of memory. After entering protected mode, QEMU successfully jumps to 0x10000 and prints given string, but VMWare reboots. I used "jmp $" to seek where the error occurs, and found "jmp dword 0x08:0x0000" in boot.asm makes the error.

The version of QEMU is 20180519, VMware is Workstation Player 14.1.2 build-8497320.

Why "jmp dword 0x08:0x0000" after LGDT makes error? What do I have to do to resolve this problem?

boot.asm:

[org 0x7c00]
[bits 16]

load:
    mov     ax,     0x1000
    mov     es,     ax
    mov     bx,     0x0000;

    mov     ah,     0x02;   Read sectors from drive
    mov     al,     0x08;   Number of sectors to read
    mov     ch,     0x00;   Cylinder index
    mov     cl,     0x02;   Sector
    mov     dh,     0x00;   Head
    mov     dl,     0x00;   Drive

    int     0x13
    jc      load

    cli

    lgdt    [gdtr]
    mov     eax,    cr0
    or      eax,    0x00000001
    mov     cr0,    eax

    jmp     dword   0x08:0x0000

gdt_list:
    dw      0
    dw      0
    db      0
    db      0
    db      0
    db      0

.gdt_code:
    dw      0xFFFF
    dw      0x0000
    db      0x01
    db      0x9A
    db      0xCF
    db      0x00

.gdt_data:
    dw      0xFFFF
    dw      0x0000
    db      0x00
    db      0x92
    db      0xCF
    db      0x00

.gdt_video:
    dw      0xFFFF
    dw      0x8000
    db      0x0B
    db      0x92
    db      0xCF
    db      0x00

gdtr:
    dw      4*8
    dd      gdt_list
    times 510 - ($ - $$) db 0
    dw 0xAA55

protected_start.asm:

[org 0x10000]
[bits 32]

protected:

    mov     ax,     0x10

    mov     ds,     ax
    mov     fs,     ax
    mov     gs,     ax
    mov     ss,     ax
    mov     esp,    0xFFFF
    mov     ebp,    0xFFFF

    mov     ax,     0x18
    mov     es,     ax

    mov     edi,    0x0000
    mov     esi,    my_str
    call    write

    jmp     $
write:
    push    eax
    push    ebx
    mov     ah,     0x0e
    mov     bx,     0x0007
.loop:
    lodsb
    or      al,     al
    jz      .end
    mov     byte [es:edi],  al
    inc     di
    mov     byte [es:edi],  0x0f
    inc     di
    jmp     .loop
.end:
    pop     ebx
    pop     eax
    ret

my_str:
    db "Protected", 0x0 

times 512 - ($ - $$) db 0

What I used to invoke virtual machines in PowerShell:

qemu-system-x86_64 -m 128 -smp 1 -fda ".\bin\os.img"
Invoke-Item ".\virtual\os.vmx"

Solution

  • Initializing ds before lgdt resolves the problem. Because lgdt [gdtr] actually means lgdt[ds:gdtr], if ds is not set, the error may occur. Since I used [org 0x7c00], I set ds to 0x0000. It works in both QEMU and VMware.

    The edited version of boot.asm:

    [org 0x7c00]
    [bits 16]
    
    load:
        ; edited part: initialize ds
        mov     ax,     0x0000
        mov     ds,     ax
    
        mov     ax,     0x1000
        mov     es,     ax
        mov     bx,     0x0000;
    
        mov     ah,     0x02;   Read sectors from drive
        mov     al,     0x08;   Number of sectors to read
        mov     ch,     0x00;   Cylinder index
        mov     cl,     0x02;   Sector
        mov     dh,     0x00;   Head
        mov     dl,     0x00;   Drive
    
        int     0x13
        jc      load
    
        cli
    
        lgdt    [gdtr]
        mov     eax,    cr0
        or      eax,    0x00000001
        mov     cr0,    eax
    
        jmp     dword   0x08:0x0000
    
    gdt_list:
        dw      0
        dw      0
        db      0
        db      0
        db      0
        db      0
    
    .gdt_code:
        dw      0xffff
        dw      0x0000
        db      0x01
        db      0x9a
        db      0xcf
        db      0x00
    
    .gdt_data:
        dw      0xffff
        dw      0x0000
        db      0x00
        db      0x92
        db      0xcf
        db      0x00
    
    .gdt_video:
        dw      0xffff
        dw      0x8000
        db      0x0b
        db      0x92
        db      0xcf
        db      0x00
    
    gdtr:
        dw      4*8
        dd      gdt_list
    
        times 510 - ($ - $$) db 0
        dw 0xAA55