Диск BIOS — блокировка чтения секторов в память (int 0x13, ah=0x02)

Я пишу MBR и использую QEMU для тестирования.

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

Что может блокировать прерывание? Я думал, что это можно сделать только с помощью инструкции cli, да и то она не блокирует инструкции int.


Для контекста это код, ведущий к прерыванию блокировки в read_sectors_16:

        [bits 16]        
        [ORG 0x7C00]
        jmp 0x000:start_16      ; ensure cs == 0x0000

        reset_failure_str db 'error: boot disk reset', 13, 10, 0
        read_failure_str db 'error: boot disk read', 13, 10, 0
        boot_segaddr dw 0x7E00

        read_attempt_str db 'read attempt', 13, 10, 0
        end_read_attempt_str db 'end read attempt', 13, 10, 0

start_16:
        ;; Initialize segment registers
        mov ax, cs
        mov ds, ax
        mov es, ax

        jmp load_bootsector_2            


load_bootsector_2:              ; Read program from disk
        ;; dl set by BIOS to the drive number MBR was loaded from
        mov cx, 0x0002          ; cylinder 0, sector 2
        xor dh, dh              ; head 0
        mov al, 0x01            ; load 1 sector
        mov bx, boot_segaddr    ; destination - load right after the boot loader
        call read_sectors_16
        jnc .success
        mov si, read_failure_str
        call print_string_16        
        jmp halt                ; halt

        .success:        
        jmp boot_segaddr:0000   ; jump to program

А вот функция с блокирующим прерыванием:

;;; read_sectors_16
;;;
;;; Read sectors from disk in 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 di, 0x02            ; set attempts (max attempts - 1)

        .attempt:
        mov ah, 0x02            ; read sectors into memory (int 0x13, ah = 0x02)
        int 0x13                ; TODO: this call is not returning!
        jnc .end                ; exit if read succeeded
        dec di                  ; record attempt
        test di, di
        jz .end                 ; end if no more attempts
        xor ah, ah              ; reset disk (int 0x13, ah = 0x00)
        int 0x13
        jnc .attempt            ; retry if reset succeeded, otherwise exit
        jmp .end

        .end:
        popa
        ret

person Stewart Smith    schedule 17.09.2016    source источник
comment
Вы пробовали BOCHS вместо qemu? Он имеет встроенный отладчик, который позволяет вам проверять регистры и отдельные шаги даже в коде MBR. Почти наверняка ваша программа не буквально застряла внутри обработчика прерывания (если, возможно, вы не передали ему поддельные аргументы), возможно, у вас просто ошибка.   -  person Peter Cordes    schedule 17.09.2016
comment
Еще одна интересная вещь (не связанная с вашей проблемой, но может быть проблемой в некоторых средах). mov ax, cs mov ds, ax mov es, ax . На самом деле возможно, что CS не имеет нулевого значения. Некоторые BIOS передают управление загрузчику с CS=0x07C0: iP=0x0000, что также является физическим адресом 0x07c00. Поскольку вы используете ORG 0x7C00, ваш код предполагает, что регистры DS и ES будут равны нулю. Я бы явно установил их равными нулю, выполнив xor ax, ax mov ds, ax mov es, ax. Поэтому вместо того, чтобы копировать CS в DS и ES, мы просто устанавливаем DS=ES=0 .   -  person Michael Petch    schedule 18.09.2016
comment
Кроме того, вероятно, хорошей идеей будет установить собственный стек SS:SP . Причина в том, что вы не знаете, где BIOS установил его, и вы можете непреднамеренно перезаписать его в своем собственном коде (например, прочитать сектора в память поверх стека). Если вы установите свой собственный, вы точно знаете, где он находится, и можете избежать случайного стирания.   -  person Michael Petch    schedule 18.09.2016


Ответы (1)


Что выделяется, так это ваши сегменты. Сначала ваш код определяет boot_segaddr как:

boot_segaddr dw 0x7E00

Это создает в памяти 16-битное слово со значением 0x7E00. Затем у вас есть эти 2 строки:

mov bx, boot_segaddr
[snip]
jmp boot_segaddr:0000 

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

Я бы изменил boot_segaddr dw 0x7E00 на постоянное значение (используя EQU) и переименовал его, чтобы он выглядел так:

BOOT_OFFSET EQU 0x7E00

Затем вы можете изменить mov bx, boot_segaddr на:

mov bx, BOOT_OFFSET

Это приведет к перемещению 0x7E00 в BX. Вызов Int 13/AH=2 будет считывать сектора, начинающиеся с ES:BX = 0x0000:0x7E00, что вам и нужно.

Следующая проблема возникает, если мы повторно используем одну и ту же константу для FAR JMP следующим образом:

jmp BOOT_OFFSET:0000 

Это приведет к FAR JMP на 0x7E00:0x0000. К сожалению, это физический адрес (0x7E00‹‹4)+0x0000 = 0x7E000, а это не то место, куда вы хотите прыгать. Вы хотите перейти на физический адрес 0x07E00. Вам действительно нужен FAR JMP для 0x07E0:0x0000, который будет физическим адресом (0x07E0‹‹4)+0x0000 = 0x7E00. Чтобы FAR JMP работал правильно, мы можем сдвинуть BOOT_OFFSET вправо на 4 бита. Вы можете изменить строку на:

jmp (BOOT_OFFSET>>4):0000 

Внесение этих изменений должно заставить ваш загрузчик работать. Как бы то ни было, в исходном коде было 2 ошибки:

  • Использование адреса вашей переменной, а не значения по этому адресу
  • Выполнение FAR JMP с неправильным значением сегмента.

Очевидное зависание, вероятно, было вызвано чтением сектора, начинающегося с адреса памяти boot_segaddr, который находится в вашем загрузчике. Вероятно, вы перезаписали весь код в своем загрузчике, из-за чего он работал с ошибками, когда int 13h в конце концов вернулся.


Как указывает Питер, использование эмулятора, такого как BOCHS, и его внутреннего отладчика позволит вам пройти один шаг через 16-битный код реального режима. Вы, вероятно, обнаружили бы эти проблемы.

person Michael Petch    schedule 17.09.2016
comment
Совершенно верно, и это решило мою проблему. int не должен возвращать управление моей программе, когда она пытается скопировать в 0x7E000:0x0000 и терпит неудачу. - person Stewart Smith; 18.09.2016