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
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.