Вопрос или проблема
Я учусь, как работает операционная система. Поэтому я начал с написания загрузчика. Все шло хорошо, пока я не попытался отформатировать мой образ, содержащий этапы 1 и 2 в FAT12. В данный момент BIOS не может загрузить мой второй этап, но я не получаю никаких сообщений об ошибках.
Я думаю, что это может быть связано с тем, как я создал свой образ.
Вот код для моего первого этапа (извините, это много кода, но я не могу понять, где он не работает):
bits 16
org 0
start:
jmp main
;*********************************************
; BIOS Parameter Block
;*********************************************
bpbOEM db "Alpha OS" ; Идентификатор OEM (не может превышать 8 байт!)
bpbBytesPerSector: DW 512
bpbSectorsPerCluster: DB 1
bpbReservedSectors: DW 1
bpbNumberOfFATs: DB 2
bpbRootEntries: DW 224
bpbTotalSectors: DW 2880
bpbMedia: DB 0xf8 ;; 0xF1
bpbSectorsPerFAT: DW 9
bpbSectorsPerTrack: DW 18
bpbHeadsPerCylinder: DW 2
bpbHiddenSectors: DD 0
bpbTotalSectorsBig: DD 0
bsDriveNumber: DB 0
bsUnused: DB 0
bsExtBootSignature: DB 0x29
bsSerialNumber: DD 0xa0a1a2a3
bsVolumeLabel: DB "MOS FLOPPY "
bsFileSystem: DB "FAT12 "
;***************************************************
; Печать строки
; SI = указатель на строку
; DS=>SI: строка с нулевым терминалом
;***************************************************
Print:
lodsb ; загрузить следующий байт из si в al
or al, al ; проверить, является ли символ нулевым
jz PrintDone
mov ah, 0eh ; получить следующий символ
int 10h ; вызвать прерывание BIOS видео
jmp Print
PrintDone:
ret
;***************************************************
; Чтение серии секторов с диска
; CX = количество секторов для чтения
; AX = начальный сектор
; ES:BX = буфер для чтения
;***************************************************
ReadSectors:
.MAIN:
mov di, 0x0005 ; Количество попыток (5)
.SECTORLOOP:
push ax
push bx
push cx
call LBACHS ; Преобразовать логический блок в CHS
mov ah, 0x02 ; Функция чтения сектора BIOS
mov al, 0x01 ; Чтение одного сектора
mov ch, BYTE [absoluteTrack] ; трек
mov cl, BYTE [absoluteSector] ; сектор
mov dh, BYTE [absoluteHead] ; головка
mov dl, BYTE [bsDriveNumber] ; диск
int 0x13 ; прерывание BIOS
jnc .SUCCESS ; Если чтение прошло успешно, продолжайте
xor ax, ax ; В противном случае сбросить дисковую систему
int 0x13 ; и попробовать снова
dec di ; Если счетчик попыток не равен нулю, попробуйте снова
pop cx
pop bx
pop ax
jnz .SECTORLOOP ; Если счетчик попыток не равен нулю, попробуйте снова
int 0x18 ; Если попытки закончились, напечатайте сообщение об ошибке и остановитесь
.SUCCESS:
mov si, msgProgress
call Print
pop cx
pop bx
pop ax
add bx, WORD [bpbBytesPerSector] ; Перейти к следующему сектору
inc ax ; Перейти к следующему сектору
loop .MAIN ; Если есть еще сектора для чтения, читайте их
ret
;***************************************************
; Преобразовать CHS в LBA
; LBA = (кластер - 2) * сектора на кластер
;***************************************************
ClusterLBA:
sub ax, 0x0002 ; номер сектора с нуля
xor cx, cx
mov cl, BYTE [bpbSectorsPerCluster] ; преобразовать байт в слово
mul cx ; AX = AX * CX
add ax, WORD [datasector] ; базовый сектор данных
ret
;***************************************************
; Преобразовать LBA (логический блок) в CHS
; AX=>LBA адрес для преобразования
;
; абсолютный сектор = (логический сектор / сектора на трек) + 1
; абсолютная головка = (логический сектор / сектора на трек) MOD количество головок
; абсолютный трек = логический сектор / (сектора на трек * количество головок)
;***************************************************
LBACHS:
xor dx, dx ; подготовить dx:ax для операции
div WORD [bpbSectorsPerTrack] ; вычислить ax / SectorsPerTrack
inc dl ; корректировка для сектора 0
mov BYTE [absoluteSector], dl ; сохранить сектор
xor dx, dx ; подготовить dx для операции
div WORD [bpbHeadsPerCylinder] ; вычислить ax / bpbHeadsPerCylinder
mov BYTE [absoluteHead], dl ; номер головки
mov BYTE [absoluteTrack], al ; номер трека
ret
;***************************************************
; Точка входа загрузчика
;***************************************************
main:
;---------------------------------------------------
; код расположен на 000:7C00, настройка сегментных регистров
;---------------------------------------------------
cli ; отключить прерывания
mov ax, 0x07C0 ; настроить регистры, чтобы указывать на наш сегмент
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
;---------------------------------------------------
; создать стек
;---------------------------------------------------
mov ax, 0x0000 ; настроить стек
mov ss, ax
mov sp, 0xFFFF
sti ; восстановить прерывания
;---------------------------------------------------
; Показать сообщение загрузки
;---------------------------------------------------
mov si, msgLoading
call Print
;---------------------------------------------------
; Загрузить таблицу корневого каталога
;---------------------------------------------------
LOAD_ROOT:
; вычислить размер корневого каталога и сохранить в cx
xor cx, cx
xor dx, dx
mov ax, 0x0020 ; В корневом каталоге 32 записи
mul WORD [bpbRootEntries] ; 32 * 224 = 7168 -> общий размер каталога
div WORD [bpbBytesPerSector] ; 7168 / 512 = 14 секторов -> секторы, используемые корневым каталогом
xchg ax, cx ; сохранить количество секторов в cx
; вычислить расположение корневого каталога и сохранить в ax
mov al, BYTE [bpbNumberOfFATs] ; количество FATов
mul WORD [bpbSectorsPerFAT] ; 2 * 9 = 18 -> всего секторов, используемых FAT
add ax, WORD [bpbReservedSectors] ; 18 + 1 = 19 -> всего секторов, используемых FAT + зарезервированные сектора
mov WORD [datasector], ax ; сохранить расположение корневого каталога в datasector
add WORD [datasector], cx
; прочитать корневой каталог в память по адресу 7C00:0200
mov bx, 0x0200 ; скопировать корневой dir выше bootcode
call ReadSectors
;---------------------------------------------------
; Найти этап 2
;---------------------------------------------------
; просмотреть корневой каталог на наличие бинарного образа
mov cx, WORD [bpbRootEntries] ; загрузить счетчик цикла с количеством записей корневого каталога
mov di, 0x0200 ; указать di на первую запись корневого каталога
.LOOP:
push cx
mov cx, 0x000B ; сравнить первые 11 байтов имени файла
mov si, ImageName
push di
rep cmpsb ; сравнить имя файла с тестовой записью
pop di
je LOAD_FAT ; если совпадает, загрузить файл
pop cx
add di, 0x0020 ; перейти к следующей записи
loop .LOOP ; пройти через все записи
jmp FAILURE ; если совпадений нет, напечатать сообщение об ошибке и остановиться
;---------------------------------------------------
; Загрузить FAT
;---------------------------------------------------
LOAD_FAT:
; сохранить начальный кластер загрузочного образа
mov si, msgCRLF
call Print
mov dx, WORD [di + 0x001A] ; получить номер начального кластера
mov WORD [cluster], dx ; первый кластер файла
; вычислить размер FAT и сохранить в cx
xor ax, ax
mov al, BYTE [bpbNumberOfFATs] ; количество FATов
mul WORD [bpbSectorsPerFAT] ; всего секторов, используемых FAT
mov cx, ax ; сохранить размер FAT в cx
; вычислить расположение FAT и сохранить в ax
mov ax, WORD [bpbReservedSectors] ; зарезервированные сектора
; прочитать Fat в память по адресу 7C00:0200
mov bx, 0x0200 ; копировать FAT выше bootcode
call ReadSectors
; прочитать образ файла в память по адресу 0050:0000
mov si, msgCRLF
call Print
mov ax, 0x0050
mov es, ax ; место назначения для образа
mov bx, 0x0000 ; скопировать образ в 0050:0000
push bx
;---------------------------------------------------
; Загрузить Этап 2
;---------------------------------------------------
LOAD_IMAGE:
mov ax, WORD [cluster] ; кластер для чтения
pop bx ; буфер для чтения
call ClusterLBA ; преобразовать кластер в LBA
xor cx, cx
mov cl, BYTE [bpbSectorsPerCluster] ; сектора для чтения
call ReadSectors
push bx
; вычислить следующий кластер
mov ax, WORD [cluster] ; идентифицировать текущий кластер
mov cx, ax ; скопировать текущий кластер
mov dx, ax ; скопировать текущий кластер
shr dx, 0x0001 ; делить на два
add cx, dx ; сумма для (3/2)
mov bx, 0x0200 ; местоположение FAT в памяти
add bx, cx ; индекс в FAT
mov dx, WORD [bx] ; прочитать два байта из FAT
test ax, 0x0001
jnz .ODD_CLUSTER
.EVEN_CLUSTER:
and dx, 0000111111111111b ; взять низкие двенадцать бит
jmp .DONE
.ODD_CLUSTER:
shr dx, 0x0004 ; взять высокие двенадцать бит
.DONE:
mov WORD [cluster], dx ; сохранить новый кластер
cmp dx, 0x0FF0 ; проверить концовку файла
jb LOAD_IMAGE
DONE:
mov si, msgCRLF
call Print
push WORD 0x0050
push WORD 0x0000
ret
FAILURE:
mov si, msgFailure
call Print
mov ah, 0
int 0x16 ; ожидать нажатия клавиши
int 0x19 ; холодная загрузка
absoluteSector db 0x00
absoluteHead db 0x00
absoluteTrack db 0x00
datasector dw 0x0000
cluster dw 0x0000
ImageName db "KRNLDR SYS"
msgLoading db 0x0D, 0x0A, "Загрузка загрузочного образа ", 0x0D, 0x0A, 0x00
msgCRLF db 0x0D, 0x0A, 0x00
msgProgress db ".", 0x0D, 0x0A, 0x00
msgFailure db 0x0D, 0x0A, "ОШИБКА: Нажмите любую клавишу для перезагрузки", 0x0A, 0x00
times 510-($-$$) db 0
dw 0xAA55
Вот код для моего второго этапа, который называется KRNLDR.SYS
с компиляцией:
org 0x0 ; смещение к 0, сегмент установлен позже
bits 16 ; все еще в реальном режиме
; Загружается по линейному адресу 0x100000
jmp main
;***************************************************
; Печать строки
; SI = указатель на строку
; DS=>SI: строка с нулевым терминалом
;***************************************************
Print:
lodsb ; загрузить следующий байт из si в al
or al, al ; проверить, является ли символ нулевым
jz PrintDone
mov ah, 0eh ; получить следующий символ
int 10h ; вызвать прерывание BIOS видео
jmp Print
PrintDone:
ret
;***************************************************
; Точка входа второго загрузчика
;***************************************************
main:
cli ; отключить прерывания
push cs ; удостовериться, что DS=CS
pop ds
mov si, Msg
call Print
cli
hlt
;***************************************************
; Секция данных
;***************************************************
Msg db "Подготовка к загрузке операционной системы...", 13, 10,0
Я создаю свой образ с помощью этого маленького скрипта:
#!/bin/sh
STAGE1="Boot1.bin"
STAGE2="KRNLDR.SYS"
IMAGE="VMs/floppy.img"
nasm -f bin ./bootloader/stage1.asm -o bootloader/$STAGE1
nasm -f bin ./bootloader/stage2.asm -o bootloader/$STAGE2
dd if=/dev/zero of=$IMAGE bs=512 count=720
dd if=bootloader/$STAGE1 of=$IMAGE bs=512 count=1 conv=notrunc
dd if=bootloader/$STAGE1 of=$IMAGE bs=1 skip=62 seek=62 conv=notrunc
mcopy -i $IMAGE bootloader/$STAGE2 ::$STAGE2
Затем я запускаю свой эмулятор с этой командой: “qemu-system-x86_64 -fda VMs/floppy.img -boot a -net none
” (-boot a -net none
используется, чтобы предотвратить запуск IPXE)
После выполнения команды я получил следующее:
Я действительно не понимаю, что я сделал не так в своем коде, поэтому я думаю, что сделал конструкцию образа неправильно, но я не понимаю, где.
Я хотел бы наконец увидеть “Подготовка к загрузке операционной системы…” в BIOS.
Правка:
Даже если переход (jmp
) из start
напрямую к Failure, сообщение об ошибке не отображается.
Ответ или решение
Проблема с загрузкой второй стадии в кастомном загрузчике с использованием FAT12
Введение
При разработке операционной системы и написании кастомного загрузчика на ассемблере часто возникают проблемы, касающиеся загрузки второго сектора, особенно при работе с файловыми системами, такими как FAT12. В данной статье мы рассмотрим основные аспекты, которые могут вызвать затруднения, а также предоставим рекомендации по их устранению.
Описание проблемы
Пользователь столкнулся с проблемой, при которой BIOS не загружает вторую стадию (KRNLDR.SYS
), однако не получает никаких сообщений об ошибках. Этот этап важен, так как именно второй загрузчик отвечает за дальнейшую инициализацию системы.
Возможные причины
-
Ошибки в структуре образа диска:
- Убедитесь, что образ имеет правильную структуру и соответствует спецификациям FAT12. Ваша команда для создания образа с помощью
dd
иmcopy
может не копировать файлKRNLDR.SYS
корректно. - Проверьте, что загрузочный сектор действительно содержит корректный указатель на начальный кластер второго загрузчика.
- Убедитесь, что образ имеет правильную структуру и соответствует спецификациям FAT12. Ваша команда для создания образа с помощью
-
Ошибки в загрузочном коде:
- Код первой стадии должен корректно находить и загружать файл второго загрузчика. Проверьте, что правильные параметры передаются в функции, отвечающие за чтение с диска.
-
Ошибки в логике работы с FAT12:
- Ваша логика поиска и загрузки второго загрузчика (кластерный номер) может быть неправильной. Убедитесь, что вы правильно интерпретируете значения из структуры FAT12.
-
Системные прерывания и вызовы:
- Убедитесь, что все системные прерывания и обращения к BIOS выполняются корректно. Неправильные вызовы могут привести к ожиданию, которое выглядит как "повисание" программы.
Шаги по устранению проблемы
-
Проверка структуры образа:
- Откройте образ диска в hex-редакторе и проверьте наличие записи FAT12 и корневого каталога. Убедитесь, что
KRNLDR.SYS
присутствует.
- Откройте образ диска в hex-редакторе и проверьте наличие записи FAT12 и корневого каталога. Убедитесь, что
-
Обновление алгоритма загрузки в первой стадии:
- Проверьте функцию
ReadSectors
. Убедитесь, что она возвращает полноценные данные, и в случае ошибок производится корректная обработка.
- Проверьте функцию
-
Студийный отладочный вывод:
- Добавьте в код строку, которая позволяет выводить состояние программы перед этапом загрузки второго загрузчика. Это поможет диагностировать, где именно происходит сбой.
-
Документация и спецификации:
- Ознакомьтесь с документацией по FAT12. Убедитесь, что ваш подход к вычислению LBA и CHS корректен.
Заключение
Загрузчик первой стадии играет критическую роль в загрузке операционной системы. Осознание структуры файловой системы FAT12 и корректное чтение данных – важные аспекты, которые необходимо учитывать при разработке таких систем. Следуя приведенным рекомендациям, вы сможете найти и устранить причины, по которым вторая стадия загрузчика не загружается. Убедитесь, что вся логика работы с FAT12 правильно реализована и образ диска корректно сконстурирован.