int 13h не читает сектора с виртуального диска

Я пытаюсь построить многоступенчатый загрузчик, но я застрял в 1-м этапе кода, который должен прочитать 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 и обнаружил, что это все еще значение, которое я поставил ранее, а не значение, которое следует читать из сектора.

Итак, у меня есть тест, который говорит мне, что сектор действительно прочитан, и тест, который говорит мне, что в память ничего не читается... поэтому я застрял!

Какие-нибудь мысли?

1 ответ

Основная проблема в том, что вы читаете не с того места на диске. Если вы разместите вторую ступень, начиная со второго сектора диска сразу после загрузочного сектора, то это будет Цилиндр / Головка / Сектор (CHS) = (0,0,2). Загрузочный сектор (0,0,1). Номера секторов начинаются с 1, а нумерация цилиндров и головок - с 0.

Другие потенциальные проблемы (многие из которых можно найти в моих общих советах по загрузчику):

  • Ваш код основан на том факте, что CS установлен на 0000h (поскольку вы используете ORG 7c00h). Вы устанавливаете DS=ES=SS=CS. Вы не должны предполагать состояние каких-либо сегментных регистров или регистров общего назначения, за исключением номера диска в DL. Если вам нужен сегментный регистр, такой как DS, установленный на 0000h, установите его на ноль.

  • Вы записываете номер накопителя в DL по адресу памятиdriveNumberДО того, как вы установите сегмент DS. Возможно записать загрузочный диск в один сегмент, а затем прочитать из неправильного сегмента. Если вам нужно сохранить DL в памяти, сделайте это после установки сегмента DS.mov driveNumber, dlимеет неявное использование DS при ссылкеdriveNumber (то есть: он похож на mov [ds:driveNumber], dl.

  • Фактически вы не устанавливаете SP в своем коде. Вы только SS обновляете. Кто знает, куда указывает SP! Комбинация SS:SP определяет текущий указатель стека. Вы можете настроить размер стека ниже загрузчика, установив для SS:SP значение 0000h:7c00h. Это не помешает загрузке stage2 в 1000h:0000h.

  • Вам не нужно размещать CLI/STI вокруг обновлений регистров сегментов. Единственное место, которое должно быть атомарным, - это обновление SS:SP. Если вы пишете в СС процессор не будет запрещать прерывания до тех пор, после следующей инструкции. Если вы обновите SP сразу после SS, это можно будет рассматривать как атомарную операцию, и нет необходимости в CLI / STI. Это верно почти для каждого процессора, за исключением некоторых неисправных 8088, выпущенных в начале 1980-х годов. Если есть вероятность, что вы загрузитесь в такой системе, подумайте о том, чтобы поместить 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 imm8 или PUSH 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        ; Storingbooting 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 как счетчик повторных попыток для дисковых операций, поскольку SI не используется для чтения с диска и вызовов сброса BIOS.

  • Необязательно начинать с:

    main:  jmp short start
           nop
    start:
    

    если вы не вставляете блок параметров BIOS (BPB) для использования в качестве загрузочной записи тома (VBR). Наличие BPB - хорошая идея на реальном оборудовании при загрузке с USB-устройств с использованием эмуляции гибкого диска (FDD).

  • Если обновить верхний и нижний 8-битные регистры 16-битного регистра следующим образом:

    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

Чтобы собрать и связать код, а также создать образ диска, вы можете использовать эти команды при использовании ML.EXE и LINK16.EXE из MASM32 SDK:

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, должно появиться что-то вроде:

Взгляните на список прерываний Ральфа Брауна за 13 часов.

IIRC, он даже имел некоторый код, чтобы показать вам, что делать для чтения / записи определенных фрагментов данных (таких как загрузочный сектор) и т. Д.

Другие вопросы по тегам