用int 13h读取硬盘驱动器写入扇区

Van*_*zef 6 x86 assembly bios bootloader bochs

我有一个简单的程序.它必须从硬盘驱动器(而不是mbr)读取第一个扇区,并将其写入0扇区(mbr).但它不起作用.我认为它与错误的DAP有关.谢谢.

    [bits   16]
    [org    0x7c00]

;clear screen
start:
    mov     ax, 0x3
    int     0x10

;reset the hard drive
    xor     ah, ah
    mov     dl, 0x80
    int     0x13
    jnz     error

;read the second sector
    mov     si, DAP
    mov     ah, 0x42
    int     0x13

    mov     si, data
    call    print_string
    jmp     $

DAP:
    db      0x10    ;size of DAP
    db      0x0     ;zero
    db      0x1     ;number of sectors to read
    db      0x0     ;zero
;point to memory
    dw      0x0     ;offset
    dw      0x0     ;segment
    dq      0x1     ;disk address

DAP2:
    db      0x10
    db      0x0
    db      0x1
    db      0x0
    dw      0x0
    dw      0x0
    dd      0x0
    dd      0x0            

print_string:
    mov     ax, 0xb800
    mov     es, ax
    xor     di, di
    mov     cx, 8
    rep     movsw
    ret
data: db 'H',2,'e',2,'l',2,'l',2
error:db 'E',2,'r',2,'r',2
    times   510 - ($ - $$) db 0
    dw      0xaa55   
Run Code Online (Sandbox Code Playgroud)

UPD:新代码

    [bits   16]
    [org    0x7c00]

;clear screen
start:
;    mov     ah, 0
;    push    ax
;    pop     ds
    mov     ax, 0x3
    int     0x10

;reset the hard drive
    xor     ah, ah
    mov     dl, 0x80
    int     0x13
    jc      error

;read the second sector
    mov     si, DAP
    mov     ah, 0x42
    int     0x13

    mov     si, data
    call    print_string
    jmp     $

DAP:
    db      0x10    ;size of DAP
    db      0x0     ;zero
    db      0x1     ;number of sectors to read
    db      0x0     ;zero
;point to memory
    dw      0x0     ;offset
    dw      0x8c00  ;segment
    dq      0x1     ;disk address

DAP2:
    db      0x10
    db      0x0
    db      0x1
    db      0x0
    dw      0x0
    dw      0x8c00
    dq      0x2            

print_string:
    mov     ax, 0xb800
    mov     es, ax
    xor     di, di
    mov     si, 0x8c00
    mov     cx, 8
    rep     movsw
    ret

data: db 'H',2,'e',2,'l',2,'l',2
error:db 'E',2,'r',2,'r',2
endp:
    times   510 - ($ - $$) db 0
    dw      0xaa55 
Run Code Online (Sandbox Code Playgroud)

PS我正在使用Bochs.

lin*_*ina 21

有点嗜尸; 希望你的装配技能在此期间得到改善.但为了以防万一......

引用@Alexey Frunze,"你需要注意你正在做的事情".除了其他答案中详述的错误之外,以下是我的一些观察:


你的模拟器太善良了

  • 您的代码似乎是一个引导加载程序.您假设BIOS将加载您的代码0x0000:0x7C00,但您无法确定它实际上没有加载它0x07C0:0000或任何其他等效地址.阅读细分.

  • 您无法初始化任何段寄存器.你可能逃脱它,因为你的模拟器是一种,正确初始化cs,ds以及es0x0000.

您可以像这样解决这两个问题:

[bits 16]
[org 0x7C00]

    jmp 0x0000:start_16 ; ensure cs == 0x0000

start_16:
    ; initialise essential segment registers
    xor ax, ax
    mov ds, ax
    mov es, ax
Run Code Online (Sandbox Code Playgroud)


根本的误解

  • 如果发生错误,您可以直接跳转到字符串,而不是可执行代码.Lord只知道如果发生这种情况,计算机会做什么.

  • 检查驱动器复位的返回值(CF),但不检查读取本身.如果读取失败,您应该重置驱动器并再次尝试读取.如果驱动器打嗝,请在循环中执行一些尝试(例如,3).如果驱动器重置失败,可能是更严重的问题,你应该保释.


更安全的方法

我建议使用int 0x13, ah = 0x02.您正在使用可能并非所有系统都支持的扩展BIOS功能(仿真器支持可能很复杂,更不用说在某些现代硬件上发现的懒惰BIOS实现).你处于真实模式 - 你不需要做任何花哨的事情.最好进入保护模式,其长期目标是编写PM驱动程序来处理磁盘I/O.

只要您保持实模式,这里就是一个独立的功能,它将使用简单的BIOS功能从磁盘读取一个或多个扇区.如果您事先不知道您需要哪个扇区,则必须添加额外的检查以处理多轨读取.

; read_sectors_16
;
; Reads sectors from disk into memory using BIOS services
;
; input:    dl      = drive
;           ch      = cylinder[7:0]
;           cl[7:6] = cylinder[9:8]
;           dh      = head
;           cl[5:0] = sector (1-63)
;           es:bx  -> destination
;           al      = number of sectors
;
; output:   cf (0 = success, 1 = failure)

read_sectors_16:
    pusha
    mov si, 0x02    ; maximum attempts - 1
.top:
    mov ah, 0x02    ; read sectors into memory (int 0x13, ah = 0x02)
    int 0x13
    jnc .end        ; exit if read succeeded
    dec si          ; decrement remaining attempts
    jc  .end        ; exit if maximum attempts exceeded
    xor ah, ah      ; reset disk system (int 0x13, ah = 0x00)
    int 0x13
    jnc .top        ; retry if reset succeeded, otherwise exit
.end:
    popa
    retn
Run Code Online (Sandbox Code Playgroud)


您的打印功能采用彩色监视器(通过写入0xB8000的视频内存).再次,你是在真实模式.把事情简单化.使用BIOS服务:

; print_string_16
;
; Prints a string using BIOS services
;
; input:    ds:si -> string

print_string_16:
    pusha
    mov  ah, 0x0E    ; teletype output (int 0x10, ah = 0x0E)
    mov  bx, 0x0007  ; bh = page number (0), bl = foreground colour (light grey)
.print_char:
    lodsb            ; al = [ds:si]++
    test al, al
    jz   .end        ; exit if null-terminator found
    int  0x10        ; print character
    jmp  .print_char ; repeat for next character
.end:
    popa
    retn
Run Code Online (Sandbox Code Playgroud)


用法示例

load_sector_2:
    mov  al, 0x01           ; load 1 sector
    mov  bx, 0x7E00         ; destination (might as well load it right after your bootloader)
    mov  cx, 0x0002         ; cylinder 0, sector 2
    mov  dl, [BootDrv]      ; boot drive
    xor  dh, dh             ; head 0
    call read_sectors_16
    jnc  .success           ; if carry flag is set, either the disk system wouldn't reset, or we exceeded our maximum attempts and the disk is probably shagged
    mov  si, read_failure_str
    call print_string_16
    jmp halt                ; jump to a hang routine to prevent further execution
.success:
    ; do whatever (maybe jmp 0x7E00?)


read_failure_str db 'Boot disk read failure!', 13, 10, 0

halt:
    cli
    hlt
    jmp halt
Run Code Online (Sandbox Code Playgroud)


最后但并非最不重要的...

您的引导加载程序不会设置堆栈.我提供的代码使用堆栈来防止寄存器丢弃.在引导加载程序(<0x7C00)之前有大约30KiB可用,因此您可以在引导加载程序的开头附近执行此操作:

xor ax, ax
cli         ; disable interrupts to update ss:sp atomically (AFAICT, only required for <= 286)
mov ss, ax
mov sp, 0x7C00
sti
Run Code Online (Sandbox Code Playgroud)


唷!要消化很多.请注意,我试图保持独立功能的灵活性,因此您可以在其他16位实模式程序中重复使用它们.我建议你尝试编写更多的模块化代码,并坚持这种方法,直到你更有经验.

例如,如果您已经设置了使用扩展读取功能,那么您可能应该在堆栈上编写一个接受DAP或指向一个DAP的函数.当然,你会浪费代码空间在那里推送数据,但是一旦它存在,你可以简单地调整后续读取的必要字段,而不是让大量的DAP占用内存.堆栈空间可以在以后回收.

不要灰心,汇编需要时间,并且对细节有着极大的关注......在工作时抨击这些东西并不容易,所以我的代码可能会出错!:)

  • 您的答案很彻底。我想评论一下CLI / STI部分。如果针对多种硬件,则使用CLI / STI进行操作是个好主意。在8086/8088/80286上也不需要CLI / STI,但是在某些非常早的8088(但不是全部)中有一个“错误”,其中中断未按规范关闭(按照规范)段寄存器更新的持续时间和以下说明。在8086/8088上,所有段寄存器都假定这样做(从286开始,它仅适用于SS,不适用于其他段寄存器)。 (2认同)
  • 如果不是8088上的错误,则永远不需要显式CLI/STI.但是如果你想使用CLI/STI来定位8088s,那么这是一个好主意! (2认同)

Ale*_*nze 8

首先,您需要检查cf并且不要zf查看BIOS调用是否成功.纠正你的jnz error.

其次,你似乎依赖于ds等于0.它不能保证为0.将其设置为0.

同上flags.df,它不能保证为0.将其设置为0.检查文档rep,movs*cld.

第三,你要求BIOS读取扇区并将其写入内存中的物理地址0.通过这样做,您将覆盖中断向量表(从那里开始并占用1KB)并损坏系统,需要重新启动.选择一个更好的地址.最好的是在内存中bootsector结束之后.但是您还需要确保堆栈不存在,因此您还需要将堆栈设置为已知位置.

你需要注意你正在做的事情.