主要问题是您从磁盘上的错误位置读取。如果您将第二阶段从磁盘的第二个扇区开始放置在引导扇区之后,即圆柱/磁头/扇区 (CHS) = (0,0,2)。引导扇区为 (0,0,1)。扇区编号从 1 开始,而柱面和磁头编号从 0 开始。
其他潜在问题是(其中许多可以在我的通用引导加载程序提示中找到):
您的代码依赖于CS设置为 0000h的事实(因为您使用的是 7c00h 的 ORG)。您设置DS=ES=SS=CS。除了DL中的驱动器号之外,您不应假定任何段寄存器或通用寄存器的状态。如果您需要像DS这样的段寄存器设置为 0000h,则将其设置为零。
在设置DS段之前,将DL中的驱动器号写入内存地址。可以将引导驱动器写入一个段,然后稍后从错误的段中读取。如果您需要将DL保存到内存,请在设置DS段后执行。引用时隐式使用DS(即:它类似于.driveNumber
mov driveNumber, dl
driveNumber
mov [ds:driveNumber], dl
您实际上并未在代码中设置SP。你只更新SS。谁知道SP指向哪里!SS:SP的组合决定了当前堆栈指针。您可以通过将SS:SP设置为 0000h:7c00h 来将堆栈设置为在引导加载程序之下向下增长。这不会干扰在 1000h:0000h 加载 stage2。
您不需要在段寄存器更新周围放置 CLI/STI。必须是原子的一个地方是在更新SS:SP时。如果您写入SS,CPU 将禁用中断,直到执行以下指令。如果您在SS之后立即更新SP ,则可以将其视为原子操作,无需CLI / STI。除了1980 年代初期生产的一些有缺陷的 8088之外,几乎所有处理器都是如此。如果您有机会在这样的系统上启动,请考虑将CLI / STI放在更新SS:SP的代码周围。
js reset
尝试磁盘重置后您有一个。我相信你jc
的意思是用来检查进位标志(CF)而不是标志标志。通常,您通常不必检查重置失败。进行重置,然后重新发出驱动器访问命令(即:磁盘读取)并捕获那里的任何驱动器错误。在真正的硬件上,您通常会在放弃和中止之前重新尝试操作 3 次。
看来您启用.286
了指令集,以便此代码编译:
push 1000h ; pushing the memory address into stack
push 0h ; pushing the offset
retf
您曾经retf
执行 FAR JMP 的等效操作,早期版本的 MASM 不支持 JMP 语法。您的代码是正确的,但您至少需要一个.186
指令,因为 Intel 8088/8086 处理器不支持PUSH imm8
或PUSH imm16
编码。这是在 80186 中添加的。如果您希望您的代码在 8088/8086 上运行,您可以这样做:
; Version that uses a FAR RET to do same as FAR JMP that works on 8086
mov ax, 1000h ; On 8086 push imm16 doesn't exist
push ax ; Push the code segment (1000h) to execute
xor ax, ax ; Zero AX
push ax ; Push the offset (0) of code to execute
retf ; MASM may not understand a FAR JMP, do RETF instead
尽管该解决方案有效,但它的编码相当冗长。您可以使用以下代码手动发出FAR JMP(操作码 0EAh):
; Early versions of MASM don't support FAR JMP syntax like 'jmp 1000h:0000h'
; Manually encode the FAR JMP instruction
db 0eah ; 0EAh is opcode for a FAR JMP
dw 0000h, 1000h ; 0000h = offset, 1000h segment of the FAR JMP
您可以发出0aa55h
引导签名并将引导代码填充到 512 字节,方法是将所有代码和数据放入.code
段中,并用于ORG
填充和放置引导签名。
要解决上述问题,您的代码可能如下所示:
.8086
.model tiny
.code
org 7c00h
main PROC
jmp short start
nop
start:
xor ax, ax
mov ds, ax ; DS=0
cli ; Only need STI/CLI around SS:SP change on buggy 8088
mov ss, ax ; SS:SP = 0000h:7c00h grow down from beneath bootloader
mov sp, 7c00h
sti
mov driveNumber, dl ; Storing booting drive number
jmp read ; Jump to reading (don't need reset first time)
reset:
mov ah, 0h ; Reset the drive before retrying operation
mov dl, driveNumber
int 13h
read:
mov ax, 1000h ; Reading sectors into memory address 0x1000:0
mov es, ax
xor bx, bx
mov ah, 02h
mov al, 01h ; Reading 1 sector
mov ch, 00h ; Form cylinder #0
mov cl, 02h ; Dtarting from sector #2
mov dh, 00h ; Using head #0
mov dl, driveNumber ; On boot drive
int 13h
jc reset
; Early versions of MASM don't support FAR JMP syntax like 'jmp 1000h:0000h'
; Manually encode the FAR JMP instruction
db 0eah ; 0EAh is opcode for a FAR JMP
dw 0000h, 1000h ; 0000h = offset, 1000h segment of the FAR JMP
; Error - end with HLT loop or you could use 'jmp $' as an infinite loop
error:
cli
endloop:
hlt
jmp endloop
main ENDP
; Boot sector data between code and boot signature.
; Don't put in data section as the linker will place that section after boot sig
driveNumber db ?
org 7c00h+510 ; Pad out boot sector up to the boot sig
dw 0aa55h ; Add boot signature
END main
其他观察
Int 13h/AH=2 (read) 和Int 13h/AH=0 (reset) 只会破坏 AX 寄存器 ( AH/AL )。无需设置所有参数即可在磁盘故障后进行另一次读取。
如前所述,重试磁盘操作 3 次在真实硬件上很常见。您可以使用SI作为磁盘操作的重试计数,因为磁盘读取和重置 BIOS 调用不使用SI 。
没有必要从以下开始:
main: jmp short start
nop
start:
除非您插入 BIOS 参数块 (BPB) 用作卷引导记录 (VBR)。在使用软盘驱动器仿真 (FDD) 在 USB 设备上启动时,在真实硬件上使用BPB 是一个好主意。
如果像这样更新一个 16 位寄存器的高 8 位寄存器和低 8 位寄存器:
mov ah,02h
mov al,01h
您可以通过这种方式将它们组合成一条指令:
mov ax, 0201h
实现附加观察中确定的内容,代码可能如下所示:
启动.asm:
DISK_RETRIES EQU 3
.8086
.model tiny
IFDEF WITH_BPB
include bpb.inc
ENDIF
.code
org 7c00h
main PROC
IFDEF WITH_BPB
jmp short start
nop
bpb bpb_s<>
ENDIF
start:
xor ax, ax
mov ds, ax ; DS=0
; cli ; Only need STI/CLI around SS:SP change on buggy 8088
mov ss, ax ; SS:SP = 0000h:7c00h
mov sp, 7c00h
; sti
mov ax, 1000h ; Reading sectors into memory address (ES:BX) 1000h:0000h
mov es, ax ; ES=1000h
xor bx, bx ; BX=0000h
mov cx, 0002 ; From cylinder #0
; Starting from sector #2
mov dh, 00h ; Using head #0
mov si, DISK_RETRIES+1 ; Retry count
jmp read ; Jump to reading (don't need reset first time)
reset:
dec si ; Decrement retry count
jz error ; If zero we reached the retry limit, goto error
mov ah, 0h ; If not, reset the drive before retrying operation
int 13h
read:
mov ax, 0201h ; BIOS disk read function
; Reading 1 sector
int 13h ; BIOS disk read call
; This call only clobbers AX
jc reset ; If error reset drive and try again
; Early versions of MASM don't support FAR JMP syntax like 'jmp 1000h:0000h'
; Manually encode the FAR JMP instruction
db 0eah ; 0EAh is opcode for a FAR JMP
dw 0000h, 1000h ; 0000h = offset, 1000h segment of the FAR JMP
; Error - end with HLT loop or you could use 'jmp $' as an infinite loop
error:
cli
endloop:
hlt
jmp endloop
main ENDP
; Boot sector data between code and boot signature.
; Don't put in data section as the linker will place that section after boot sig
org 7c00h+510 ; Pad out boot sector up to the boot sig
dw 0aa55h ; Add boot signature
END main
bpb.inc:
bpb_s STRUCT
; 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 0f0h
numFATsectors dw 9
sectorsPerTrack dw 18
numHeads dw 2
numHiddenSectors dd 0
numSectorsHuge dd 0
driveNum db 0
reserved db 0
signature db 29h
volumeID dd 2d7e5a1ah
volumeLabel db "NO NAME "
fileSysType db "FAT12 "
bpb_s ENDS
运行时显示字符串的示例stage2.asm :
.8086
.model tiny
.data
msg_str db "Running stage2 code...", 0
.code
org 0000h
main PROC
mov ax, cs
mov ds, ax
mov es, ax
cld
mov si, offset msg_str
call print_string
; End with a HLT loop
cli
endloop:
hlt
jmp endloop
main ENDP
; 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 PROC
mov ah, 0eh ; BIOS tty Print
xor bx, bx ; Set display page to 0 (BL)
jmp getch
chloop:
int 10h ; print character
getch:
lodsb ; Get character from string
test al,al ; Have we reached end of string?
jnz chloop ; if not process next character
ret
print_string ENDP
END main
要组装和链接代码并创建磁盘映像,如果使用MASM32 SDK中的 ML.EXE 和 LINK16.EXE,您可以使用以下命令:
ml.exe /Fe boot.bin /Bl link16.exe boot.asm
ml.exe /Fe stage2.bin /Bl link16.exe stage2.asm
copy /b boot.bin+stage2.bin disk.img
如果您希望包含 BPB,则可以通过以下方式组装和链接它:
ml.exe /DWITH_BPB /Fe boot.bin /Bl link16.exe boot.asm
ml.exe /Fe stage2.bin /Bl link16.exe stage2.asm
copy /b boot.bin+stage2.bin disk.img
这两种方法都会创建一个名为disk.img
. 当disk.img
在 BOCHS 中启动时,它应该看起来像: