簡體   English   中英

用於在第二階段引導實模式代碼的傳統 BIOS 引導加載程序

[英]Legacy BIOS bootloader to bootstrap real-mode code in second stage

我正在編寫自己的操作系統。 到目前為止,我的代碼超過了 512 字節,這對於一個簡單的引導扇區來說太大了。

我知道我現在必須編寫一個引導加載程序來讀取可能大於或不大於單個 512 字節扇區的任意代碼。

引導加載程序需要:

  • 用作磁盤簽名為 0xaa55 的引導記錄。
  • 從內存地址 0x7E00 開始的任意長度的 LBA 1(LBA 0 是引導扇區)開始讀取第二階段(測試代碼)。
  • 使用 FAR JMP 將控制權轉移到 0x0000:0x7E00。
  • 可用作 1.44 MiB 軟盤映像,用於 QEMU、BOCHS、VirtualBox 等模擬器。
  • 可以在 USB 記憶棒上傳輸和使用,以在 BIOS 設置為使用軟盤驅動器 (FDD) 仿真啟動 USB 的真實硬件上進行測試。 注意某些引導加載程序放置在 USB 驅動器上時無法正常工作。
  • 將引導驅動器傳遞到 DL 中的第二階段。
  • 將所有段寄存器清零並將 SS:SP 設置為 0x0000:0x7C00(從引導加載程序正下方增長)。

這也可以作為詢問涉及操作系統開發的 Stack Overflow 問題的良好起點。 程序員經常努力創建一個最小的、完整的和可驗證的示例 一個通用的樣板/模板將允許其他 Stack Overflow 用戶希望幫助測試代碼,但不會大驚小怪。

我將如何構建這樣一個可重用的引導加載程序?

我已經編寫了這樣的代碼作為其他答案的一部分,但從來沒有機會展示一個可以從其他 Stackoverflow 問題中引用的簡單測試工具。 你所要求的是相當微不足道的。 可以通過在 NASM 中編寫引導加載程序來實現這一點,其中包含您要測試的匯編代碼的二進制映像。 將從 LBA 1(引導加載程序之后的第一個扇區)開始的磁盤使用 BIOS 函數Int 13/ah=2讀取該映像。 然后控制將通過 FAR JMP 轉移到 0x0000:0x7e00。

引導加載程序代碼如下所示:

bpb.inc :

global bpb_disk_info

    jmp short boot_continue
    nop

bpb_disk_info:

    ; Dos 4.0 EBPB 1.44MB floppy
    OEMname:           db    "mkfs.fat"  ; mkfs.fat is what OEMname mkdosfs uses
    bytesPerSector:    dw    512
    sectPerCluster:    db    1
    reservedSectors:   dw    1
    numFAT:            db    2
    numRootDirEntries: dw    224
    numSectors:        dw    2880
    mediaType:         db    0xf0
    numFATsectors:     dw    9
    sectorsPerTrack:   dw    18
    numHeads:          dw    2
    numHiddenSectors:  dd    0
    numSectorsHuge:    dd    0
    driveNum:          db    0
    reserved:          db    0
    signature:         db    0x29
    volumeID:          dd    0x2d7e5a1a
    volumeLabel:       db    "NO NAME    "
    fileSysType:       db    "FAT12   "

啟動.asm :

STAGE2_ABS_ADDR  equ 0x07e00
STAGE2_RUN_SEG   equ 0x0000
STAGE2_RUN_OFS   equ STAGE2_ABS_ADDR
                                ; Run stage2 with segment of 0x0000 and offset of 0x7e00

STAGE2_LOAD_SEG  equ STAGE2_ABS_ADDR>>4
                                ; Segment to start reading Stage2 into
                                ;     right after bootloader

STAGE2_LBA_START equ 1          ; Logical Block Address(LBA) Stage2 starts on
                                ;     LBA 1 = sector after boot sector
STAGE2_LBA_END   equ STAGE2_LBA_START + NUM_STAGE2_SECTORS
                                ; Logical Block Address(LBA) Stage2 ends at
DISK_RETRIES     equ 3          ; Number of times to retry on disk error

bits 16
ORG 0x7c00

; Include a BPB (1.44MB floppy with FAT12) to be more compatible with USB floppy media
%ifdef WITH_BPB
%include "bpb.inc"
%endif

boot_continue:
    xor ax, ax                  ; DS=SS=0 for stage2 loading
    mov ds, ax
    mov ss, ax                  ; Stack at 0x0000:0x7c00
    mov sp, 0x7c00
    cld                         ; Set string instructions to use forward movement

    ; Read Stage2 1 sector at a time until stage2 is completely loaded
load_stage2:
    mov [bootDevice], dl        ; Save boot drive
    mov di, STAGE2_LOAD_SEG     ; DI = Current segment to read into
    mov si, STAGE2_LBA_START    ; SI = LBA that stage2 starts at
    jmp .chk_for_last_lba       ; Check to see if we are last sector in stage2

.read_sector_loop:
    mov bp, DISK_RETRIES        ; Set disk retry count

    call lba_to_chs             ; Convert current LBA to CHS
    mov es, di                  ; Set ES to current segment number to read into
    xor bx, bx                  ; Offset zero in segment

.retry:
    mov ax, 0x0201              ; Call function 0x02 of int 13h (read sectors)
                                ;     AL = 1 = Sectors to read
    int 0x13                    ; BIOS Disk interrupt call
    jc .disk_error              ; If CF set then disk error

.success:
    add di, 512>>4              ; Advance to next 512 byte segment (0x20*16=512)
    inc si                      ; Next LBA

.chk_for_last_lba:
    cmp si, STAGE2_LBA_END      ; Have we reached the last stage2 sector?
    jl .read_sector_loop        ;     If we haven't then read next sector

.stage2_loaded:
    mov ax, STAGE2_RUN_SEG      ; Set up the segments appropriate for Stage2 to run
    mov ds, ax
    mov es, ax

    ; FAR JMP to the Stage2 entry point at physical address 0x07e00
    xor ax, ax                  ; ES=FS=GS=0 (DS zeroed earlier)
    mov es, ax

    ; SS:SP is already at 0x0000:0x7c00, keep it that way
    ; DL still contains the boot drive number
    ; Far jump to second stage at 0x0000:0x7e00
    jmp STAGE2_RUN_SEG:STAGE2_RUN_OFS

.disk_error:
    xor ah, ah                  ; Int13h/AH=0 is drive reset
    int 0x13
    dec bp                      ; Decrease retry count
    jge .retry                  ; If retry count not exceeded then try again

error_end:
    ; Unrecoverable error; print drive error; enter infinite loop
    mov si, diskErrorMsg        ; Display disk error message
    call print_string
    cli
.error_loop:
    hlt
    jmp .error_loop

; Function: print_string
;           Display a string to the console on display page 0
;
; Inputs:   SI = Offset of address to print
; Clobbers: AX, BX, SI

print_string:
    mov ah, 0x0e                ; BIOS tty Print
    xor bx, bx                  ; Set display page to 0 (BL)
    jmp .getch
.repeat:
    int 0x10                    ; print character
.getch:
    lodsb                       ; Get character from string
    test al,al                  ; Have we reached end of string?
    jnz .repeat                 ;     if not process next character
.end:
    ret

;    Function: lba_to_chs
; Description: Translate Logical block address to CHS (Cylinder, Head, Sector).
;
;   Resources: http://www.ctyme.com/intr/rb-0607.htm
;              https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
;              https://stackoverflow.com/q/45434899/3857942
;              Sector    = (LBA mod SPT) + 1
;              Head      = (LBA / SPT) mod HEADS
;              Cylinder  = (LBA / SPT) / HEADS
;
;      Inputs: SI = LBA
;     Outputs: DL = Boot Drive Number
;              DH = Head
;              CH = Cylinder (lower 8 bits of 10-bit cylinder)
;              CL = Sector/Cylinder
;                   Upper 2 bits of 10-bit Cylinders in upper 2 bits of CL
;                   Sector in lower 6 bits of CL
;
;       Notes: Output registers match expectation of Int 13h/AH=2 inputs
;
lba_to_chs:
    push ax                    ; Preserve AX
    mov ax, si                 ; Copy LBA to AX
    xor dx, dx                 ; Upper 16-bit of 32-bit value set to 0 for DIV
    div word [sectorsPerTrack] ; 32-bit by 16-bit DIV : LBA / SPT
    mov cl, dl                 ; CL = S = LBA mod SPT
    inc cl                     ; CL = S = (LBA mod SPT) + 1
    xor dx, dx                 ; Upper 16-bit of 32-bit value set to 0 for DIV
    div word [numHeads]        ; 32-bit by 16-bit DIV : (LBA / SPT) / HEADS
    mov dh, dl                 ; DH = H = (LBA / SPT) mod HEADS
    mov dl, [bootDevice]       ; boot device, not necessary to set but convenient
    mov ch, al                 ; CH = C(lower 8 bits) = (LBA / SPT) / HEADS
    shl ah, 6                  ; Store upper 2 bits of 10-bit Cylinder into
    or  cl, ah                 ;     upper 2 bits of Sector (CL)
    pop ax                     ; Restore scratch registers
    ret

; If not using a BPB (via bpb.inc) provide default Heads and SPT values
%ifndef WITH_BPB
numHeads:        dw 2          ; 1.44MB Floppy has 2 heads & 18 sector per track
sectorsPerTrack: dw 18
%endif

bootDevice:      db 0x00
diskErrorMsg:    db "Unrecoverable disk error!", 0

; Pad boot sector to 510 bytes and add 2 byte boot signature for 512 total bytes
TIMES 510-($-$$) db  0
dw 0xaa55

; Beginning of stage2. This is at 0x7E00 and will allow your stage2 to be 32.5KiB
; before running into problems. DL will be set to the drive number originally
; passed to us by the BIOS.

NUM_STAGE2_SECTORS equ (stage2_end-stage2_start+511) / 512
                                ; Number of 512 byte sectors stage2 uses.

stage2_start:
    ; Insert stage2 binary here. It is done this way since we
    ; can determine the size(and number of sectors) to load since
    ;     Size = stage2_end-stage2_start
    incbin "stage2.bin"

; End of stage2. Make sure this label is LAST in this file!
stage2_end:

; Fill out this file to produce a 1.44MB floppy image
TIMES 1024*1440-($-$$) db 0x00

要使用它,您首先需要生成一個名為stage2.bin的二進制文件。 之后stage2.bin已經建好,你可以建立不包含BIOS參數塊(BPB)使用此命令1.44MiB磁盤映像:

nasm -f bin boot.asm -o disk.img

要使用 BPB 構建 1.44MiB 磁盤映像,您可以使用以下命令構建它:

nasm -DWITH_BPB -f bin boot.asm -o disk.img

stage2.bin的代碼必須假設 ORG(原點)在內存中為 0x07e00。


示例用法/示例

生成到名為stage2.bin的文件的代碼示例,該文件可以使用此測試工具加載:

測試代碼.asm :

ORG 0x7e00

start:
    mov si, testCodeStr
    call print_string

    cli
.end_loop:
    hlt
    jmp .end_loop

testCodeStr: db "Test harness loaded and is executing code in stage2!", 0

; Function: print_string
;           Display a string to the console on display page 0
;
; Inputs:   SI = Offset of address to print
; Clobbers: AX, BX, SI

print_string:
    mov ah, 0x0e                ; BIOS tty Print
    xor bx, bx                  ; Set display page to 0 (BL)
    jmp .getch
.repeat:
    int 0x10                    ; print character
.getch:
    lodsb                       ; Get character from string
    test al,al                  ; Have we reached end of string?
    jnz .repeat                 ;     if not process next character
.end:
    ret

注意:頂部有一個ORG 0x7e00 這個很重要。 要將此文件組裝到stage2.bin使用:

nasm -f bin testcode.asm -o stage2.bin

然后使用以下命令創建 1.44MiB 磁盤映像:

nasm -f bin boot.asm -o disk.img

結果應該是一個大小正好為 1.44MiB 的磁盤映像,包含stage2.bin的副本並具有我們的測試工具引導扇區。

文件stage2.bin可以是任何寫入二進制代碼以加載並從 0x0000:0x7e00 開始的文件。 用於在stage2.bin中創建代碼的語言(C、匯編等)無關緊要。 我在這個例子中使用了 NASM。 當使用qemu-system-i386 -fda disk.img在 QEMU 中執行此測試代碼時,它看起來類似於:

在此處輸入圖片說明


特別注意::如果您使用 FDD 仿真從 USB 啟動,則使用-DWITH_BPB來啟用 BPB 非常有用。 一些將 USB 作為軟盤啟動的 BIOS 會假設存在 BPB,並在將控制轉移到物理地址 0x07c00 之前用驅動器幾何覆蓋該區域。

我修改了自己的引導扇區加載程序以添加新協議。 它使它設置 es = ds = ss = 0 並將整個加載文件加載到地址 07E00h,跳轉到 0000h:7E00h。 但是,sp 仍然指向略低於 7C00h。

問題中的要求存在很大差異:此加載程序使用(FAT12 或 FAT16)文件系統加載下一階段。 如果找到,它會從名為 KERNEL7E.BIN 的文件加載。 文件名,就像整個加載協議一樣,可以通過編輯源文件或在 NASM 命令行上傳遞定義來調整。

由於代碼大小的限制,發生錯誤時僅輸出單字符錯誤消息:R 表示磁盤讀取錯誤,M 表示要加載的文件太大(內存不足)。 另一個限制是不使用 RPL(遠程程序加載器)協議,因為它需要更多字節。

為了減輕空間壓力,可以使用-D_CHS=0 -D_QUERY_GEOMETRY=0 (如果通過ROM-BIOS的LBA接口加載)或-D_LBA=0 (如果通過CHS接口加載)來-D_LBA=0加載器。

要構建加載程序,請克隆lmacrosldosboot存儲庫,並將它們放在一起。 對於 FAT12,加載器將從 ldosboot 目錄使用 NASM 以這種方式構建:

$ nasm -I ../lmacros/ boot.asm -l boot7e12.lst -D_MAP=boot7e12.map -o boot7e12.bin -D_COMPAT_KERNEL7E

或者這樣對於 FAT16:

$ nasm -I ../lmacros/ boot.asm -l boot7e16.lst -D_MAP=boot7e16.map -o boot7e16.bin -D_FAT16 -D_COMPAT_KERNEL7E

以下是如何將加載程序安裝到現有的已格式化的 FAT12 或 FAT16 文件系統映像中:

dd if=boot7e12.bin of=floppy.img bs=1 count=11 conv=notrunc
dd if=boot7e12.bin of=floppy.img bs=1 count=$((512 - 0x3e)) seek=$((0x3e)) skip=$((0x3e)) conv=notrunc

NASM 可以創建整個映像,而不是使用現有映像。 我在https://hg.ulukai.org/ecm/bootimg寫了一個這樣的程序,它是這樣構建的:

nasm -I ../lmacros/ -D_BOOTFILE="'../ldosboot/boot12.bin'" -D_MULTIPAYLOADFILE="'../ldebug/bin/ldebug.com','../ldebug/bin/lddebug.com'" bootimg.asm -o bootimg.img

請注意 long def 如何在單引號列表條目周圍使用雙引號。 每個列表條目都被剝離到基本名稱(在最后一個斜杠或反斜杠之后),將其內容添加到數據區,並將目錄條目添加到根目錄。 文件名是 ASCII 和全大寫。

ldosboot 存儲庫也包含一個兩扇區的 FAT32 加載程序,但我還沒有對其進行修改以支持此協議。 通過重定位,FAT 緩沖區應該已經位於內存的頂部。 這意味着文件可以加載到 07E00h。 但是, ss 將處於高段而不是零。 除了這個區別,協議可以用開關指定。 構建它的命令是nasm -I ../lmacros/ boot32.asm -l boot7e32.lst -D_MAP=boot7e32.map -o boot7e32.bin -D_RELOCATE -D_MEMORY_CONTINUE=0 -D_ZERO_DS -D_ZERO_ES -D_SET_BL_UNIT=0 -D_SET_DL_UNIT=1 -D_LOAD_ADR=07E00h -D_EXEC_SEG_ADJ=-7E0h -D_EXEC_OFS=7E00h -D_OEM_NAME="'KERNEL7E'" -D_LOAD_NAME="'KERNEL7E'" -D_LOAD_EXT="'BIN'"

還有用於 DOS 的 instsect 程序(在它自己的 repo 中),它是用加載器映像構建的,並將它們安裝到 DOS 驅動器。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM