Загрузочный загрузчик не переходит в код ядра

Я пишу небольшую операционную систему - для практики. Я начал с загрузчика.
Я хочу создать небольшую систему команд, которая работает в реальном режиме 16 бит (на данный момент).
Я создал загрузчик, который сбрасывает диск, а затем загружает сектор после загрузчика.
Проблема в том, что после jmp ничего не происходит.

Я пытаюсь загрузить следующий сектор на 0x7E00 (я не совсем уверен, как указать адрес с помощью es: bx, так что это может быть проблемой, я считаю, что его адрес: смещение) сразу после загрузчика.

Это код:

;
; SECTOR 0x0
;

;dl is number of harddrive where is bootloader
org 0x7C00
bits 16

;reset hard drive
xor ah,ah
int 0x13
;read sectors
clc
mov bx,0x7E00
mov es,bx
xor bx,bx
mov ah,0x02 ;function
mov al,0x1  ;sectors to read
mov ch,0x0  ;tracks
mov cl,0x1  ;sector
mov dh,0x0  ;head
int 0x13
;if not readed jmp to error
jc error
;jump to 0x7E00 - executed only if loaded
jmp 0x7E00
error:
    mov si,MSGError
    .loop:
        lodsb
        or al,al
        jz .end
        mov ah,0x0E
        int 0x10
        jmp .loop
    .end:
        hlt
MSGError db "Error while booting", 0x0
times 0x1FE - ($ - $$) db 0x0
db 0x55
db 0xAA

;
; SECTOR 0x1
;

jmp printtest
;definitions
MSGLoaded db "Execution successful", 0x0
;
; Print function
; si - message to pring (NEED TO BE FINISHED WITH 0x0)

printtest:
    mov si,MSGLoaded
    .loop:
        lodsb
        or al,al
        jz .end
        mov ah,0x0E
        int 0x10
        jmp .loop
    .end:
        hlt

times 0x400 - ($-$$) db 0x0

Я тестировал этот код с помощью VirtualBox, но ничего не происходит, ошибка чтения не отображается, а также сообщение, которое должно быть напечатано.

Ответ 1

Основные проблемы с этим кодом:

  • ES: BX указывал на неправильный сегмент: смещение для загрузки ядра в
  • Неправильный сектор загружался, поэтому ядро ​​не было тем, что ожидалось

Первый в этом коде:

mov bx,0x7E00
mov es,bx
xor bx,bx

Вопрос хочет загрузить сектор с диска на 0x0000:0x7E00 (ES: BX). Этот код устанавливает ES: BX в 0x7E00:0x0000, который разрешает физический адрес 0x7E000 ((0x7E00 < 4) + 0x0000). Я думаю, что целью было загрузить 0x07E0 в ES, который даст физический адрес 0x7E00 ((0x07E0 < 4) + 0x0000). Вы можете узнать больше о вычислениях адресации памяти 16:16 здесь. Умножение сегмента на 16 - это то же самое, что и сдвиг влево 4 бит.

Вторая проблема в коде:

mov ah,0x02 ;function
mov al,0x1  ;sectors to read
mov ch,0x0  ;tracks
mov cl,0x2  ;sector number
mov dh,0x0  ;head
int 0x13

Число для второго сектора 512 блоков на диске равно 2, а не 1. Поэтому, чтобы исправить вышеуказанный код, вам нужно соответственно установить CL:

mov cl,0x2  ;sector number

Общие советы по разработке загрузчика

Другие проблемы, которые могут вызвать запуск кода на различных эмуляторах, виртуальных машинах и реальном физическом оборудовании, которые должны быть устранены, следующие:

  • Когда BIOS переходит на ваш код, вы не можете полагаться на регистры CS, DS, ES, SS, SP, имеющие действительные или ожидаемые значения. Они должны быть настроены надлежащим образом при запуске загрузчика. Вам может быть гарантировано, что ваш загрузчик будет загружен и запущен с физического адреса 0x00007c00 и что номер загрузочного диска будет загружен в регистр DL.
  • Установите SS: SP в память, которая, как вы знаете, не будет конфликтовать с работой вашего собственного кода. Возможно, BIOS разместил указатель стека по умолчанию в любом месте в первом мегабайте полезной и адресной ОЗУ. Существует никаких гарантий относительно того, где это и будет ли он подходит для кода, который вы пишете.
  • Флаг направления, используемый lodsb, movsb и т.д., может быть установлен или очищен. Если флаг направления установлен неправильно, регистры SI/DI могут быть отрегулированы в неправильном направлении. Используйте STD/CLD, чтобы установить его в желаемое направление (CLD = вперед /STD = назад). В этом случае код предполагает движение вперед, поэтому следует использовать CLD. Подробнее об этом можно узнать в справочной системе
  • При переходе на ядро, это, как правило, хорошая идея для FAR JMP, чтобы он правильно устанавливал CS: IP в ожидаемые значения. Это может избежать проблем с кодом ядра, которые могут делать абсолютные значения рядом с JMP и CALL в одном сегменте.
  • Если таргетинг на ваш загрузчик для 16-разрядного кода, который работает на процессорах 8086/8088 (И выше), избегайте использования 32-разрядных регистров в ассемблере. Используйте AX/BX/CX/DX/SI/DI/SP/BP вместо EAX/EBX/ECX/EDX/ESI/EDI/ESP/EBP. Хотя это не проблема в этом вопросе, это проблема для других, кто обращается за помощью. 32-битный процессор может использовать 32-битные регистры в 16-разрядном реальном режиме, но 8086/8088/80286 не может, поскольку они были 16-разрядными процессорами без доступа к расширенным 32-разрядным регистрам.
  • Регистры сегментов FS и GS добавлены к процессорам 80386+. Избегайте их, если вы намереваетесь настроить таргетинг на 8086/8088/80286.

Чтобы разрешить первый и второй элементы, этот код можно использовать рядом с началом загрузчика:

xor ax,ax      ; We want a segment of 0 for DS for this question
mov ds,ax      ;     Set AX to appropriate segment value for your situation
mov es,ax      ; In this case we'll default to ES=DS
mov bx,0x8000  ; Stack segment can be any usable memory

cli            ; Disable interrupts to circumvent bug on early 8088 CPUs
mov ss,bx      ; This places it with the top of the stack @ 0x80000.
mov sp,ax      ; Set SP=0 so the bottom of stack will be @ 0x8FFFF
sti            ; Re-enable interrupts

cld            ; Set the direction flag to be positive direction

Несколько вещей, чтобы отметить. Когда вы меняете значение регистра SS (в данном случае через MOV), процессор должен отключить прерывания для этой команды и отключить их до следующей инструкции. Обычно вам не нужно беспокоиться об отключении прерываний, если вы обновляете SS сразу же после обновления SP. Ошибка в очень ранних процессорах 8088, где это не было выполнено, поэтому, если вы ориентируетесь на самые широкие возможности, это безопасная ставка, чтобы явно отключить и повторно включить их. Если вы не собираетесь работать с ошибкой 8088, тогда инструкции CLI/STI могут быть удалены в приведенном выше коде. Я знаю об этой ошибке в первую очередь с работой, которую я сделал в середине 80-х на моем домашнем ПК.

Второе, что нужно отметить, это то, как я устанавливаю стек. Для людей, новавших в 1688-битную сборку 8088/8086, стек можно установить множеством способов. В этом случае я устанавливаю верхнюю часть стека (самая низкая часть в памяти) в 0x8000 (SS). Затем я устанавливаю указатель стека (SP) на 0. Когда вы нажмите что-нибудь в стекев 16-битном реальном режиме процессор сначала уменьшает указатель стека на 2, а затем помещает 16-битное WORD в этом месте. Таким образом, первое нажатие на стек будет 0x0000-2 = 0xFFFE (-2). Тогда у вас будет SS: SP, который выглядит как 0x8000:0xFFFE. В этом случае стек работает от 0x8000:0x0000 до 0x8000:0xFFFF.

При работе со стеком, работающим на 8086 (не относится к процессорам 80286,80386+), рекомендуется установить указатель стека (SP) на четное число. На исходном 8086, если вы установите SP на нечетное число, вы получите 4 штрафных цикла за каждый доступ к стеку. Поскольку 8088 имел 8-битную шину данных, этого штрафа не существовало, но загрузка 16-разрядного слова на 8086 заняла 4 такта, тогда как на 8088 (8 бит) читалось 8 тактов.

Наконец, если вы хотите явно установить CS: IP, чтобы CS была правильно установлена ​​к моменту завершения JMP (к вашему ядру), тогда рекомендуется сделать FAR JMP (см. Операции, которые влияют на регистры сегментов /FAR Jump). В синтаксисе NASM JMP будет выглядеть так:

jmp 0x07E0:0x0000

Некоторые (т.е. MASM/MASM32) ассемблеры не имеют прямой поддержки для кодирования FAR Jmp, поэтому один из способов, которым это может быть сделано, выглядит так:

db 0x0ea     ; Far Jump instruction
dw 0x0000    ; Offset
dw 0x07E0    ; Segment

Если вы используете GNU-сборщик, он выглядит так:

ljmpw $0x07E0,$0x0000