TheMatt
TheMatt

Reputation: 23

Reset at jumping to Protected Mode

I'm developing a bootloader to load my OS (I didn't use GRUB, because I wanted to learn assembler), and my code triple faults and resets QEMU. Here is the code in question:

Bootloader

 [ org 0x7c00 ]
 [ BITS 16 ]

jmp 0x0000:Start

%include 'PrintFunc.asm'
%include 'DiskOp.asm'

Start:
    cli
    xor ax, ax
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov sp, 500h
    mov bp, 1500h
    cld
    sti
    
    mov ax, 0007h
    int 10h
    
    mov si, A1
    call printStr
    
    mov [BootDrive], dl
    
    mov al, 2
    
    call diskLoad
    
    mov si, A2
    call printStr
    
    jmp Cont
    
A1: db 'Loading sectors...', 0xA, 0xD, 0

BootDrive: db 0x00
    
times 510-($-$$) db 0
dw 0xAA55

A2: db 'Loaded two more sectors.', 0xA, 0xD, 0xA, 0
A3: db 'Checking A20...', 0xA, 0xD, 0
A4: db 'Enabling A20...', 0xA, 0xD, 0
A5: db 'A20 Enabled.', 0xA, 0xD, 0xA, 0
A6: db 'Loaded GDT, preparing to jump into PM.', 0xA, 0xD, 0
A7: db 'Landed in 32bit Protected Mode.', 0xA, 0xD, 0xA, 0
A8: db 0xA, 'Current FlameLoader version: ', 0
Ver: db '0.1', 0xA, 0xD, 0

GDT: 
    .NULL:
        dq 0
    
    .CodeSeg:
        dw 0FFFFh
        dw 0
        
        db 0
        db 010011010b
        db 011011111b
        db 0
    .DataSeg:
        dw 0FFFFh
        dw 0
        
        db 0
        db 010010010b
        db 011011111b
        db 0
    
    .end:
    
    .desc:
        dw .end - GDT - 1
        db GDT

%include 'A20Func.asm'
        
Cont:
    mov si, A3
    call printStr

    call testA20
    cmp ax, 1
    je EA20
    
    mov si, A4
    call printStr
    
    call enableA20
    
EA20:   ; A20 Enabled
    mov si, A5
    call printStr

    
    cli
    
    lgdt [GDT.desc]
    
    sti
        
    mov si, A6
    call printStr
    
    cli

    mov eax, cr0
    or eax, 1
    mov cr0, eax
    
    jmp 0x8:Init32

 [BITS 32]
    
Init32:
    
    jmp $   ; Debug
    
    mov ax, 0x10
    mov ds, ax
    mov ss, ax
    mov es, ax
    
    mov esp, 500h
    
    jmp Start32

%include 'Print32.asm'

Start32:
    
    mov esi, A7
    call PrintStr32
    
    jmp $   ; Actual program end

Based on BOCHS debugger and trying to stop the program at various points with jmp $, I have deduced that the problem lies with this line:

jmp 0x8:Init32

P.S. I didn't include the function files as I don't think they would be useful here.

Upvotes: 1

Views: 164

Answers (1)

Michael Petch
Michael Petch

Reputation: 47633

You don't show your disk load function but ultimately it would have to do something equivalent to:

    mov ax, 0201h
    mov cx, 0002h
    mov dh, 0
    mov bx, 7e00h
    int 13h

This would load CHS=(0,0,2) to ES:BX (0x0000:0x7e00) which is just after the bootloader. Alternatively you could have set ES:BX to 0x07e0:0x0000. Since you claim the problem seems the jump into protected mode I will assume the disk load is working.

That really only leaves the GDT that could be the problem other than a potential stack problem1. I do see a significant problem here that could cause the FAR JMP into protected mode to fail:

.desc:
    dw .end - GDT - 1
    db GDT

The GDT Record (GDTR) should be a WORD (dw) with the length-1 which you have done followed by a DWORD (dd). You have defined the base as a single byte db! You need to change that to dd. It should look like:

.desc:
    dw .end - GDT - 1
    dd GDT

NASM unfortunately won't try to tell you a value it stuffed into a byte was narrowed (chopped) from another value. This issue likely resulted in your problems as the code descriptor would have been invalid causing the jmp 0x8:Init32 to triple fault and cause a reboot.

If debugging with BOCHS you could have set a breakpoint at 0x7c00 and stepped through instruction by instruction until the instruction after lgdt [GDT.desc]. You would have been able to view the GDT with info gdt. You would likely discover that the base is wrong and all the entries are incorrect.


Additional Notes

  • 1You have set the stack pointer SS:SP to 0x0000:0x0500. The stack grows down from that address. 0x0000:0x0500 contains data for the BIOS Data Area (BDA) and just below it is the real mode interrupt vector table (IVT). You should put the stack somewhere safer. 0x0000:0x7c00 growing down below the bootloader leaves a lot of stack room before it would clobber the BDA.

  • You need to turn off interrupts before entering protected mode so you could have simply done a CLI at the beginning of your code and avoided all the CLI/STI instructions. Alternatively you could have kept them enabled the entire time and just done a CLI just before entering protected mode.

    It is often customary when doing things with interrupts enabled to update SS:SP together by first updating SS to a new value and SP. This is because updating SS has a side effect of disabling interrupts until after the following instruction. If updating SP is the following instruction then the operation is done atomically and no interrupt can occur between updating SS and SP. On some 8088 processors there was a defect where this didn't occur but that doesn't apply here since your code requires a 386+.

  • Setting BP in your real mode code has no effect since you aren't using stack frames anywhere and you aren't calling anything like a BIOS routine that requires BP to be set to a particular value. You also don't need to set GS and FS as your code isn't using them in real mode.

  • The start of your code could have appeared as:

    Start:
        xor ax, ax
        mov ds, ax
        mov es, ax
        mov ss, ax     ; Set SS:SP to grow down beneath bootloader at 0x0000:0x7c00
        mov sp, 7c00h
        cld
    
  • I have addition Bootloader tips in this Stackoverflow answer that may be of use.

Upvotes: 4

Related Questions