Загрузчик не переходит на код ядра
Я пишу небольшую операционную систему - для практики. Я начал с загрузчика.
Я хочу создать небольшую командную систему, которая работает в 16-битном реальном режиме (пока).
Я создал загрузчик, который сбрасывает диск, а затем загружает сектор после загрузчика.
Проблема в том, что после jmp
функция ничего на самом деле не происходит.
Я не пытаюсь загрузить следующий сектор в 0x7E00 (я не совсем уверен, как указать адрес, используя es: bx, чтобы это могло быть проблемой, я считаю, что его Address:offset), сразу после загрузчика.
Это код:
;
; 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+ CPU. Избегайте их, если вы намерены нацелиться на 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
(СС). Затем я устанавливаю указатель стека (SP) на 0
, Когда вы помещаете что-либо в стек в 16-разрядном реальном режиме, процессор сначала уменьшает указатель стека на 2, а затем помещает 16-разрядное СЛОВО в это место. Таким образом, первый толчок в стек будет при 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). В синтаксисе NASM JMP
будет выглядеть так:
jmp 0x07E0:0x0000
Некоторые (т.е. MASM/MASM32) ассемблеры не имеют прямой поддержки для кодирования FAR Jmp, поэтому один из способов сделать это можно вручную, вот так:
db 0x0ea ; Far Jump instruction
dw 0x0000 ; Offset
dw 0x07E0 ; Segment
При использовании ассемблера GNU это будет выглядеть так:
ljmpw $0x07E0,$0x0000