owacoder
owacoder

Reputation: 4873

Bootloader code sometimes crashes (triple-faults?) computer

I have code in my custom bootloader that copies memory from a 512-byte buffer at address 0x8E00 into high memory, 0x100000 and higher. This works fine on some computers, and crashes (triple-faults, I assume) on others. This code also works fine in the Bochs x86 emulator.

I have tried replacing the custom segment-offset copy loop with a rep movsb, setting esi and edi to the appropriate addresses, and finding this also faults on some computers. Is there any reason why this should fail?

Bootload.asm:

; Portions of this code are under the MikeOS license, as follows:
;
; ==================================================================
; Copyright (C) 2006 - 2014 MikeOS Developers -- http://mikeos.sourceforge.net
;
; All rights reserved.
;
; Redistribution and use in source and binary forms, with or without
; modification, are permitted provided that the following conditions are met:
;
;    * Redistributions of source code must retain the above copyright
;      notice, this list of conditions and the following disclaimer.
;
;    * Redistributions in binary form must reproduce the above copyright
;      notice, this list of conditions and the following disclaimer in the
;      documentation and/or other materials provided with the distribution.
;
;    * Neither the name MikeOS nor the names of any MikeOS contributors
;      may be used to endorse or promote products derived from this software
;      without specific prior written permission.
;
; THIS SOFTWARE IS PROVIDED BY MIKEOS DEVELOPERS AND CONTRIBUTORS "AS IS"
; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
; ARE DISCLAIMED. IN NO EVENT SHALL MIKEOS DEVELOPERS BE LIABLE FOR ANY
; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
; USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
; ==================================================================

BOOTLOADER_SECTORS equ 3                ; includes first sector, loaded by 

BIOS

; a boot sector that enters 32-bit protected mode
        BITS 16
        ORG 0x7c00

        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 "OLIVEOS "   ; Disk label
BytesPerSector          dw 512          ; Bytes per sector
SectorsPerCluster       db 1            ; Sectors per cluster
ReservedForBoot         dw BOOTLOADER_SECTORS ; 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 "OLIVEOS    "; Volume Label: any 11 chars
FileSystem              db "FAT12   "   ; File system type: don't change!

        KERNEL_OFFSET equ 0x100000      ; kernel load offset
        STACK_LOCATION equ 0x7c00       ; stack location
        MEM_MAP_ENTRIES equ 0x5000      ; memory map length offset
        MEM_MAP_OFFSET equ 0x5004       ; memory map offset

bootloader_start:
        ; NOTE: A few early BIOSes are reported to improperly set DL

        cmp dl, 0
        je no_change
        mov [BOOT_DRIVE], dl            ; Save boot device number
        mov ah, 8                       ; Get drive parameters
        int 13h
        jc 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

        cli
        xor ax, ax                      ; make AX zero
        mov ds, ax                      ; so we point our segment registers to zero
        mov es, ax
        mov fs, ax
        mov gs, ax
        mov ss, ax

        jmp 0x0000:bootloader_landing   ; far jump to clear cs to 0

bootloader_landing:
        mov bp, STACK_LOCATION          ; set up stack
        mov sp, bp

        sti

        mov si, MSG_STARTING_BOOTLOADER
        call bios_print_string

        call load_bootloader            ; load the rest of the bootloader (if we don't do it first,
                                        ; something is very likely going to mess up)

        pusha
        mov di, MEM_MAP_OFFSET
        jmp bios_get_memory             ; get memory map for kernel
bios_get_memory_return:
        popa

        mov bp, STACK_LOCATION          ; set up stack again (bios_get_memory trashed our stack)
        mov sp, bp

        jmp second_stage                ; transfer control to second stage!

; loads the rest of this bootloader
load_bootloader:
        mov bx, second_stage            ; read to 0x7e00 (right after this 512 byte code segment)
        mov al, BOOTLOADER_SECTORS-1    ; sectors to read
        mov dl, [BOOT_DRIVE]            ; drive
        mov cl, 0x02                    ; start sector
        mov ch, 0x00                    ; cylinder
        mov dh, 0x00                    ; head
        mov ah, 0x02                    ; BIOS read sector function

        push ax                         ; store AX on stack so later we can recall
                                        ; how many sectors were request to be read,
                                        ; even if it is altered in the meantime

        int 0x13                        ; call BIOS

        jc disk_error                   ; jump if error (carry flag set)

        pop dx                          ; restore from stack (was AX before)
        cmp dl, al                      ; if AL (sectors read) != DH (sectors expected)
        jne disk_error                  ;     display error message

        ret

; displays error message and hangs
disk_error:
        mov si, DISK_ERROR_MSG
        call bios_print_string

        sti
.halt:
        hlt
        jmp .halt

; -----------------------------------------------------------------
; BOOTLOADER SUBROUTINES:
;
bios_print_string:
        pusha

        mov ah, 0x0e                    ; int 10h teletype function

.repeat:
        lodsb                           ; Get char from string

        cmp al, 0
        je .done                        ; If char is zero, end of string

        int 0x10                        ; Otherwise, print it
        jmp .repeat                     ; And move on to next char

.done:
        popa
        ret

; prints 16 bit hex value from AX
bios_print_2hex:
        push cx

        mov cx, 16-4
.repeat:        
        push ax

        shr ax, cl
        and ax, 0xf
        cmp ax, 9
        jle .print

        add ax, 'A'-'9'-1

.print:
        add ax, '0'
        mov ah, 0x0e
        int 0x10

        pop ax

        cmp cx, 0
        je .done

        sub cx, 4
        jmp .repeat;
.done:
        pop cx
        ret

; prints 32 bit hex value from AX
bios_print_4hex:
        push eax

        shr eax, 16
        call bios_print_2hex

        pop eax
        and eax, 0xffff
        call bios_print_2hex

        ret

; global variables
BOOT_DRIVE      db 0
MSG_STARTING_BOOTLOADER db "OliveOS", 0
MSG_STARTING_SECOND_STAGE db " has started!", 0
MSG_READING     db ".", 0
MSG_READING2    db "!", 0
DISK_ERROR_MSG  db "Disk read error!", 0
MSG_REG_DUMP    db 0xD, 0xA, "INTERNAL REG DUMP", 0xD, 0xA, 0
NEWLINE         db 0xD, 0xA, 0

; bootsector padding
times 510-($-$$) db 0
dw 0xaa55

        BITS 16

second_stage:
        mov si, MSG_STARTING_SECOND_STAGE
        call bios_print_string

        ;call bios_enable_a20
        call load_kernel

        jmp switch_to_pm                ; switch to protected mode, we won't return from here

        BITS 32
; this is where we arrive after switching to and initializing protected mode
begin_pm:
        call kbd_enable_a20
        call fast_enable_a20

        call CODE_SEG:KERNEL_OFFSET     ; now call the kernel!

.halt:
        hlt                             ; hang
        jmp .halt

        BITS 16

load_dest:
        dd      KERNEL_OFFSET

; loads the kernel from the floppy image
load_kernel:        
        mov ax, BOOTLOADER_SECTORS      ; start logical sector
        mov cx, 200                     ; number of sectors to read

.continue:
        cmp cx, 0
        je .done

        pusha
        mov ebx, 0x8E00                 ; write to 0x8E00 a temporary 512 byte buffer
        call bios_disk_load             ; load bytes to buffer

        mov si, MSG_READING
        call bios_print_string
        popa

        pusha                           ; copy bytes in buffer to destination
        call switch_to_unreal           ; switch to unreal mode to access high memory

        mov cx, 0x200                   ; copy 512 bytes
        mov ebx, 0x8E00                 ; read from 0x8E00
        mov edx, dword [load_dest]      ; load destination address

.copy:
        cmp cx, 0
        je .done_copying

        mov eax, dword [fs:ebx]
        mov dword [fs:edx], eax         ; commenting out this line (the actual write) will work on any computer

        add ebx, 4
        add edx, 4

        sub cx, 4
        jmp short .copy

.done_copying:
        call switch_to_real             ; switch back to real mode
        popa

        add dword [load_dest], 0x200    ; add 512 bytes to output pointer
        inc ax                          ; increment logical sector
        dec cx                          ; decrement loop counter

        jmp .continue                   ; continue reading
.done:    
        ret

;sets up LBA address in AX for INT 13H
logical_int13_setup:
        push bx
        push ax

        mov bx, ax                      ; Save logical sector

        mov dx, 0                       ; First the sector
        div word [SectorsPerTrack]
        add dl, 0x01                    ; 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 [BOOT_DRIVE]       ; Set correct device

        ret

;bios_disk_load: loads logical sector in AX to ES:BX
bios_disk_load:
        call logical_int13_setup        ; setup our parameters

        mov ah, 0x2                     ; INT 0x13 function
        mov al, 0x1                     ; load 1 sector

        int 0x13

        jc disk_error                   ; jump if error (carry flag set)

        cmp al, 1                       ; if AL (sectors read) != 1 (sectors expected)
        jne disk_error                  ;     display error message

        ret

bios_reg_dump:
        pusha

        mov si, MSG_REG_DUMP
        call bios_print_string

        mov si, .MSG_AX
        call bios_print_string
        call bios_print_4hex
        mov si, NEWLINE
        call bios_print_string

        mov si, .MSG_BX
        call bios_print_string
        mov eax, ebx
        call bios_print_4hex
        mov si, NEWLINE
        call bios_print_string

        mov si, .MSG_CX
        call bios_print_string
        mov eax, ecx
        call bios_print_4hex
        mov si, NEWLINE
        call bios_print_string

        mov si, .MSG_DX
        call bios_print_string
        mov eax, edx
        call bios_print_4hex
        mov si, NEWLINE
        call bios_print_string

        mov si, .MSG_CS
        call bios_print_string
        mov eax, cs
        call bios_print_4hex
        mov si, NEWLINE
        call bios_print_string

        mov si, .MSG_DS
        call bios_print_string
        mov eax, ds
        call bios_print_4hex
        mov si, NEWLINE
        call bios_print_string

        mov si, .MSG_ES
        call bios_print_string
        mov eax, es
        call bios_print_4hex
        mov si, NEWLINE
        call bios_print_string

        mov si, .MSG_FS
        call bios_print_string
        mov eax, fs
        call bios_print_4hex
        mov si, NEWLINE
        call bios_print_string

        mov si, .MSG_GS
        call bios_print_string
        mov eax, gs
        call bios_print_4hex
        mov si, NEWLINE
        call bios_print_string

        popa
        ret

        .MSG_AX db "EAX: 0x", 0
        .MSG_BX db "EBX: 0x", 0
        .MSG_CX db "ECX: 0x", 0
        .MSG_DX db "EDX: 0x", 0
        .MSG_CS db "CS: 0x", 0
        .MSG_DS db "DS: 0x", 0
        .MSG_ES db "ES: 0x", 0
        .MSG_FS db "FS: 0x", 0
        .MSG_GS db "GS: 0x", 0

%include "source/bootload/gdt.asm"
%include "source/bootload/protected_mode.asm"
%include "source/bootload/memory.asm"

times (BOOTLOADER_SECTORS*512)-($-$$) db 0

Note: The faulting code is not in the bios_print_string routine, as that works flawlessly elsewhere.

Upvotes: 4

Views: 225

Answers (1)

owacoder
owacoder

Reputation: 4873

I found the answer to my problem. The write mov dword [fs:edx], eax failed, not because fs or edx contained an invalid segment and address, but because the A20 line was not enabled before writing to addresses 0x100000 and higher. Instead, it was being enabled afterward.

Some BIOSes, like Bochs, already set the A20 line, which allowed the code to run. Others did not have the A20 line set, so the write was to an address modulo 0x100000, to addresses 0x000000 and higher. This is where the IVT (Interrupt Vector Table) is stored in memory, and, if overwritten, can easily create triple-faults from unhandled interrupts and crash or hang the computer. The solution? Set the A20 line before writing to the high addresses.

Upvotes: 5

Related Questions