Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing to "Unreal" mode, processor crash

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.

like image 326
Kamila Szewczyk Avatar asked Oct 24 '16 16:10

Kamila Szewczyk


Video Answer


1 Answers

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:

enter image description here

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.
like image 165
Michael Petch Avatar answered Oct 14 '22 19:10

Michael Petch