[英]Legacy BIOS bootloader to bootstrap real-mode code in second stage
我正在编写自己的操作系统。 到目前为止,我的代码超过了 512 字节,这对于一个简单的引导扇区来说太大了。
我知道我现在必须编写一个引导加载程序来读取可能大于或不大于单个 512 字节扇区的任意代码。
引导加载程序需要:
这也可以作为询问涉及操作系统开发的 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
加载器。
要构建加载程序,请克隆lmacros和ldosboot存储库,并将它们放在一起。 对于 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.