【问题标题】:int 13h not reading sectors from virtual diskint 13h 不从虚拟磁盘读取扇区
【发布时间】:2013-11-20 21:43:28
【问题描述】:

我正在尝试构建一个多级引导加载程序,但我被困在应该将第二级读入内存的第一级代码中,该代码使用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的值,发现它仍然是我之前输入的值,而不是应该从扇区中读取的值。

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

有什么想法吗?

【问题讨论】:

  • 一些快速cmets:在明确希望从0:7c00执行之后,您不应该使用cs来初始化dses,只需将它们设置为0(它不会'不过,对于这个确切的例子来说并不重要)。柱面/磁头/扇区的事情令人困惑,但 IIRC 仅扇区从 1 开始,其他扇区从 0 开始(但请查看)。但是您确实应该启动并运行一些简单的调试输出(和/或使用调试器,例如 Bochs)。如果你还不知道,我真的推荐OSDEV wiki

标签: assembly x86-16 bootloader bios osdev


【解决方案1】:

看看Ralf Brown's interrupt list for int 13h

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

【讨论】:

    【解决方案2】:

    主要问题是您从磁盘上的错误位置读取。如果您将第二阶段从磁盘的第二个扇区开始放置在引导扇区之后,即圆柱/磁头/扇区 (CHS) = (0,0,2)。引导扇区为 (0,0,1)。扇区编号从 1 开始,而柱面和磁头编号从 0 开始。

    其他潜在问题是(其中许多可以在我的General Bootloader tips 中找到):

    • 您的代码依赖于 CS 设置为 0000h 的事实(因为您使用的 ORG 为 7c00h)。您设置了 DS=ES=SS=CS。除了 DL 中的驱动器编号之外,您不应假定任何段寄存器或通用寄存器的状态。如果您需要像 DS 这样的段寄存器设置为 0000h,然后将其设置为零。

    • 在设置 DS 段之前,将 DL 中的驱动器号写入内存地址 driveNumber。可以将引导驱动器写入一个段,然后稍后从错误的段中读取。如果您需要将 DL 保存到内存中,请在设置 DS 段后执行。 mov driveNumber, dl 在引用 driveNumber 时隐式使用 DS(即:它类似于 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 年代初生产的一些 defective 8088s 之外,几乎所有处理器都是如此。如果您有机会在这样的系统上启动,请考虑将 CLI/STI 放在更新 SS:SP 的代码周围。 p>

    • 在尝试磁盘重置后,您有一个 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 编码。这是added in the 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
      
    • 1234563签名。

    要解决上述问题,您的代码可能如下所示:

    .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(读取)和Int 13h/AH=0(重置)只会破坏 AX 寄存器 (AH/AL)。无需设置所有参数即可在磁盘故障后进行另一次读取。

    • 如前所述,重试磁盘操作 3 次在真实硬件上很常见。您可以使用 SI 作为磁盘操作的重试计数,因为磁盘读取和重置 BIOS 调用不使用 SI

    • 不用开头:

      main:  jmp short start
             nop
      start:
      

      除非您插入 BIOS 参数块 (BPB) 用作卷引导记录 (VBR)。在使用软盘驱动器仿真 (FDD) 在 USB 设备上启动时,在真实硬件上拥有 BPB 是 good idea

    • 如果像这样更新一个 16 位寄存器的高 8 位寄存器和低 8 位寄存器:

      mov ah,02h
      mov al,01h
      

      您可以通过这种方式将它们组合成一条指令:

      mov ax, 0201h
      

    实现附加观察中确定的内容,代码可能如下所示:

    boot.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 中启动时,它应该显示如下:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-02-01
      • 2010-12-31
      • 2010-12-17
      • 2020-02-22
      • 2014-09-16
      • 2019-09-05
      • 2020-04-12
      • 2020-02-03
      相关资源
      最近更新 更多