assemblyx86bootloaderfasmbochs

Error After Switching to Protected Mode and Making a Far Jump


I am writing a boot loader using FASM (the Flat Assembler). I was successful in 16-bit mode but I am facing an error in switching to 32-bit mode. I looked at an answer to a similar (infact same problem at GPF after far jump to protected mode) but the solution doesn't solve my problem.

Here's my boot loader -

org 0x7c00

jmp main

include 'bios.asm'
include 'print32.asm'
include 'gdt.asm'

main:

mov bp,0x9000
mov sp,bp

mov bx, bootMsg
call print_string

lgdt [gdt_descriptor]
cli
mov eax, cr0
or eax, 0x1
mov cr0, eax
jmp CODE_SEG:init_pm   ;**The error seems to occurs here

jmp $

bits = 32

init_pm:
    mov ax,DATA_SEG
    mov ds,ax
    mov ss,ax
    mov es,ax

    mov ebp, 0x90000
    mov esp, ebp
    jmp BEGIN_PM

BEGIN_PM:
    mov ebx, pmMsg
    call print_string32
    jmp $

pmMsg:
    db "Sucessfully switched to the 32-bit protected mode....",0

bootMsg:
    db "Booted in 16-bit Real Mode mode....",0

times 510-($-$$) db 0
dw 0xaa55

Here's the GDT -

gdt_start:
gdt_null:
    dd 0x0
    dd 0x0

gdt_code:
    dw 0xffff
    dw 0x0
    db 0x0
    db 10011010b
    db 11001111b
    db 0x0

gdt_data:
    dw 0xffff
    dw 0x0
    db 0x0
    db 10010010b
    db 11001111b
    db 0x0
gdt_end:

gdt_descriptor:
    dw gdt_end - gdt_start - 1
    dd gdt_start

CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start 

Hers is th Bochs console output -

00478171069i[BIOS  ] Booting from 0000:7c00
00478195765e[CPU0  ] write_virtual_checks(): write beyond limit, r/w
00478195765e[CPU0  ] interrupt(): gate descriptor is not valid sys seg (vector=0x0d)
00478195765e[CPU0  ] interrupt(): gate descriptor is not valid sys seg (vector=0x08)
00478195765i[CPU0  ] CPU is in protected mode (active)
00478195765i[CPU0  ] CS.mode = 32 bit
00478195765i[CPU0  ] SS.mode = 32 bit
00478195765i[CPU0  ] EFER   = 0x00000000
00478195765i[CPU0  ] | EAX=d88e0010  EBX=00007d77  ECX=00090000  EDX=00000000
00478195765i[CPU0  ] | ESP=00009000  EBP=00000000  ESI=000e0000  EDI=0000ffac
00478195765i[CPU0  ] | IOPL=0 id vip vif ac vm RF nt of df if tf sf zf af PF cf
00478195765i[CPU0  ] | SEG sltr(index|ti|rpl)     base    limit G D
00478195765i[CPU0  ] |  CS:0008( 0001| 0|  0) 00000000 ffffffff 1 1
00478195765i[CPU0  ] |  DS:0000( 0005| 0|  0) 00000000 0000ffff 0 0
00478195765i[CPU0  ] |  SS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00478195765i[CPU0  ] |  ES:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00478195765i[CPU0  ] |  FS:0000( 0005| 0|  0) 00000000 0000ffff 0 0
00478195765i[CPU0  ] |  GS:0000( 0005| 0|  0) 00000000 0000ffff 0 0
00478195765i[CPU0  ] | EIP=00007d2f (00007d2f)
00478195765i[CPU0  ] | CR0=0x60000011 CR2=0x00000000
00478195765i[CPU0  ] | CR3=0x00000000 CR4=0x00000000
00478195765i[CPU0  ] 0x0000000000007d2f>> or dword ptr ds:[eax], eax : 0900
00478195765e[CPU0  ] exception(): 3rd (13) exception with no resolution, shutdown status is 00h, resetting

Can anyone please help me with this? This has been troubling me since long..

EDIT-

Here's the print32 code-

use32

VIDEO_MEM equ 0xb8000
W_O_B equ 0x0f

print_string32:
    pusha
    mov edx,VIDEO_MEM

print_string32_loop:
    mov al, [ebx]
    mov ah, W_O_B
    cmp al,0
    je print_string32_end
    mov [edx],ax
    inc ebx
    add edx,2
    jmp print_string32_loop

print_string32_end:
    popa
    ret

And the changed code for the bootloader -

org 0x7c00

mov bp,0x9000
mov sp,bp

mov bx, bootMsg
call print_string

cli
lgdt [gdt_descriptor]
mov eax, cr0
or eax, 0x1
mov cr0, eax
jmp 0x8:init_pm

jmp $

use32

init_pm:
    mov ax, 0x10
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov ebp,0x90000
    mov esp,0x90000

    jmp BEGIN_PM

jmp $

include 'bios.asm'
include 'gdt.asm'
include 'print32.asm'

use32

BEGIN_PM:
    mov ebx, pmMsg
    call print_string32
    jmp $

pmMsg:
    db "Sucessfully switched to the 32-bit protected mode....",0

bootMsg:
    db "Booted in 16-bit Real Mode mode....",0

times 510-($-$$) db 0
dw 0xaa55

Solution

  • TL;DR : To fix change bits = 32 to use32

    To get FASM to generate instructions for a processor running in 32-bit mode. The FASM Documentation states in Section 1.1.4 Output Formats :

    By default, when there is no format directive in source file, flat assembler simply puts generated instruction codes into output, creating this way flat binary file. By default it generates 16-bit code, but you can always turn it into the 16-bit or 32-bit mode by using use16 or use32 directive.

    It appears you used bits = 32 when converting NASM code to FASM and bits 32 wasn't accepted by FASM. bits=32 sets a constant value named bits to the value 32. It doesn't tell FASM to generate instructions to be used by a processor in 32-bit mode. Although bits = 32 assembled without error, it did not do what you expected.

    By not using use32 you told FASM to generate code after init_pm with instructions that use 32-bit addresses and operands that work in 16-bit real mode, rather than instructions that use 32-bit addresses and operands that work in 32-bit protected mode.


    Although I can't test your code I'm going to make these observations while I was trying to understand what might have been going on with the code you did post. First of all BOCHS dumps these lines:

    [CPU0  ] 0x0000000000007d2f>> or dword ptr ds:[eax], eax : 0900
    [CPU0  ] exception(): 3rd (13) exception with no resolution, shutdown status is 00h, resetting
    

    This says that at address 0x7d2f the instruction or dword ptr ds:[eax], eax was encountered (its encoding being 0900) and generated exception 13 (General Protection Fault).

    There are things in the BOCHS state dump at the time that suggest you are in protected mode:

    CPU is in protected mode (active)
    

    Additionally there is an indication that jmp CODE_SEG:init_pm executed properly. This is suggested by the fact that at the time of your error BOCHS dumped CS:0008 which means that CS was set to the value 0008 (= CODE_SEG). The DS selector is 0 which is unusual given that after the JMP you set it to DATA_SEG (0x10) BUT the ES and SS segment selectors were set to 0x10. This all suggests the init_pm code was executed but somehow it ended up not executing as expected.

    At this point I realized you had written bits = 32 which effectively sets a constant to the value 32. It doesn't tell FASM to generate code that will be targeting a CPU that will be executing in 32-bit mode.

    With this in mind I decided to take the instructions and have the assembler encode them for 16-bit mode:

    init_pm:
        mov ax,DATA_SEG
        mov ds,ax
        mov ss,ax
        mov es,ax
    
        mov ebp, 0x90000
    

    When I dumped my test code with NDISASM using the -b32 option (to force NDISASM to decode as a 32-bit target), it decoded it to:

    00000000  B810008ED8        mov eax,0xd88e0010
    00000005  8ED0              mov ss,eax
    00000007  8EC0              mov es,eax
    00000009  66BD0000          mov bp,0x0
    0000000D  0900              or [eax],eax
    0000000F  6689EC            mov sp,bp
    

    The incorrect decoding first did mov eax, 0xd88e0010. This explains why in your BOCHS dump you have EAX=d88e0010 . The lower 16-bits of EAX got moved to ES so ES=0x0010 which matches your BOCHS output ES:0010 . Similar thing applies to SS being set. BP was set to 0 which is confirmed in the BOCHS output BP:0000. This instruction causes the fault and crash:

    0000000D  0900              or [eax],eax
    

    or [eax],eax is the same as or ds:[eax],eax . [eax] has an implicit reference to DS . Compare this instruction to the BOCHS output:

    0x0000000000007d2f>> or dword ptr ds:[eax], eax : 0900
    

    AHA, that is where this unusual instruction came from (you can ignore the DWORD PTR). The incorrectly decoded instruction attempts to use DS which point to the NULL (0x0000) descriptor. This causes a processor fault, and the subsequent error reported by BOCHS and the state dump.


    As I stated in the comments, using the internal debugger in BOCHS is valuable especially when debugging bootloaders and kernels. If you had stepped through the bootloader instructions one at a time in the debugger you would have probably found that your FAR JMP to init_pm worked as expected. You would have then observed unexpected instructions being executed that eventually led to a processor fault.