Прочитайте записать сектор с жесткого диска с int 13h

У меня есть простая программа. Он должен прочитать первый сектор с жесткого диска (не 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   

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 

PS Я использую Bochs.

4 ответа

Немного некрофилии; надеюсь, ваши навыки сборки улучшились за это время. Но на всякий случай...

Цитируя @Alexey Frunze, "Вам нужно обратить внимание на то, что вы делаете". В дополнение к ошибкам, описанным в других ответах, вот некоторые из моих наблюдений:


Ваш эмулятор слишком добрый

  • Ваш код выглядит как загрузчик. Вы предполагаете, что BIOS загрузит ваш код в 0x0000:0x7C00, но вы не можете быть уверены, что он на самом деле не загружается в 0x07C0:0000 или любой другой эквивалентный адрес. Читайте о сегментации.

  • Вы не можете инициализировать регистры любого сегмента. Вам, вероятно, это сойдет с рук, потому что ваш эмулятор добрый и правильно инициализирует cs, ds, а также es в 0x0000,

Вы можете решить обе эти проблемы следующим образом:

[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


Фундаментальные недоразумения

  • В случае ошибки вы переходите непосредственно к строке, а не к исполняемому коду. Господь знает только, что сделает компьютер, если это произойдет.

  • Вы проверяете возвращаемое значение (CF) сброса привода, но не считываете само чтение. В случае сбоя чтения, вы должны перезагрузить накопитель и повторить попытку чтения. Сделайте это в цикле на несколько попыток (скажем, 3), если диск работает. Если сброс диска не удастся, скорее всего, что-то более серьезное не так, и вам следует внести залог.


Более безопасный подход

Я бы предложил использовать int 0x13, ah = 0x02, Вы используете расширенную функцию BIOS, которая может поддерживаться не во всех системах (поддержка эмулятора может быть ненадежной, не говоря уже о ленивых реализациях BIOS, найденных на некоторых современных аппаратных средствах). Вы находитесь в реальном режиме - вам не нужно делать что-то необычное. Было бы лучше перейти в защищенный режим с долгосрочной целью написания драйвера PM для обработки дискового ввода-вывода.

Пока вы находитесь в реальном режиме, здесь есть отдельная функция, которая будет считывать один или несколько секторов с диска, используя простые функции 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


Ваша функция печати предполагает цветной монитор (путем записи в видеопамять в 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


Пример использования

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


Последний, но тем не менее важный...

Ваш загрузчик не настроил стек. Код, который я предоставил, использует стек для предотвращения сбоев в реестре. Перед загрузчиком доступно почти 30 КБ (< 0x7C00), так что вы можете просто сделать это где-то в начале вашего загрузчика:

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


Уф! Много переварить. Заметьте, я пытался сохранить гибкость автономных функций, чтобы вы могли повторно использовать их в других 16-битных программах реального режима. Я бы посоветовал вам попытаться написать более модульный код и придерживаться этого подхода, пока вы не станете более опытным.

Например, если вы не можете использовать расширенную функцию чтения, возможно, вам следует написать функцию, которая принимает DAP или указатель на нее в стеке. Конечно, вы будете тратить впустую пространство кода, выталкивая туда данные в первую очередь, но как только они появятся, вы можете просто настроить необходимые поля для последующих чтений, вместо того, чтобы много DAP занимало память. Пространство стека может быть восстановлено позже.

Не расстраивайтесь, сборка занимает много времени, и чудовищное внимание к деталям... нелегко, когда ломаешь все это на работе, поэтому в моем коде могут быть ошибки!:)

Прежде всего, вам нужно проверить cf и не zf чтобы увидеть, если вызов BIOS успешно. Поправьте jnz error,

Во-вторых, вы, кажется, полагаетесь на ds быть равным 0. Это не обязательно будет 0. Установите его в 0.

То же самое для flags.df, не гарантируется, что будет 0. Установите его на 0. Проверьте документацию на rep, movs* а также cld,

В-третьих, вы просите BIOS прочитать сектор и записать его в физический адрес 0 в памяти. Тем самым вы перезаписываете таблицу векторов прерываний (которая начинается там и занимает 1 КБ) и повреждает систему, требуя перезагрузки. Выберите лучший адрес. Лучше всего будет сразу после окончания загрузочного сектора в памяти. Но вам также нужно убедиться, что стек не существует, поэтому вам нужно также установить стек в известном месте.

Вы должны обратить внимание на то, что вы делаете.

Пример минимального NASM BIOS, который загружает сектор с кодом и переходит на него без проверки ошибок и DAP:

use16
org 0x7C00

    ; For greater portability you should
    ; do further initializations here like setup the stack and segments. 

    ; Load stage 2 to memory.
    mov ah, 0x02
    mov al, 1
    ; This may not be necessary as many BIOS setup is as an initial state.
    mov dl, 0x80
    mov ch, 0
    mov dh, 0
    mov cl, 2
    mov bx, stage2
    int 0x13

    jmp stage2

    ; Magic bytes.    
    times ((0x200 - 2) - ($ - $$)) db 0x00
    dw 0xAA55

stage2:

    ; Print 'a'.
    mov ax, 0x0E61
    int 0x10

    cli
    hlt

    ; Pad image to multiple of 512 bytes.
    times ((0x400) - ($ - $$)) db 0x00

Скомпилируйте и запустите:

nasm -f bin -o main.img main.asm
qemu-system-i386 main.img

Ожидаемый результат: a выводится на экран, а затем программа останавливается.

Проверено на Ubuntu 14.04.

Пример Saner GAS с использованием сценария компоновщика и более правильной инициализации (регистры сегментов, стек) на моем GitHub.

load:
; Load sectors routine : bootdrv-drive , snum-sectors to load
;ES:BX where to load ex:  mov ax,0000h mov es,ax  mov bx, 7c00h
        push bx
        push ds
        mov verify,0h
.reset:
        cmp verify,5h
        je Err1
        add verify,1h
        mov ax, 0               ; Reset Disk
        mov dl, [BootDrv]       ; Drive to reset
        int 13h                 ;
        jc .reset               ; Failed -> Try again

        pop ds
        pop bx
        mov verify,0h
.read:
        cmp verify,5h
        je Err1
        add verify,1h
        mov ah, 2               ; Interrupt 13h,2 => Read disk sectors
        mov al, snum            ; how many sectors to read
        mov cx, fsect           ; cl-first sector to be r/w ch-track containing sector
        mov dh, head               ; head=0
        mov dl, [BootDrv]       ; Drive=boot drive
        int 13h                 ; Read! ES:BX = data from disk
        jc .read                ; failed -> Try again
        retn
Другие вопросы по тегам