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, и оба дают одно и то же: ничего!
Итак, я провел несколько тестов:
- Я думал, что проблема может быть в том, как цилиндры, головки и сектора индексируются. Поэтому я попробовал различные комбинации номеров цилиндров, головок и секторов, но это мне не помогло.
- Я проверил возвращение
int 13h
и я получил: код состояния (ah
== 00h -> успешно), фактический счетчик прочитанных секторов (al
= 01h -> 1 сектор был фактически прочитан). - Перед чтением я положил некоторую ценность в
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, он даже имел некоторый код, чтобы показать вам, что делать для чтения / записи определенных фрагментов данных (таких как загрузочный сектор) и т. Д.