I am writing a simple bootloader. Its main task is to load a kernel, and switch processor into unreal mode. My problem is when i turn on Unreal mode, the processor crashes. Here's my code (Some code used from MikeOS). I use NASM.
BITS 16
jmp short bootloader_start ; Jump past disk description section
nop ; Pad out before disk description
; ------------------------------------------------------------------
; Disk description table, to make it a valid floppy
; Note: some of these values are hard-coded in the source!
; Values are those used by IBM for 1.44 MB, 3.5" diskette
OEMLabel db "16DOSRUN" ; Disk label
BytesPerSector dw 512 ; Bytes per sector
SectorsPerCluster db 1 ; Sectors per cluster
ReservedForBoot dw 1 ; Reserved sectors for boot record
NumberOfFats db 2 ; Number of copies of the FAT
RootDirEntries dw 224 ; Number of entries in root dir
; (224 * 32 = 7168 = 14 sectors to read)
LogicalSectors dw 2880 ; Number of logical sectors
MediumByte db 0F0h ; Medium descriptor byte
SectorsPerFat dw 9 ; Sectors per FAT
SectorsPerTrack dw 18 ; Sectors per track (36/cylinder)
Sides dw 2 ; Number of sides/heads
HiddenSectors dd 0 ; Number of hidden sectors
LargeSectors dd 0 ; Number of LBA sectors
DriveNo dw 0 ; Drive No: 0
Signature db 41 ; Drive signature: 41 for floppy
VolumeID dd 00000000h ; Volume ID: any number
VolumeLabel db "16DOS "; Volume Label: any 11 chars
FileSystem db "FAT12 " ; File system type: don't change!
; ------------------------------------------------------------------
; Main bootloader code
bootloader_start:
xor ax, ax ; make it zero
mov ds, ax ; DS=0
mov ss, ax ; stack starts at seg 0
mov sp, 0x9c00 ; 2000h past code start,
; making the stack 7.5k in size
;***********HERE I TRY TO SWITCH INTO "UNREAL" MODE***********;
cli ; no interrupts
push ds ; save real mode
lgdt [gdtinfo] ; load gdt register
mov eax, cr0 ; switch to pmode by
or al,1 ; set pmode bit
mov cr0, eax
jmp $+2 ; tell 386/486 to not crash
mov bx, 0x08 ; select descriptor 1
mov ds, bx ; 8h = 1000b
and al,0xFE ; back to realmode
mov cr0, eax ; by toggling bit again
pop ds ; get back old segment
sti
;***********END***********;
mov ax, 07C0h ; Set up 4K of stack space above buffer
add ax, 544 ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
cli ; Disable interrupts while changing stack
mov ss, ax
mov sp, 4096
sti ; Restore interrupts
mov ax, 07C0h ; Set data segment to where we're loaded
mov ds, ax
; NOTE: A few early BIOSes are reported to improperly set DL
cmp dl, 0
je no_change
mov [bootdev], dl ; Save boot device number
mov ah, 8 ; Get drive parameters
int 13h
jc fatal_disk_error
and cx, 3Fh ; Maximum sector number
mov [SectorsPerTrack], cx ; Sector numbers start at 1
movzx dx, dh ; Maximum head number
add dx, 1 ; Head numbers start at 0 - add 1 for total
mov [Sides], dx
no_change:
mov eax, 0 ; Needed for some older BIOSes
; First, we need to load the root directory from the disk. Technical details:
; Start of root = ReservedForBoot + NumberOfFats * SectorsPerFat = logical 19
; Number of root = RootDirEntries * 32 bytes/entry / 512 bytes/sector = 14
; Start of user data = (start of root) + (number of root) = logical 33
floppy_ok: ; Ready to read first block of data
mov ax, 19 ; Root dir starts at logical sector 19
call l2hts
mov si, buffer ; Set ES:BX to point to our buffer (see end of code)
mov bx, ds
mov es, bx
mov bx, si
mov ah, 2 ; Params for int 13h: read floppy sectors
mov al, 14 ; And read 14 of them
pusha ; Prepare to enter loop
read_root_dir:
popa ; In case registers are altered by int 13h
pusha
stc ; A few BIOSes do not set properly on error
int 13h ; Read sectors using BIOS
jnc search_dir ; If read went OK, skip ahead
call reset_floppy ; Otherwise, reset floppy controller and try again
jnc read_root_dir ; Floppy reset OK?
search_dir:
popa
mov ax, ds ; Root dir is now in [buffer]
mov es, ax ; Set DI to this info
mov di, buffer
mov cx, word [RootDirEntries] ; Search all (224) entries
mov ax, 0 ; Searching at offset 0
next_root_entry:
xchg cx, dx ; We use CX in the inner loop...
mov si, kern_filename ; Start searching for kernel filename
mov cx, 11
rep cmpsb
je found_file_to_load ; Pointer DI will be at offset 11
add ax, 32 ; Bump searched entries by 1 (32 bytes per entry)
mov di, buffer ; Point to next entry
add di, ax
xchg dx, cx ; Get the original CX back
loop next_root_entry
mov si, file_not_found ; If kernel is not found, bail out
call print_string
found_file_to_load: ; Fetch cluster and load FAT into RAM
mov ax, word [es:di+0Fh] ; Offset 11 + 15 = 26, contains 1st cluster
mov word [cluster], ax
mov ax, 1 ; Sector 1 = first sector of first FAT
call l2hts
mov di, buffer ; ES:BX points to our buffer
mov bx, di
mov ah, 2 ; int 13h params: read (FAT) sectors
mov al, 9 ; All 9 sectors of 1st FAT
pusha ; Prepare to enter loop
read_fat:
popa ; In case registers are altered by int 13h
pusha
stc
int 13h ; Read sectors using the BIOS
jnc read_fat_ok ; If read went OK, skip ahead
call reset_floppy ; Otherwise, reset floppy controller and try again
jnc read_fat ; Floppy reset OK?
; ******************************************************************
fatal_disk_error:
; ******************************************************************
mov si, disk_error
read_fat_ok:
popa
mov ax, 2000h ; Segment where we'll load the kernel
mov es, ax
mov bx, 0
mov ah, 2 ; int 13h floppy read params
mov al, 1
push ax ; Save in case we (or int calls) lose it
; Now we must load the FAT from the disk. Here's how we find out where it starts:
; FAT cluster 0 = media descriptor = 0F0h
; FAT cluster 1 = filler cluster = 0FFh
; Cluster start = ((cluster number) - 2) * SectorsPerCluster + (start of user)
; = (cluster number) + 31
load_file_sector:
mov ax, word [cluster] ; Convert sector to logical
add ax, 31
call l2hts ; Make appropriate params for int 13h
mov ax, 2000h ; Set buffer past what we've already read
mov es, ax
mov bx, word [pointer]
pop ax ; Save in case we (or int calls) lose it
push ax
stc
int 13h
jnc calculate_next_cluster ; If there's no error...
call reset_floppy ; Otherwise, reset floppy and retry
jmp load_file_sector
; In the FAT, cluster values are stored in 12 bits, so we have to
; do a bit of maths to work out whether we're dealing with a byte
; and 4 bits of the next byte -- or the last 4 bits of one byte
; and then the subsequent byte!
calculate_next_cluster:
mov ax, [cluster]
mov dx, 0
mov bx, 3
mul bx
mov bx, 2
div bx ; DX = [cluster] mod 2
mov si, buffer
add si, ax ; AX = word in FAT for the 12 bit entry
mov ax, word [ds:si]
or dx, dx ; If DX = 0 [cluster] is even; if DX = 1 then it's odd
jz even ; If [cluster] is even, drop last 4 bits of word
; with next cluster; if odd, drop first 4 bits
odd:
shr ax, 4 ; Shift out first 4 bits (they belong to another entry)
jmp short next_cluster_cont
even:
and ax, 0FFFh ; Mask out final 4 bits
next_cluster_cont:
mov word [cluster], ax ; Store cluster
cmp ax, 0FF8h ; FF8h = end of file marker in FAT12
jae end
add word [pointer], 512 ; Increase buffer pointer 1 sector length
jmp load_file_sector
end: ; We've got the file to load!
pop ax ; Clean up the stack (AX was pushed earlier)
mov dl, byte [bootdev] ; Provide kernel with boot device info
jmp 2000h:0000h ; Jump to entry point of loaded kernel!
; ------------------------------------------------------------------
; BOOTLOADER SUBROUTINES
print_string: ; Output string in SI to screen
pusha
mov ah, 0Eh ; int 10h teletype function
.repeat:
lodsb ; Get char from string
cmp al, 0
je .done ; If char is zero, end of string
int 10h ; Otherwise, print it
jmp short .repeat
.done:
popa
ret
reset_floppy: ; IN: [bootdev] = boot device; OUT: carry set on error
push ax
push dx
mov ax, 0
mov dl, byte [bootdev]
stc
int 13h
pop dx
pop ax
ret
l2hts: ; Calculate head, track and sector settings for int 13h
; IN: logical sector in AX, OUT: correct registers for int 13h
push bx
push ax
mov bx, ax ; Save logical sector
mov dx, 0 ; First the sector
div word [SectorsPerTrack]
add dl, 01h ; Physical sectors start at 1
mov cl, dl ; Sectors belong in CL for int 13h
mov ax, bx
mov dx, 0 ; Now calculate the head
div word [SectorsPerTrack]
mov dx, 0
div word [Sides]
mov dh, dl ; Head/side
mov ch, al ; Track
pop ax
pop bx
mov dl, byte [bootdev] ; Set correct device
ret
; ------------------------------------------------------------------
; STRINGS AND VARIABLES
kern_filename db "KERNEL SYS" ; MikeOS kernel filename
disk_error db "Error.", 0
file_not_found db "Error.", 0
bootdev db 0 ; Boot device number
cluster dw 0 ; Cluster of the file we want to load
pointer dw 0 ; Pointer into Buffer, for loading kernel
gdtinfo:
dw gdt_end - gdt - 1 ;last byte in table
dd gdt ;start of table
gdt dd 0,0 ; entry 0 is always unused
flatdesc db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
gdt_end:
; ------------------------------------------------------------------
; END OF BOOT SECTOR AND BUFFER START
times 510-($-$$) db 0 ; Pad remainder of boot sector with zeros
dw 0AA55h ; Boot signature (DO NOT CHANGE!)
buffer: ; Disk buffer begins (8k after this, stack starts)
; ==================================================================
So how to fix this code? If switching into "unreal" mode in my case is impossible, how can i access whole memory (4GiB would last), in real mode? I have A20 turned on in kernel code.
After a few years: Found out that SmallerC has support for going to unreal mode, so all the assembly wasn't actually needed and I could just write it in C.
MikeOS comes with a bootloader that assumes a segment of 0x07c0 and offset of 0x0000 (0x07c0:0x0000). The offset portion is also the origin point (ORG
value in NASM). In 20-bit segment:offset addressing: a segment of 0x07c0 and offset of 0x0000 is physical address 0x07c00 (0x07c0<<4+0x0000=0x07c00) which is where the bootloader is expected to be in memory.
It appears that when you used MikeOS you spliced in some unreal mode code from OSDev Wiki that assumes the origin point is based on the segment:offset address 0x0000:0x7c00. This too represents physical address 0x07c00 (0x0000<<4+0x7c00=0x7c00). In this case you would need an ORG
of 0x7c00 in the NASM code.
When assembling with NASM using the -f bin
option (which is the default if an output format isn't specified): if you don't specify an ORG
directive the default is ORG 0x0000
.
You will need to use one or the other, not both. Since most of the MikeOS bootloader code relies on a segment of 0x07c0 and offset of 0x0000 it is easier to change the code to be similar to what MikeOS bootloader used originally. The following code
bootloader_start:
xor ax, ax ; make it zero
mov ds, ax ; DS=0
mov ss, ax ; stack starts at seg 0
mov sp, 0x9c00 ; 2000h past code start,
; making the stack 7.5k in size
;***********HERE I TRY TO SWITCH INTO "UNREAL" MODE***********;
cli ; no interrupts
Can be changed to:
bootloader_start:
; Modify all segment setup code to assume an ORG of 0x0000
mov ax, 07C0h ; Set data segment to where we're loaded
mov ds, ax
add ax, 544 ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
cli ; Disable interrupts while changing stack and entering
; protected mode, turn them on after when in unreal mode
mov ss, ax
mov sp, 4096
You can then remove all this duplicated code that appears after you finish setting up Unreal mode. These lines need to be eliminated:
mov ax, 07C0h ; Set up 4K of stack space above buffer
add ax, 544 ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
cli ; Disable interrupts while changing stack
mov ss, ax
mov sp, 4096
sti ; Restore interrupts
mov ax, 07C0h ; Set data segment to where we're loaded
mov ds, ax
Usually Unreal mode sets all the data registers to a flat memory model. Rather than just updating DS to point to a flat 4gb selector you can set DS/ES/FS/GS registers as well. Modify the code to do:
mov bx, 0x08 ; select descriptor 1
mov ds, bx ; 8h = 1000b
mov es, bx ; 8h = 1000b
mov fs, bx ; 8h = 1000b
mov gs, bx ; 8h = 1000b
Once this is done, one change to the gdtinfo
structure needs to be made. You placed this into the bootloader:
gdtinfo:
dw gdt_end - gdt - 1 ;last byte in table
dd gdt ;start of table
gdt dd 0,0 ; entry 0 is always unused
flatdesc db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
gdt_end:
The problem now is that we are using a segment of 0x07c0 and the base of the GDT is now relative to an offset of 0x0000 (not 0x7c00). The base address in the gdtinfo
structure which we'll load into the GDT register is a linear address (not a segment:offset address). In real mode a linear address and physical address are the same thing. To make gdt
into a linear address we add 0x7c00 to gdt
. We modify the line:
dd gdt ;start of table
So that it now reads:
dd gdt+0x7c00 ;start of table
Complete Code with Changes
The revised version of your file could be:
BITS 16
jmp short bootloader_start ; Jump past disk description section
nop ; Pad out before disk description
; ------------------------------------------------------------------
; Disk description table, to make it a valid floppy
; Note: some of these values are hard-coded in the source!
; Values are those used by IBM for 1.44 MB, 3.5" diskette
OEMLabel db "16DOSRUN" ; Disk label
BytesPerSector dw 512 ; Bytes per sector
SectorsPerCluster db 1 ; Sectors per cluster
ReservedForBoot dw 1 ; Reserved sectors for boot record
NumberOfFats db 2 ; Number of copies of the FAT
RootDirEntries dw 224 ; Number of entries in root dir
; (224 * 32 = 7168 = 14 sectors to read)
LogicalSectors dw 2880 ; Number of logical sectors
MediumByte db 0F0h ; Medium descriptor byte
SectorsPerFat dw 9 ; Sectors per FAT
SectorsPerTrack dw 18 ; Sectors per track (36/cylinder)
Sides dw 2 ; Number of sides/heads
HiddenSectors dd 0 ; Number of hidden sectors
LargeSectors dd 0 ; Number of LBA sectors
DriveNo dw 0 ; Drive No: 0
Signature db 41 ; Drive signature: 41 for floppy
VolumeID dd 00000000h ; Volume ID: any number
VolumeLabel db "16DOS "; Volume Label: any 11 chars
FileSystem db "FAT12 " ; File system type: don't change!
; ------------------------------------------------------------------
; Main bootloader code
bootloader_start:
; Modify all segment setup code to assume an ORG of 0x0000
mov ax, 07C0h ; Set up 4K of stack space above buffer
mov ds, ax ; Set DS segment to where we're loaded
add ax, 544 ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
cli ; Disable interrupts while changing stack
mov ss, ax
mov sp, 4096
; Enter unreal mode
; Keep interrupts off while we switch to real mode
push ds ; Switch to real mode detroys DS. We need to save it
lgdt [gdtinfo] ; load gdt register
mov eax, cr0 ; switch to pmode by
or al,1 ; set pmode bit
mov cr0, eax
jmp $+2 ; Clear the instruction pre-fetch queue
; Set DS=ES=FS=GS to descriptor with 4gb limit
mov bx, 0x08 ; select descriptor 1
mov ds, bx ; 8h = 1000b
mov es, bx ; 8h = 1000b
mov fs, bx ; 8h = 1000b
mov gs, bx ; 8h = 1000b
and al,0xFE ; back to realmode
mov cr0, eax ; by toggling bit again
sti ; enable interrupts
pop ds ; Retsore DS to original value
;***********END OF UNREAL MODE SWITCH ***********;
; NOTE: A few early BIOSes are reported to improperly set DL
cmp dl, 0
je no_change
mov [bootdev], dl ; Save boot device number
mov ah, 8 ; Get drive parameters
int 13h
jc fatal_disk_error
and cx, 3Fh ; Maximum sector number
mov [SectorsPerTrack], cx ; Sector numbers start at 1
movzx dx, dh ; Maximum head number
add dx, 1 ; Head numbers start at 0 - add 1 for total
mov [Sides], dx
no_change:
mov eax, 0 ; Needed for some older BIOSes
; First, we need to load the root directory from the disk. Technical details:
; Start of root = ReservedForBoot + NumberOfFats * SectorsPerFat = logical 19
; Number of root = RootDirEntries * 32 bytes/entry / 512 bytes/sector = 14
; Start of user data = (start of root) + (number of root) = logical 33
floppy_ok: ; Ready to read first block of data
mov ax, 19 ; Root dir starts at logical sector 19
call l2hts
mov si, buffer ; Set ES:BX to point to our buffer (see end of code)
mov bx, ds
mov es, bx
mov bx, si
mov ah, 2 ; Params for int 13h: read floppy sectors
mov al, 14 ; And read 14 of them
pusha ; Prepare to enter loop
read_root_dir:
popa ; In case registers are altered by int 13h
pusha
stc ; A few BIOSes do not set properly on error
int 13h ; Read sectors using BIOS
jnc search_dir ; If read went OK, skip ahead
call reset_floppy ; Otherwise, reset floppy controller and try again
jnc read_root_dir ; Floppy reset OK?
search_dir:
popa
mov ax, ds ; Root dir is now in [buffer]
mov es, ax ; Set DI to this info
mov di, buffer
mov cx, word [RootDirEntries] ; Search all (224) entries
mov ax, 0 ; Searching at offset 0
next_root_entry:
xchg cx, dx ; We use CX in the inner loop...
mov si, kern_filename ; Start searching for kernel filename
mov cx, 11
rep cmpsb
je found_file_to_load ; Pointer DI will be at offset 11
add ax, 32 ; Bump searched entries by 1 (32 bytes per entry)
mov di, buffer ; Point to next entry
add di, ax
xchg dx, cx ; Get the original CX back
loop next_root_entry
mov si, file_not_found ; If kernel is not found, bail out
call print_string
found_file_to_load: ; Fetch cluster and load FAT into RAM
mov ax, word [es:di+0Fh] ; Offset 11 + 15 = 26, contains 1st cluster
mov word [cluster], ax
mov ax, 1 ; Sector 1 = first sector of first FAT
call l2hts
mov di, buffer ; ES:BX points to our buffer
mov bx, di
mov ah, 2 ; int 13h params: read (FAT) sectors
mov al, 9 ; All 9 sectors of 1st FAT
pusha ; Prepare to enter loop
read_fat:
popa ; In case registers are altered by int 13h
pusha
stc
int 13h ; Read sectors using the BIOS
jnc read_fat_ok ; If read went OK, skip ahead
call reset_floppy ; Otherwise, reset floppy controller and try again
jnc read_fat ; Floppy reset OK?
; ******************************************************************
fatal_disk_error:
; ******************************************************************
mov si, disk_error
read_fat_ok:
popa
mov ax, 2000h ; Segment where we'll load the kernel
mov es, ax
mov bx, 0
mov ah, 2 ; int 13h floppy read params
mov al, 1
push ax ; Save in case we (or int calls) lose it
; Now we must load the FAT from the disk. Here's how we find out where it starts:
; FAT cluster 0 = media descriptor = 0F0h
; FAT cluster 1 = filler cluster = 0FFh
; Cluster start = ((cluster number) - 2) * SectorsPerCluster + (start of user)
; = (cluster number) + 31
load_file_sector:
mov ax, word [cluster] ; Convert sector to logical
add ax, 31
call l2hts ; Make appropriate params for int 13h
mov ax, 2000h ; Set buffer past what we've already read
mov es, ax
mov bx, word [pointer]
pop ax ; Save in case we (or int calls) lose it
push ax
stc
int 13h
jnc calculate_next_cluster ; If there's no error...
call reset_floppy ; Otherwise, reset floppy and retry
jmp load_file_sector
; In the FAT, cluster values are stored in 12 bits, so we have to
; do a bit of maths to work out whether we're dealing with a byte
; and 4 bits of the next byte -- or the last 4 bits of one byte
; and then the subsequent byte!
calculate_next_cluster:
mov ax, [cluster]
mov dx, 0
mov bx, 3
mul bx
mov bx, 2
div bx ; DX = [cluster] mod 2
mov si, buffer
add si, ax ; AX = word in FAT for the 12 bit entry
mov ax, word [ds:si]
or dx, dx ; If DX = 0 [cluster] is even; if DX = 1 then it's odd
jz even ; If [cluster] is even, drop last 4 bits of word
; with next cluster; if odd, drop first 4 bits
odd:
shr ax, 4 ; Shift out first 4 bits (they belong to another entry)
jmp short next_cluster_cont
even:
and ax, 0FFFh ; Mask out final 4 bits
next_cluster_cont:
mov word [cluster], ax ; Store cluster
cmp ax, 0FF8h ; FF8h = end of file marker in FAT12
jae end
add word [pointer], 512 ; Increase buffer pointer 1 sector length
jmp load_file_sector
end: ; We've got the file to load!
pop ax ; Clean up the stack (AX was pushed earlier)
mov dl, byte [bootdev] ; Provide kernel with boot device info
jmp 2000h:0000h ; Jump to entry point of loaded kernel!
; ------------------------------------------------------------------
; BOOTLOADER SUBROUTINES
print_string: ; Output string in SI to screen
pusha
mov ah, 0Eh ; int 10h teletype function
.repeat:
lodsb ; Get char from string
cmp al, 0
je .done ; If char is zero, end of string
int 10h ; Otherwise, print it
jmp short .repeat
.done:
popa
ret
reset_floppy: ; IN: [bootdev] = boot device; OUT: carry set on error
push ax
push dx
mov ax, 0
mov dl, byte [bootdev]
stc
int 13h
pop dx
pop ax
ret
l2hts: ; Calculate head, track and sector settings for int 13h
; IN: logical sector in AX, OUT: correct registers for int 13h
push bx
push ax
mov bx, ax ; Save logical sector
mov dx, 0 ; First the sector
div word [SectorsPerTrack]
add dl, 01h ; Physical sectors start at 1
mov cl, dl ; Sectors belong in CL for int 13h
mov ax, bx
mov dx, 0 ; Now calculate the head
div word [SectorsPerTrack]
mov dx, 0
div word [Sides]
mov dh, dl ; Head/side
mov ch, al ; Track
pop ax
pop bx
mov dl, byte [bootdev] ; Set correct device
ret
; ------------------------------------------------------------------
; STRINGS AND VARIABLES
kern_filename db "KERNEL SYS" ; MikeOS kernel filename
disk_error db "Error.", 0
file_not_found db "Error.", 0
bootdev db 0 ; Boot device number
cluster dw 0 ; Cluster of the file we want to load
pointer dw 0 ; Pointer into Buffer, for loading kernel
gdtinfo:
dw gdt_end - gdt - 1 ;last byte in table
dd gdt+0x7c00 ;start of table
gdt dd 0,0 ; entry 0 is always unused
flatdesc db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
gdt_end:
; ------------------------------------------------------------------
; END OF BOOT SECTOR AND BUFFER START
times 510-($-$$) db 0 ; Pad remainder of boot sector with zeros
dw 0AA55h ; Boot signature (DO NOT CHANGE!)
buffer: ; Disk buffer begins (8k after this, stack starts)
; ==================================================================
The Bounty
In the bounty you say this:
I'd want bounty winner to explain me why such code is wrong and help me to fix it (to make it work, by switching to unreal mode aka flat real mode and load kernel file named KERNEL.SYS and execute it. Kernel WILL use interrupts so protected mode aint option.)
Protected mode supports software and hardware interrupts. I believe what you mean to say is that your kernel will use BIOS interrupts. BIOS interrupts aren't available while running in protected mode unless you create a VM86 task or switch back to real mode.
Real/Unreal mode kernels can be made but they will have limitations with lack of virtual memory, memory protection and paging mechanisms that are available in protected mode.
Getting into unreal mode requires temporarily entering protected mode; setting DS/ES/GS/FS with a selector that points to a 16-bit data descriptor with a 4gb limit (rather than 64k); then turns off protected mode. During the time the switch is made to protected mode, interrupts have to be disabled because there is no protected mode interrupt vector set up.
KERNEL.SYS
The MikeOS bootloader requires a file called KERNEL.SYS be placed into the root directory of a disk image formatted as FAT12. I will assume you know how to do this. The method to do this is different between Windows and Linux and outside the scope of this answer. A sample kernel.asm
that tests whether unreal mode is enabled and working is as follows:
bits 16
; MikeOS bootloader loads our code at 0x2000:0x0000 so we need org of 0x0000
; for the kernel code to work properly.
org 0x0000
kernel_start:
; Set DS, ES, FS, GS to 0x0000. In Unreal mode these segment registers
; are not limited to 64kb. We can address full 4gb of memory
xor ax, ax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; This code will not work in normal real mode. We emit instructions
; that use 0xb8000 as an offset. This offset is >= 65536 and normally
; isn't addressable directly in real mode. This should display MDP white
; on purple to upper left of screen. 0xb8000 is pointer to first cell
; of the text mode video display
;
; In Real AND Unreal mode on a 386 you are allowed to use 32-bit registers
; and memory operands. Real mode is limited to an offset that computes
; to a value below 64kb (65536) unlike unreal mode with a 4gb limit
mov edi, 0xb8000
mov word [edi], 0x57<<8 | 'M';
mov word [edi+2], 0x57<<8 | 'D';
mov word [edi+4], 0x57<<8 | 'P';
cli
.endloop:
hlt
jmp .endloop
It can be assembled to KERNEL.SYS
with:
nasm -f bin kernel.asm -o KERNEL.SYS
When a disk image with this bootloader and KERNEL.SYS file is generated and run in QEMU (Bochs will be similar) the output would look something like:
If the processor is not in Unreal mode then the characters written to the upper left will not appear or the hardware/emulator may reach some other type of undefined state.
Other Observations and Information
- It isn't actually required to place the switch into Unreal mode within the MikeOS bootloader. You could leave the MikeOS bootloader as it is and move the switch to Unreal mode into KERNEL.SYS .
- If you intend to access data in memory above 1MiB in any odd numbered mebibyte memory region (0x100000-0x1fffff, 0x300000-0x3fffff, ...) then you will also need to make sure the A20 gate is enabled.
- The Unreal mode code you used in your bootloader sets up data segments with a 4gb limit. It doesn't setup CS in the same way. This version of Unreal mode is called Big Unreal Mode.
- Handling or calling interrupts (ie. BIOS interrupts) in Big Unreal mode is the same as you would in real mode
- If you wish to create an interrupt handler its placement is restricted to low memory area between 0x0000:0x0000 and 0xFFFF:0xFFFF. All code in the Code Segment has the same limitations as Real mode.
- The comment by @RossRidge that says This means you can't have interrupts enabled or use BIOS calls while in unreal mode since they can change the value in DS is incorrect for Big Unreal mode.
- Using a 4gb limit on the Code Segment is possible however code above 64kb can't reliably be used because interrupts only save CS:IP and not CS:EIP. EIP can be any value between 0 and 4gb but anything outside the first 64kb can't reliably be done unless you disable interrupts while running such code. This is pretty restrictive and why this mode is rarely used. This mode is often referred to as Huge Unreal mode.
- @AlexeyFrunze has a number of bootloaders that support loading of kernels and supports Unreal mode. Alex also developed the Smaller C Compiler which can be used to produce code that can be launched from his bootloaders and supports the generation of code for Unreal mode.