x86 BIOS 第 1 阶段引导代码在中断循环后停止

问题描述 投票:0回答:1

我有一些 x86 BIOS 启动代码(16 位实模式):

org 0x7C00                              ; tell compiler where we are located in memory
bits 16                                 ; tell compiler that we are in 16 bit mode

%define ENDL 0x0D, 0x0A

; FAT12 header
jmp short start                         ; jump past FAT12 header and to actual code
nop                                     ; no operation

BPB_OEM_ID: db 'FADOS0.0'               ; OEM Identifier
BPB_BPS:    dw 512                      ; Bytes per Sector
BPB_SPC:    db 1                        ; Sectors per Cluster
BPB_RS:     dw 1                        ; Reserved Sectors
BPB_FC:     db 2                        ; Number of FATs on the drive
BPB_RDE:    dw 0E0h                     ; Root Dir Entry Count
BPB_TSC:    dw 2880                     ; Total Sector Count: 2880 * 512 = 1.44MB
BPB_MDT:    db 0F0h                     ; Media Descriptor Type: F0 = 3.5" floppy disk
BPB_SPF:    dw 9                        ; Sectors Per FAT
BPB_SPT:    dw 18                       ; Sectors Per Track
BPB_HC:     dw 2                        ; Head Count
BPB_HS:     dd 0                        ; Hidden Sectors
BPB_LSC:    dd 0                        ; Large Sector Count
EBR_DN:     db 0                        ; Drive Number: 0x00 floppy, 0x80 HDD, almost never used
            db 0                        ; Windows NT Flags: not used / reserved
EBR_SIG:    db 29h                      ; Boot Signature: this tells if the EBR data is present
EBR_VID:    db 12h, 34h, 56h, 78h       ; Volume ID: Drive Serial Number, almost never used
EBR_VL:     db 'FADOS0.0dev'            ; Volume Label: Name of the Drive, it can be whatever
EBR_SID:    db 'FAT12   '               ; System Identifier: FAT type

; Code section                          
start:                                  
    ; setup data segments               
    mov ax, 0                           ; can't set ds/es directly
    mov ds, ax                          ; set data segment to 0
    mov es, ax                          ; set extra segment to 0

    ; setup stack               
    mov ss, ax                          ; set stack segment to 0
    mov sp, 0x7C00                      ; stack grows downwards from where it is loaded into memory
    
    ; ensure we are loaded into memory in the right location
    push es
    push word .after
    retf

.after:                                 ; Get disk info and write it to FAT12 header
    mov [EBR_DN], dl                    ; BIOS should set DL to drive number
    
    ; show loading message
    mov si, msg_loading                 ; move loading message into si
    call print                          ; print message

    ; read drive parameters instead of relying on data on formatted disk
    push es                             ; save extra segment pointer to stack
    mov ah, 08h                         ; move 08 hex into ah
    int 13h                             ; call interupt 13 hex
    jc floppy_error                     ; if carry flag is set, jump to floppy read error handler
    pop es                              ; restore extra segment pointer from stack

    and cl, 0x3F                        ; remove top 2 bits
    xor ch, ch                          ; set ch to 0
    mov [BPB_SPT], cx                   ; sector count

    inc dh                              ; add 1 to dh
    mov [BPB_HC], dh                    ; head count

    ; compute LBA of root directory = reserved + fats * sectors_per_fat
    ; note: this section can be hardcoded
    mov ax, [BPB_SPF]                   ; move Sectors Per FAT into ax
    mov bl, [BPB_FC]                    ; move FAT Count into bl
    xor bh, bh                          ; set bh to 0
    mul bx                              ; ax = (fats * sectors_per_fat)
    add ax, [BPB_RS]                    ; ax = LBA of root directory
    push ax                             ; save ax to stack

    ; compute size of root directory = (32 * number_of_entries) / bytes_per_sector
    mov ax, [BPB_RDE]                   ; move Root Dir Entries into ax
    shl ax, 5                           ; ax *= 32
    xor dx, dx                          ; dx = 0
    div word [BPB_BPS]                  ; number of sectors we need to read

    test dx, dx                         ; if dx != 0, add 1
    jz .root_dir_after                  ;
    inc ax                              ; division remainder != 0, add 1
                                        ; this means we have a sector only partially filled with entries
.root_dir_after:

    ; read root directory
    mov cl, al                          ; cl = number of sectors to read = size of root directory
    pop ax                              ; ax = LBA of root directory
    mov dl, [EBR_DN]                    ; dl = drive number (we saved it previously)
    mov bx, buffer                      ; es:bx = buffer
    call disk_read                      ; read from disk

    ; search for stage 2
    xor bx, bx                          ; set bx to 0
    mov di, buffer                      ; move buffer into di

.search_stage2:
    mov si, stage2_bin                  ; move stage2 filename into si
    mov cx, 11                          ; compare up to 11 characters
    push di                             ; save di to stack
    repe cmpsb                          ; repeat while equal; compare ds:si with es:di
    pop di                              ; restore di from stack
    je .found_stage2                    ; jump to .found_stage2 if equal

    add di, 32                          ; add 32 to di
    inc bx                              ; add 1 to bx
    cmp bx, [BPB_RDE]                   ; compare Root Dir Entries to bx
    jl .search_stage2                   ; loop this code block if previous operation returns less than

    ; stage 2 not found
    jmp stage2_not_found_error          ; jump to floppy error handler

.found_stage2:

    ; di should have the address to the entry
    mov ax, [di + 26]                   ; first logical cluster field (offset 26)
    mov [stage2_cluster], ax            ; move ax into stage2 cluster

    ; load FAT from disk into memory
    mov ax, [BPB_RS]                    ; move Reserved Sectors into ax
    mov bx, buffer                      ; move buffer into bx
    mov cl, [BPB_SPF]                   ; move Sectors Per FAT into cl
    mov dl, [EBR_DN]                    ; move Drive Number into dl
    call disk_read                      ; read from disk

    ; read stage 2 and process FAT chain
    mov bx, STAGE2_LOAD_SEGMENT         ; move stage2 segment into bx
    mov es, bx                          ; move bx to extra segment pointer
    mov bx, STAGE2_LOAD_OFFSET          ; move stage2 offset into bx

.load_stage2_loop:
    
    ; Read next cluster
    mov ax, [stage2_cluster]            ; move stage2_cluster into ax
    
    ; not nice :( hardcoded value
    add ax, 31                          ; first cluster = (stage2_cluster - 2) * sectors_per_cluster + start_sector
                                        ; start sector = reserved + fats + root directory size = 1 + 18 + 134 = 33
    mov cl, 1                           ; move 1 into cl
    mov dl, [EBR_DN]                    ; move the drive number into dl
    call disk_read                      ; read from the disk

    add bx, [BPB_BPS]                   ; move Bytes Per Sector into bx

    ; compute location of next cluster
    mov ax, [stage2_cluster]            ; move stage2_cluster into ax
    mov cx, 3                           ; move 3 into cx
    mul cx                              ; multiply cx by ax
    mov cx, 2                           ; move 2 into cx
    div cx                              ; ax = index of entry in FAT, dx = cluster mod 2

    mov si, buffer                      ; move buffer into si
    add si, ax                          ; add ax to si
    mov ax, [ds:si]                     ; read entry from FAT table at index ax

    or dx, dx                           ; check if ax is zero
    jz .even                            ; if ax is zero jump to .even

.odd:
    shr ax, 4                           ; shift ax to the right by 4 bits
    jmp .next_cluster_after             ; jump to .next_cluster_after

.even:
    and ax, 0x0FFF                      ; move 0x0FFF into ax

.next_cluster_after:
    cmp ax, 0x0FF8                      ; subtract ax from 0x0FF8: end of chain
    jae .read_finish                    ; jump is result was above or equal

    mov [stage2_cluster], ax            ; move the cluster stage2 is located in into ax
    jmp .load_stage2_loop               ; jump to the stage 2 loading loop

.read_finish:
    
    ; jump to stage 2
    mov dl, [EBR_DN]                    ; move boot drive number into dl

    mov ax, STAGE2_LOAD_SEGMENT         ; move the stage 2 location into ax: set segment registers
    mov ds, ax                          ; move data segment to the stage 2 location
    mov es, ax                          ; move extra segment to the stage 2 location

    ; jump to the 2nd stage bootloader
    jmp STAGE2_LOAD_SEGMENT:STAGE2_LOAD_OFFSET

    jmp wait_key_and_reboot             ; should never happen, here just incase

    cli                                 ; disable interrupts, this way CPU can't get out of halt state
    hlt                                 ; halt processor

; Error handlers

floppy_error:
    mov si, msg_read_fail               ; move string to print into si
    call print                          ; print error
    jmp wait_key_and_reboot             ; jump to wait_key_and_reboot

stage2_not_found_error:
    mov si, msg_stage2_err              ; move string to print into si
    call print                          ; print error
    jmp wait_key_and_reboot             ; jump to wait_key_and_reboot

wait_key_and_reboot:
    mov ah, 0                           ; move 0 into ah
    int 16h                             ; wait for keypress
    jmp 0FFFFh:0                        ; jump to beginning of BIOS, should reboot

.halt:
    cli                                 ; disable interrupts, this way CPU can't get out of halt state
    hlt                                 ; halt processer 

; print text to screen
; ds:si = pointer to string to print

print:
    push si                             ; save si to stack
    push ax                             ; save ax to stack
    push bx                             ; save bx to stack

.loop:
    lodsb                               ; loads next character in al
    or al, al                           ; verify if next character is null
    jz .done                            ; if next character is null then jump to .done

    mov ah, 0x0E                        ; call bios interrupt
    mov bh, 0                           ; set page number to 0
    int 0x10                            ; call interupt 0x10

    jmp .loop                           ; loop printing until all characters have been printed

.done:
    pop bx                              ; restore bx from stack
    pop ax                              ; restore ax from stack
    pop si                              ; restore si from stack
    ret                                 ; return from function

; convert an lba address to a chs address
; ax = LBA address
; returns:
; cx = [bits 0-5]: sector number
; ch = [bits 6-15]: cylinder
; dh = head

lba_to_chs:

    push ax                             ; save ax to stack
    push dx                             ; save ax to stack

    xor dx, dx                          ; dx = 0
    div word [BPB_SPT]                  ; ax = LBA / SectorsPerTrack
                                        ; dx = LBA % SectorsPerTrack

    inc dx                              ; dx = (LBA % SectorsPerTrack + 1) = sector
    mov cx, dx                          ; cx = sector

    xor dx, dx                          ; dx = 0
    div word [BPB_HC]                   ; ax = (LBA / SectorsPerTrack) / Heads = cylinder
                                        ; dx = (LBA / SectorsPerTrack) % Heads = head
    mov dh, dl                          ; dh = head
    mov ch, al                          ; ch = cylinder (lower 8 bits)
    shl ah, 6
    or cl, ah                           ; put upper 2 bits of cylinder in CL

    pop ax                              ; restore ax from stack
    mov dl, al                          ; restore dl from stack
    pop ax                              ; restore ax from stack
    ret                                 ; return from function

; read from disk
; ax = LBA address
; cl = number of sectors to read (up to 128)
; dl = drive number
; es:bx = memory address where to store read data

disk_read:

    push ax                             ; save ax to stack
    push bx                             ; save bx to stack
    push cx                             ; save cx to stack
    push dx                             ; save dx to stack
    push di                             ; save di to stack

    push cx                             ; temporarily save CL (number of sectors to read)
    call lba_to_chs                     ; compute CHS address
    pop ax                              ; al = number of sectors to read
    
    mov ah, 02h                         ; move 02 in hex into ah
    mov di, 3                           ; retry count = 5

.retry:
    pusha                               ; save all registers, we don't know what bios modifies
    stc                                 ; set carry flag, some BIOS chips don't set it
    int 13h                             ; call interupt 13h: carry flag cleared = success
    jnc .done                           ; jump to .done if carry not set

    ; read failed
    popa                                ; restore all general purpose registers from stack
    call disk_reset                     ; reset disk controller

    dec di                              ; subtract 1 from di
    test di, di                         ; set zero flag if di and di AND to 0
    jnz .retry                          ; if result is not zero then retry

.fail:
    jmp floppy_error                    ; jump to error handler if all attempts failed

.done:
    popa                                ; restore all general purpose registers from stack

    pop di                              ; restore di regitser from stack
    pop dx                              ; restore dx register from stack
    pop cx                              ; restore cx register from stack
    pop bx                              ; restore bx register from stack
    pop ax                              ; restore ax register from stack
    ret                                 ; return from function

; reset disk controller
; ah = drive number

disk_reset:
    pusha                               ; push all values in registers to stack
    mov ah, 0                           ; move 0 into ah
    stc                                 ; set carry flag
    int 13h                             ; call interupt 13 in hex
    jc floppy_error                     ; jump if carry is set
    popa                                ; pop all values from stack
    ret                                 ; return from function

; data section

msg_loading:            db 'LOADING', ENDL, 0
msg_read_fail:          db 'DISK READ FAIL', ENDL, 0
msg_stage2_err:         db 'STAGE2.BIN NOT FOUND', ENDL, 0
stage2_bin:             db 'STAGE2  BIN', ENDL, 0
stage2_cluster:         dw 0
STAGE2_LOAD_SEGMENT     equ 0x2000
STAGE2_LOAD_OFFSET      equ 0

times 510-($-$$) db 0                   ; fill remaning space with 0s
dw 0AA55h                               ; last 2 bytes of boot sector

buffer:                                 ; buffer space

调用 BIOS 中断 0x10 以在屏幕上打印“LOADING”(确实如此)后,它冻结了。 我在 bochs 中运行了它,它陷入了 BIOS 例程中函数的循环中,在该循环运行了不确定的时间后,程序停止了,似乎在 BIOS 中的某个地方。有人可以帮我解决这个问题吗?我自己似乎找不到任何解决这个问题的方法,所以我来到这里。

我试图删除屏幕上的打印,但它仍然在 BIOS 中的某个地方停止,并且没有打印任何内容,因此错误处理程序很可能没有在检测到错误时启动,所以我现在完全迷失了,感谢任何帮助!

assembly nasm x86-16 bootloader bios
1个回答
0
投票

我组装了你的代码并尝试了它,它按预期工作来加载一个小内核。但是,如果您尝试加载大于 64 KiB 的文件,它将溢出

bx
中的近指针。我不知道这是否就是您观察到挂起的原因。

我使用 DOS 命令

instsect B: /s12=20240318.bin
将您的加载程序安装到 1440 KiB 软盘映像中,并启动到我的调试器,然后运行
boot fdb
,您的加载程序就可以工作了。

按如下方式修改您的加载程序允许我使用您的加载程序加载最新版本的我的可启动调试器,lDebug

首先,在

mov sp, 0x7C00
之后添加
mov bp, sp
。 lDebug 的 FreeDOS 风格入口点需要一个带有
ss:bp
指向的 BPB 的引导扇区。

其次,添加到

bx
之后(我们假设
bx
溢出将导致零):

    add bx, [BPB_BPS]                   ; move Bytes Per Sector into bx
    jnz .no_es_adjust
    mov ax, es
    add ah, 1000h >> 8
    mov es, ax
.no_es_adjust:

(为了加载 lDebug,我使用了 instsect 开关

/f3=ldebug.com
。instsect 检测到的前两个名称位于您的错误消息中,直到我开始修复代码以禁止嵌入空白。我没想到会出现错误消息大写,因此它会将消息误检测为文件名。)

© www.soinside.com 2019 - 2024. All rights reserved.