2

我正在尝试构建一个多级引导加载程序,但我被困在应该将第二级读入内存的第一级代码中,该代码用于int 13h从虚拟软盘(.img 文件)中读取扇区。这是代码(MASM 语法):

        .286
        .model tiny
        .data
    org 07c00h
    driveNumber db ?
        .code
main:  jmp short start
       nop
start: mov driveNumber,dl  ;storing booting drive number
       cli
       mov ax,cs
       mov ds,ax
       mov es,ax
       mov ss,ax
       sti
reset: mov ah,0h              ;resetting the drive to the first sector
       mov dl,driveNumber
       int 13h
       js reset
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,01h             ;form cylinder #1
       mov cl,02h             ;starting from sector #2
       mov dh,01h             ;using head #1
       mov dl,driveNumber     ;on booting drive
       int 13h
       jc read

       push 1000h             ;pushing the memory address into stack
       push 0h                ;pushing the offset
       retf

end main

此代码与最后两个字节中的 0x55AA 签名一起放在虚拟磁盘的第一个扇区,第二阶段代码放在后面的扇区。

而且,当我在这里时,它没有用!

我在 vmware 和 bochs 上都试过了,都给出了同样的结果:什么都没有!

所以我进行了一些测试:

  1. 我认为问题可能与柱面、磁头和扇区的索引方式有关。所以我尝试了柱面、磁头和扇区号的各种组合,但对我没有好处。
  2. 我检查了返回结果int 13h并得到:状态码(ah==00h -> 成功),实际扇区读取计数(al= 01h -> 1 个扇区已实际读取)。
  3. 在读取过程之前,我已经输入了一些值es:bx然后运行读取过程,完成后我检查了值,es:bx发现它仍然是我之前输入的值,而不是应该读取的值形式部门。

所以,我有一个测试告诉我该扇区实际上已被读取,还有一个测试告诉我没有任何内容读入内存......因此,我被卡住了!

有什么想法吗?

4

2 回答 2

4

主要问题是您从磁盘上的错误位置读取。如果您将第二阶段从磁盘的第二个扇区开始放置在引导扇区之后,即圆柱/磁头/扇区 (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(即:它类似于.driveNumbermov driveNumber, dldriveNumbermov [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 imm8PUSH 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 中启动时,它应该看起来像:

在此处输入图像描述

于 2019-08-27T00:16:00.750 回答
0

看看Ralf Brown 的 int 13h 中断列表

IIRC,它甚至有一些代码向您展示如何读取/写入特定数据(例如引导扇区)等。

于 2013-11-08T19:14:55.933 回答