Пользовательский загрузчик не загружает второй сектор при использовании FAT12.

Вопрос или проблема

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

Возможные причины

  1. Ошибки в структуре образа диска:

    • Убедитесь, что образ имеет правильную структуру и соответствует спецификациям FAT12. Ваша команда для создания образа с помощью dd и mcopy может не копировать файл KRNLDR.SYS корректно.
    • Проверьте, что загрузочный сектор действительно содержит корректный указатель на начальный кластер второго загрузчика.
  2. Ошибки в загрузочном коде:

    • Код первой стадии должен корректно находить и загружать файл второго загрузчика. Проверьте, что правильные параметры передаются в функции, отвечающие за чтение с диска.
  3. Ошибки в логике работы с FAT12:

    • Ваша логика поиска и загрузки второго загрузчика (кластерный номер) может быть неправильной. Убедитесь, что вы правильно интерпретируете значения из структуры FAT12.
  4. Системные прерывания и вызовы:

    • Убедитесь, что все системные прерывания и обращения к BIOS выполняются корректно. Неправильные вызовы могут привести к ожиданию, которое выглядит как "повисание" программы.

Шаги по устранению проблемы

  1. Проверка структуры образа:

    • Откройте образ диска в hex-редакторе и проверьте наличие записи FAT12 и корневого каталога. Убедитесь, что KRNLDR.SYS присутствует.
  2. Обновление алгоритма загрузки в первой стадии:

    • Проверьте функцию ReadSectors. Убедитесь, что она возвращает полноценные данные, и в случае ошибок производится корректная обработка.
  3. Студийный отладочный вывод:

    • Добавьте в код строку, которая позволяет выводить состояние программы перед этапом загрузки второго загрузчика. Это поможет диагностировать, где именно происходит сбой.
  4. Документация и спецификации:

    • Ознакомьтесь с документацией по FAT12. Убедитесь, что ваш подход к вычислению LBA и CHS корректен.

Заключение

Загрузчик первой стадии играет критическую роль в загрузке операционной системы. Осознание структуры файловой системы FAT12 и корректное чтение данных – важные аспекты, которые необходимо учитывать при разработке таких систем. Следуя приведенным рекомендациям, вы сможете найти и устранить причины, по которым вторая стадия загрузчика не загружается. Убедитесь, что вся логика работы с FAT12 правильно реализована и образ диска корректно сконстурирован.

Оцените материал
Добавить комментарий

Капча загружается...