引导加载程序代码有时会使计算机崩溃(三重故障?)

Bootloader code sometimes crashes (triple-faults?) computer

我的自定义引导加载程序中有代码将内存从地址 0x8E00 的 512 字节缓冲区复制到高端内存 0x100000 和更高。这在某些计算机上工作正常,而在其他计算机上崩溃(我假设是三重故障)。此代码在 Bochs x86 模拟器中也能正常工作。

我尝试用 rep movsb 替换自定义段偏移复制循环,将 esiedi 设置为适当的地址,发现这在某些计算机上也会出错。是否有 任何 失败的原因?

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

注意:在 bios_print_string 例程中,错误代码是 而不是 ,因为它在其他地方可以完美运行。

我找到了问题的答案。写入 mov dword [fs:edx], eax 失败,不是因为 fsedx 包含无效段和地址,而是因为在写入地址 0x100000 和更高地址之前未启用 A20 线。相反,它在之后.

被启用

一些 BIOS,例如 Bochs,已经设置了 A20 行,允许代码 运行。其他人没有设置 A20 线,因此写入地址模 0x100000,地址 0x000000 和更高地址。这是 IVT(中断向量 Table)在内存中的存储位置,如果被覆盖,很容易从未处理的中断中创建三重故障并使计算机崩溃或挂起。解决方案?在写入高地址之前设置A20线。