I coded this small bootloader that prints a single character to the screen in 32-bit protected mode:
bits 16
org 0x7c00
jmp boot
times 3-($-$$) db 0x90 ; Support 2 or 3 byte encoded JMPs before BPB.
; Dos 4.0 EBPB 1.44MB floppy
OEMname: db "mkfs.fat" ; mkfs.fat is what OEMname mkdosfs uses
bytesPerSector: dw 512
sectPerCluster: db 1
reservedSectors: dw 1
numFAT: db 2
numRootDirEntries: dw 224
numSectors: dw 2880
mediaType: db 0xf0
numFATsectors: dw 9
sectorsPerTrack: dw 18
numHeads: dw 2
numHiddenSectors: dd 0
numSectorsHuge: dd 0
driveNum: db 0
reserved: db 0
signature: db 0x29
volumeID: dd 0x2d7e5a1a
volumeLabel: db "NO NAME "
fileSysType: db "FAT12 "
boot:
mov ax, 0x2401
int 0x15 ; enable A20 bit
mov ax, 0x0003 ; change the video mode to 0x03
int 0x10
lgdt [gdt_pointer] ; load the gdt table
mov eax, cr0
or eax, 0x1 ; set the protected mode bit on special CPU reg cr0
mov cr0, eax
jmp code_seg:main ; long jump to the code segment code_seg
gdt_start:
dq 0x0
gdt_code:
dw 0xFFFF
dw 0
db 0
db 0b10011010
db 0b11001111
db 0
gdt_data:
dw 0xFFFF
dw 0
db 0
db 0b10010010
db 0b11001111
db 0
gdt_end:
gdt_pointer:
dw gdt_end - gdt_start
dd gdt_start
code_seg equ gdt_code - gdt_start
data_seg equ gdt_data - gdt_start
bits 32
main:
mov ax, data_seg
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov ebp, 0x90000
mov esp, ebp
mov ebx, 0xb8000
mov al, '!'
mov ah, 0b00001111
mov word[ebx], ax
cli
jmp $
times 510 - ($-$$) db 0
dw 0xaa55
This code works fine on a VM, but it causes a couple of screen flashes and a reboot on a real machine (I have tested this on two PCs). Changing a few lines (deleting the "bits 32", "or eax, 0x1", and replacing "jmp code_seg:main" with "jmp main") to use 16 bit mode instead of 32 bit protected causes the code to work just fine. However, when I use 32-bit mode, it fails. Why is this? Thanks in advance.
The problem was that you didn't initialise ds
to zero. You're using the following lgdt
instruction:
lgdt [gdt_pointer] ; load the gdt table
This memory access implicitly uses the default segment, which is ds
for an address without bp
. You're using org 7C00h
so you want ds
as zero. However, it is valid for the ROM-BIOS loader to leave any possible value in ds
when it transfers control to your loader. The solution is to add the following prior to the lgdt
instruction:
xor ax, ax
mov ds, ax
This zeros the ax
register then uses it to initialise ds
to zero.
This solution is also provided in Michael Petch's General Tips for Bootloader Development.