Я попытался загрузить ядро, хранящееся на дискете, отформатированной в FAT12. При попытке прочитать сектора, где находится ядро, у меня ошибок нет (флаг переноса не установлен), но в памяти, куда я пытаюсь загрузить ядро, стоят нули.
org 0x7C00
bits 16
%define endl 0x0D, 0x0A
%define dirEntrySize 32
%define fatLocation 0x7E00
%define rootdirLocation 0x9000
%define kernelLocation 0x9E00
%define filenameLength 11
jmp short main
nop
bpb_oemId: db "mkfs.fat"
bpb_bytesPerSector: dw 512
bpb_sectorsPerCluster: db 1
bpb_reservedSectors: dw 1
bpb_fatCount: db 2
bpb_rootdirEntries: dw 224
bpb_sectorCount: dw 2880
bpb_mediaDescriptor: db 0xF0
bpb_sectorsPerFat: dw 9
bpb_sectorsPerTrack: dw 18
bpb_headCount: dw 2
bpb_hiddenSectors: dd 0
bpb_largeSectorCount: dd 0
ebr_driveNumber: db 0x00
ebr_ntFlags: db 0
ebr_driveSignature: db 0x29
ebr_volumeId: dd 0
ebr_volumeLabel: db "NO NAME "
ebr_systemId: db "FAT12 "
rootdirStart: dw 0
dataStart: dw 0
main:
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
mov [ebr_driveNumber], dl
mov si, msg_loading
call puts
; Read FAT
mov ax, [bpb_reservedSectors]
mov bx, fatLocation
mov cx, [bpb_sectorsPerFat]
call readSectors
; Read Root Directory
mov ax, [bpb_rootdirEntries]
mov bx, dirEntrySize
mul bx
div word [bpb_bytesPerSector]
mov [dataStart], ax
xchg ax, cx
mul byte [bpb_fatCount]
add ax, [bpb_reservedSectors]
mov bx, rootdirLocation
call readSectors
mov [rootdirStart], ax
add [dataStart], ax
; Find and read the kernel
mov si, kernelFilename
call findFile
mov bx, kernelLocation
call readFile
; Load kernel
mov dl, [ebr_driveNumber]
jmp kernelLocation
cli
hlt
putc:
push ax
push bx
mov ah, 0x0E
xor bx, bx
int 0x10
pop bx
pop ax
ret
puts:
push ax
push si
.loop:
lodsb
test al, al
jz .done
call putc
jmp .loop
.done:
pop si
pop ax
ret
lbaToChs:
push ax
push dx
xor dx, dx
div word [bpb_sectorsPerTrack]
inc dx
mov cx, dx
xor dx, dx
div word [bpb_headCount]
mov dh, dl
mov ch, al
shl ah, 6
or cl, ah
pop ax
mov dl, al
pop ax
ret
readSectors:
push ax
push cx
push dx
push si
push di
push cx
call lbaToChs
pop si
mov di, 3
mov dl, [ebr_driveNumber]
.loop:
mov ah, 0x02
int 0x13
mov ah, 0x01
int 0x13
jnc .done
mov ah, 0x00
int 0x13
dec di
jz .fail
dec si
jnz .loop
.done:
pop di
pop si
pop dx
pop cx
pop ax
ret
.fail:
mov si, msg_diskError
call puts
cli
hlt
findFile:
push bx
push si
push di
mov ax, 0
mov di, rootdirLocation
.loop:
mov bx, filenameLength
.compare:
cmpsb
jnz .skip
dec bx
jz .done
jmp .compare
.skip:
sub si, filenameLength
add di, dirEntrySize - filenameLength
inc ax
cmp ax, word [bpb_rootdirEntries]
jnz .loop
.fail:
mov si, msg_findFailed
call puts
cli
hlt
.done:
mov si, rootdirLocation
mov bx, dirEntrySize
mul dx
add si, ax
add si, 0x1A
lodsw
pop di
pop si
pop bx
ret
readFile:
push ax
push bx
push cx
push dx
.loop:
push ax
sub ax, 2
mul byte [bpb_sectorsPerCluster]
add ax, word [dataStart]
mov cx, 1
call readSectors
mov ax, [bpb_bytesPerSector]
mul byte [bpb_sectorsPerCluster]
add bx, ax
pop ax
mov cl, 3
mul cx
dec cl
div cx
mov si, fatLocation
add si, ax
mov ax, [si]
or dx, dx
jnz .odd
.even:
and ax, 0x0FFF
jmp .update
.odd:
shr ax, 4
.update:
cmp ax, 0xFF0
jnl .loop
.done:
pop dx
pop cx
pop bx
pop ax
ret
.fail:
mov si, msg_loadFailed
call puts
cli
hlt
kernelFilename: db "KERNEL BIN"
msg_loading: db "Loading...", endl, 0
msg_findFailed: db "Cannot find kernel", 0
msg_loadFailed: db "Cannot load kernel", 0
msg_diskError: db "Disk error", 0
times 510+$$-$ db 0
dw 0xAA55
Я попробовал отладку с помощью GDB и проверил регистры, но их значения были правильными. Я также проверил память, в которой хранил FAT и корневой каталог, но эти данные тоже оказались верными. Только память, куда я хотел загрузить ядро, была заполнена нулями.
Значения регистров после преобразования lba-to-chs:
eax 0x21 33
ecx 0x10 16
edx 0x100 256
ebx 0x9e00 40448
esp 0x7be6 0x7be6
ebp 0x0 0x0
esi 0x7d98 32152
edi 0x0 0
eip 0x7ce6 0x7ce6
eflags 0x202 [ IOPL=0 IF ]
cs 0x0 0
ss 0x0 0
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
Значение AX правильное, должно быть lba начала данных: reserved_sectors + sectors_per_fat * fat_count + root_dir_entries * dir_entry_size / bytes_per_sectors = 1 + 9 * 2 + 224 * 32 / 512 = 1 + 18 + 14 = 33. Образ дискеты, открытый в шестнадцатеричном редакторе, указывает на начало ядра.
Таблица размещения загруженных файлов:
0x7e00: 0xf0 0xff 0xff 0xff 0x0f 0x00 0x00 0x00
0x7e08: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
...
Загруженный корневой каталог:
0x9000: 0x4b 0x45 0x52 0x4e 0x45 0x4c 0x20 0x20
0x9008: 0x42 0x49 0x4e 0x20 0x18 0x00 0x28 0x61
0x9010: 0xaa 0x58 0xaa 0x58 0x00 0x00 0x28 0x61
0x9018: 0xaa 0x58 0x02 0x00 0x25 0x01 0x00 0x00
0x9020: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x9028: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x9030: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x9038: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
...
*Загружено* ядро:
0x9e00: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x9e08: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x9e10: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x9e18: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
...
Нет, я просто сократил это для краткости.
Это то, что я заявил. Вы это пропустили. Я не могу запустить ваш код без этих переменных. Поэтому я не могу это проверить, даже если бы захотел. (Инструкции по сборке также будут полезны.)
@ecm я вставил это обратно
jnl .loop имеет неверный смысл: запись FAT >= 0FF7h (или 0FF0h, если мы не требовательны) означает конец, а не дальнейший цикл. Несвязанно с этим, в readFile.loop у вас есть mov cx, 1 раньше call readSectors, но вы хотите прочитать количество секторов на кластер, а не 1. Кроме того, вы делаете некоторые предположения о значениях, умещающихся в байтах или словах, и о том, что корневой каталог не нуждается в округлении. , и этот каталог, FAT, ядро помещается в памяти. Кроме того, сканирование вашего каталога сбивается и не работает, если KERNEL.BIN не является первой записью. readSectors неправильно читает несколько секторов.
Я отладил это с помощью загрузочного lDebug , кстати, работающего в qemu. Я создал образ дискеты размером 1440 КиБ, используя мой скрипт bootimg.





org 0x7C00
bits 16
Вы можете добавить сюда cpu 8086, чтобы гарантировать, что условные переходы используют только кодировки коротких переходов.
%define endl 0x0D, 0x0A
%define dirEntrySize 32
%define fatLocation 0x7E00
%define rootdirLocation 0x9000
%define kernelLocation 0x9E00
%define filenameLength 11
FAT на образе дискеты размером 1440 КиБ имеет 9 секторов по 1200 байт. Так что это просто соответствует выделенной вами памяти. Это может привести к сбою, если вы захотите загрузить файловую систему большего размера.
Корневому каталогу с 224 записями требуется 1C00h байт. Однако на самом деле вам больше не нужен корневой каталог после того, как вы нашли файл для поиска, поэтому перекрытие между расположением каталога и расположением ядра не является проблемой.
jmp short main
nop
bpb_oemId: db "mkfs.fat"
bpb_bytesPerSector: dw 512
bpb_sectorsPerCluster: db 1
bpb_reservedSectors: dw 1
bpb_fatCount: db 2
bpb_rootdirEntries: dw 224
bpb_sectorCount: dw 2880
bpb_mediaDescriptor: db 0xF0
bpb_sectorsPerFat: dw 9
bpb_sectorsPerTrack: dw 18
bpb_headCount: dw 2
bpb_hiddenSectors: dd 0
bpb_largeSectorCount: dd 0
ebr_driveNumber: db 0x00
ebr_ntFlags: db 0
ebr_driveSignature: db 0x29
ebr_volumeId: dd 0
ebr_volumeLabel: db "NO NAME "
ebr_systemId: db "FAT12 "
rootdirStart: dw 0
dataStart: dw 0
Для оптимизации размера вы не хотите помещать их в загрузчик как байты с нулевой инициализацией, потому что вам никогда не понадобится их инициализация нулями. Однако, хотя это и не оптимально, это не является неправильным.
main:
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
mov [ebr_driveNumber], dl
mov si, msg_loading
call puts
; Read FAT
mov ax, [bpb_reservedSectors]
mov bx, fatLocation
mov cx, [bpb_sectorsPerFat]
call readSectors
; Read Root Directory
mov ax, [bpb_rootdirEntries]
mov bx, dirEntrySize
mul bx
div word [bpb_bytesPerSector]
Это должно округлить секторы для каждого корневого каталога. Однако многие загрузчики и драйверы допускают эту ошибку.
mov [dataStart], ax
Это то, что я указал для переменных, вы безоговорочно перезаписываете их, поэтому их не нужно инициализировать нулями.
xchg ax, cx
mul byte [bpb_fatCount]
Предполагается, что количество секторов на FAT <256.
add ax, [bpb_reservedSectors]
Предполагается, что начало корневого каталога < 65_536.
mov bx, rootdirLocation
call readSectors
mov [rootdirStart], ax
Кажется, вы нигде не читаете эту переменную, так зачем ее устанавливать?
add [dataStart], ax
; Find and read the kernel
mov si, kernelFilename
call findFile
Поскольку файл find никогда не вызывается с другим именем, вы можете жестко запрограммировать адрес имени файла ядра в функции поиска файла.
mov bx, kernelLocation
call readFile
; Load kernel
mov dl, [ebr_driveNumber]
jmp kernelLocation
Комментарий некорректен, на этом этапе ядро уже загружено (если код работает корректно). Здесь вы пытаетесь передать управление ядру.
Вы предполагаете, что cs:ip равно 0:7C00h в начале вашего загрузчика. Вместо этого было бы лучше сделать большой прыжок, например jmp 0:kernelLocation, чтобы гарантировать, что cs сбрасывается в ноль, потому что вместо этого предыдущий загрузчик может ввести ваш код по адресу 7C0h:0. (Немедленные короткие и близкие вызовы и прыжки всегда выполняются с использованием rel8 или rel16, поэтому эта разница не влияет на них.)
cli
hlt
Это мертвый код, который так и не был достигнут. Удалите его, чтобы сэкономить место.
putc:
push ax
push bx
mov ah, 0x0E
xor bx, bx
int 0x10
pop bx
pop ax
ret
Если вы использовали bp, вам также следует нажать и вставить его сюда , потому что эта функция может уничтожить bp .
puts:
push ax
push si
.loop:
lodsb
test al, al
jz .done
call putc
jmp .loop
.done:
pop si
pop ax
ret
Функция put-символа не обязательно должна быть функцией, если вы вызываете ее только из одного места. Однако это не проблема корректности.
lbaToChs:
push ax
push dx
xor dx, dx
div word [bpb_sectorsPerTrack]
inc dx
mov cx, dx
xor dx, dx
div word [bpb_headCount]
mov dh, dl
mov ch, al
shl ah, 6
or cl, ah
pop ax
mov dl, al
pop ax
ret
push dx и сопутствующие pop и mov не нужны как таковые, потому что вы всегда перезаписываете dl после вызова этой функции.
readSectors:
push ax
push cx
push dx
push si
push di
push cx
call lbaToChs
pop si
mov di, 3
mov dl, [ebr_driveNumber]
.loop:
mov ah, 0x02
int 0x13
Вы не инициализировали al количество секторов для чтения! Наивным решением было бы инициализировать общее количество секторов, которые вы хотите прочитать за один вызов функции. Однако если вы пересечете границу 64 КиБ или границу «дорожки», чтение более 1 сектора может завершиться неудачно. Поэтому в моих загрузчиках загрузочных секторов функция readSectors считывает один сектор безоговорочно. Он выполняет расчет LBA в CHS для каждого сектора, который он хочет прочитать, а не один раз за непрерывное чтение. Вызывающие программы должны позаботиться о зацикливании, если они хотят прочитать несколько секторов.
Итак, если вы хотите сделать это, вам следует выполнить расчет LBA в CHS в функции чтения одного сектора и вызвать это в цикле, который увеличивает LBA и добавляет к read_sector после чтения каждого отдельного сектора. (Это также означает, что вам не нужно вычислять количество байтов на кластер и добавлять их к указателю позже в bx.)
mov ah, 0x01
int 0x13
jnc .done
Этот звонок в лучшем случае бесполезен. readFile подскочит, если чтение прошло успешно. Но если это произойдет, вы пропустите цикл jnc и никогда не продолжите читать последующие сектора.
mov ah, 0x00
int 0x13
dec di
jz .fail
dec si
jnz .loop
Петля dec si очень запутана. Цикл dec si, похоже, предназначен для повторной попытки ошибки. Но зачем зацикливаться на следующем секторе, только если происходит повторная попытка с ошибкой? Кроме того, если вы выполняете цикл для dec di > 1, вы не вычисляете следующий кортеж CHS и не передаете адрес, поэтому вы не сможете правильно прочитать несколько секторов.
.done:
pop di
pop si
pop dx
pop cx
pop ax
ret
.fail:
mov si, msg_diskError
call puts
cli
hlt
Лучше запустить si функцию 00h, а затем int 16h вернуть управление предыдущему загрузчику. Или используйте int 19h \ sti \ halt: \ hlt, что позволяет перезагрузиться, например, с помощью Ctrl-Alt-Del.
findFile:
push bx
push si
push di
mov ax, 0
mov di, rootdirLocation
.loop:
mov bx, filenameLength
.compare:
cmpsb
jnz .skip
dec bx
jz .done
jmp .compare
Этот цикл работает, если (SFN) KERNEL.BIN находится в самой первой записи каталога.
.skip:
sub si, filenameLength
add di, dirEntrySize - filenameLength
Это очень сбивает с толку. Вы не знаете, на какую точку jmp halt и di указывают (например, если si обнаруживается как несоответствие, то KERNEL.SYS будет указывать на di в «SYS», а Y на si в «BIN»), поэтому эти жестко запрограммированные настройки не могут быть выполнены. правильный. Если я вставлю удаленную запись каталога (первый байт не совпадает), ваши инструкции здесь также не сработают.
inc ax
cmp ax, word [bpb_rootdirEntries]
jnz .loop
Это странно, но не неправильно. Я предпочитаю инициализировать записи корневого каталога и вести обратный отсчет в регистре.
.fail:
mov si, msg_findFailed
call puts
cli
hlt
Вы можете поделиться большей частью кода с другой функцией отображения ошибок.
.done:
mov si, rootdirLocation
mov bx, dirEntrySize
mul dx
Это кажется неправильным, возможно, вы имели в виду I ?
add si, ax
add si, 0x1A
lodsw
pop di
pop si
pop bx
ret
readFile:
push ax
push bx
push cx
push dx
.loop:
push ax
sub ax, 2
mul byte [bpb_sectorsPerCluster]
Предполагается, что ваш текущий кластер < 256. Это не обязательно верно для образа дискеты размером 1440 КиБ.
add ax, word [dataStart]
mov cx, 1
call readSectors
Если бы mul bx работало, это бы жестко запрограммировало размер кластера в 1 сектор.
mov ax, [bpb_bytesPerSector]
mul byte [bpb_sectorsPerCluster]
add bx, ax
Предполагается, что количество байтов на сектор < 256, что неверно для типичного изображения. Таким образом, вы добавите ноль к readSectors, поэтому позже перезапишете первый сектор ядра.
pop ax
mov cl, 3
mul cx
dec cl
div cx
mov si, fatLocation
add si, ax
mov ax, [si]
or dx, dx
jnz .odd
.even:
and ax, 0x0FFF
jmp .update
.odd:
shr ax, 4
О, это 186 инструкция. Конечно, для qemu это не проблема.
.update:
cmp ax, 0xFF0
jnl .loop
Это должно быть bx (или jb .loop). Если следующий кластер ниже 0FF0h, выполните цикл.
.done:
pop dx
pop cx
pop bx
pop ax
ret
.fail:
mov si, msg_loadFailed
call puts
cli
hlt
Это мертвый код, который так и не был достигнут.
kernelFilename: db "KERNEL BIN"
msg_loading: db "Loading...", endl, 0
msg_findFailed: db "Cannot find kernel", 0
msg_loadFailed: db "Cannot load kernel", 0
msg_diskError: db "Disk error", 0
times 510+$$-$ db 0
dw 0xAA55
Наконец, я отладил это с помощью загрузочного lDebug , работающего в qemu. Я создал образ дискеты размером 1440 КиБ, используя мой скрипт bootimg. А вот командный скриптлет, который я использовал для отладки вашего загрузчика:
jl .loop
Я изменил переключатель на test$ nasm kernel.asm -o kernel.bin && nasm test.asm -o test.bin -l test.lst && nasm -I ~/proj/lmacros/ -I ~/proj/bootimg/ ~/proj/bootimg/bootimg.asm -D_PAYLOADFILE = "kernel.bin" -D_BOOTPATCHFILE = "test.bin" -o diskette.img && nasm -I ~/proj/lmacros/ -I ~/proj/ldosboot/ ~/proj/ldosboot/boot.asm -o boot12.bin -D_LOAD_NAME = "'LDEBUG'" && nasm -I ~/proj/lmacros/ -I ~/proj/ldosmbr/ ~/proj/ldosmbr/oldmbr.asm -o oldmbr.bin && nasm -I ~/proj/ldebug/bin/ -I ~/proj/lmacros/ -I ~/proj/bootimg/ ~/proj/bootimg/bootimg.asm -D_PAYLOADFILE = "ldebug.com,extlib.eld" -D_BOOTPATCHFILE = "boot12.bin" -D_MBR -D_MBRPATCHFILE = "oldmbr.bin" -o hdimage.img && qemu-system-i386 -hda hdimage.img -fda diskette.img -boot order=c -display curses -chardev serial,id=serial2,path=/tmp/vptty-dos -serial null -serial chardev:serial2; stty sane, чтобы вставить удаленную запись каталога, что приводит к сбою вашего кода даже после исправления ошибки -D_PAYLOADFILE = "::directorypad,1,kernel.bin".
Конечно, findFile и readFile также вызываются только один раз, поэтому их можно встроить.
В lbaToChs есть shl ah, 6, а в ReadFile есть shr ax, 4, о котором вы упомянули: «О, это инструкция 186. Конечно, для qemu это не проблема». Это по-прежнему справедливо, если ОП будет использовать cpu 8086, как вы предложили, чтобы гарантировать, что условные переходы используют только кодировки коротких переходов?
@SepRoland Если вы попытаетесь собрать более 186 инструкций (хороший улов для конвертации CHS) с действующей директивой cpu 8086, сборка завершится неудачей во время сборки. В этом случае, чтобы избежать 386+ около условных прыжков, но при этом разрешить 186 сдвигов, вы можете использовать cpu 186 или cpu 286.
Кажется, вы пропустили переменные в разделе «Данные BPB и EBR здесь».