Пишем свой начальный загрузчик bootloader

Пишем свой начальный загрузчик bootloader

Сообщение nezabudka » 08 окт 2016, 13:34

Предыдущую часть смотрите здесь
Сегодня мы напишим свой собственный начальный загрузчик
Который помимо вывода строк на монитор будет предоставлять
нам возможность выбора из списка. Я не стала эспериментировать
со свистоперделками, цветом и другой доступной из биоса
хренью, а ограничилась тем что нам может действительно
в дальнейшем пригодится. Практически в коде у нас получился
обычный переключатель switch(). Если вы знакомы с меню
загрузчика freebsd то наш результат будет выглядеть похоже.
Основными кирпичиками для работы с биосом мы определили функции
вывода символа на монитор и задержки, аналогичной тому что нам
предоставляет команда sleep в баше. Алгоритм работы нашего
загрузчика следующий:
Загрузчик начинает свою работу и через команду
прерывания int $0x10 отправляет для вывода на монитор
символ за символом в интервале 0,05 секунды пока на мониторе
полностью не отобразится меню загрузчика определенное
в переменной menu.
Интервал между выводами знаков мы так же передаем
биосу посредством прерывания int $0x15. Далее мы отправляем
биосу сигнал ожидания ввода символа с клавиатуры int $0x16.
При нажатии любой клавиши наш код определит и преключит
выполнение по одному из трех сценариев. Первый сценарий
осуществится если мы нажмем единицу, второй - двойку и
третий - дефолтный если мы нажмем любую другую отличную
от первых двух клавиш. Вот код нашего загрузчика:
[spoiler]
Код: выделить все
.code16 #шеснадцати битный режим

.section .text #только одна секция
.globl _start;
#точка входа в программу
_start:
   jmp _boot #прыгаем на метку _boot: в код выполнения

welcome:   .asciz "Hello, World\n\r"  #here we define the string
goodby:      .asciz "This is normal level\r\nreboot will over 3 seconds\r\n"
menu:      .asciz "choose level download:\r\n1 - nomal boot\r\n2 - fast reboot\r\n"
mistake:   .asciz "Mistake, try again\r\nreboot now"

.macro mWait str
   mov $0x86,   %ah
   mov \str,   %cx
   int $0x15
.endm

.macro mWritestr str
   lea   \str,   %si
   call Writestr
.endm

.func
Writestr:
1:   lodsb
   orb   %al,   %al #проверяем на ноль, определяем конец строки.
   jz   1f
   movb   $0xe,   %ah
   int   $0x10

#   mWait $0x1
   mov $0x86,   %ah
   mov $0x0,   %cx
   mov $0x5000,   %dx
   int $0x15

   jmp   1b
1:   ret
.endfunc

_boot:
   mWritestr welcome
   mWait $0x5
   mWritestr menu

   mov   $0x00,   %ah
   int   $0x16

   cmp   $0x02,   %ah
   je   Exit

   cmp   $0x03,   %ah
   je   reboot

   mWritestr mistake   

   mWait $0x10
   jmp reboot

Exit:
   mWritestr goodby
   mWait $0x30

reboot:
   ljmp   $0xffff, $0x0000

#Заполняем оставшееся место до сигнатуры нулями
.fill   (510 - (. - _start)), 1, 0
.word 0xaa55
[/spoiler]
Функцию вывода на монитор единичного символа мы обернули
в цикл и при помощи команды lods организовали конвеерную
итерацию по перебору всего массива строки. Выход из цикла
осуществляется автоматически когда закончится строка и в
регистре %al появится занчение '\0'

Так как функции в asm вызываются без параметров
а нам просто необходимо задавать ей разные адреса
строк в регистр %si то вместо того что бы перед каждым
вызовом этой функции мы заносили адрес переменной
в регистр (lea var, %si),
мы скомбинируем функцию с макросом в котором мы можем
задавать параметры. Тоесть мы вызываем определенный макрос
с параметром а он в свою очередь воспользуется услугами
функции. В результате мы уложимся всего одной командой.
В нашей программе определены два макроса. Один вполне
самодостаточный, строки 14-18, посылает биосу сигнал
на выполнение задержки определенный параметром. У нас имеются
задержки между выводом строк в 0,5 секунды и в 3 секунды
перед перезагрузкой. Все хорошо видно в коде программы.
Так как я посчитала что интервал между выводом знаков
строчного массива определенного в макросе минимально
возможной величино в 0,1 секунды это слишком много то
в функции вывода строки я не использовала макрос задержки
а написала самостоятельный код и определила в нем
интервал в 0,05 секунды. Строки 34-37. На самом деле при
вызове задержки интервал определяется в двух регистрах,
значения старших разрядов числа заносятся в регистр %cx,
а младшие разряды в регистр %dx. Одно общее значение из этих
двух регистров %cx:%dx и определяет интевал в микросекундах.
Тоесть поместив в регистр %cx ноль а в регистр %dx 5000
мы определили задержку как раз в 0,05 секунды. В макросе
мы ограничились только регисром %cx у которого нижний предел
начинается с 0,1 секунды, нас это устраивает, а %dx в этом
случае мы обнуляем что бы при вызове макроса не приходилось
указывать два параметра. Собираем наш код как описано в
предыдущей статье и запускаем в гипервизоре qemu-kvm.
Код: выделить все
as -o bootloader.o bootloader.s
ld -Ttext 0x7c00 --oformat=binary -o bootloader.bin bootloader.o
qemu-kvm -fda bootloader.bin -boot a

454
Начальный загрузчик у нас получился простенький, без излишеств,
но в то же время достаточно симпатичный и функциональный.
В следующей статье мы научимся отлаживать наш код и настроим для
этого подходящий инструментарий.
"I invented the term Object-Oriented and I can tell you I did not have C++ in mind." - Alan Kay
Аватар пользователя
nezabudka
Местный говорун
Местный говорун
 
Автор темы
Сообщений: 618
Фото: 180
Стаж: 4 года 1 месяц 2 дня
Откуда: Ростов на Дону
Благодарил (а): 287 раз.
Поблагодарили: 147 раз.

Пишем свой начальный загрузчик bootloader

Спонсор

Спонсор
 

Re: Пишем свой начальный загрузчик bootloader

Сообщение Olej » 08 окт 2016, 20:00

/
nezabudka писал(а):

Загрузчик начинает свою работу и через команду
прерывания int $0x10 отправляет для вывода на монитор
символ за символом в интервале 0,05 секунды пока на мониторе
полностью не отобразится меню загрузчика определенное
в переменной menu.

Если вы описываете это упражнение не только для себя, а ещё для кого-то, кто будет это читать, то нужно предупреждать:
- всё это будет работать только в реальном аппаратном режиме процессора x86 (с адресацией только до 640Kb RAM) ...
- который (режим) повторяет работу процессоров 8086/8088 - времени ... ранних 90-х
- все же программы в операционных системах в Windows, Linux, FreeBSD и во всех других операционных системах, когда они пишутся и на ассемблере - используют совсем другую мнемонику (некоторых) команд и схему адресации - защищёного режима (в который процессор переключается аппаратно)
- ... поэтому ассемблерный код реального режима невыполним в защищённом и наоборот
- (современные ОС в процессе загрузки переключает процессор из реального режима в защищённый)
- но в процессорах x86 существует, кроме реального и защищённого режимов, ещё один - иногда его называют нереальный, когда в режиме подобного реальному адресуется 4Gb памяти как в защищённом.

Зачем я сделал такое дополнение?
1. для общей эрудиции, тех кто заинтересуется тем, что в x86 (по недосмотру Intel) есть не 2, а 3 режима адресации.
2. очень интересно было бы все ваши эксперименты выполнять переведя процессор в нереальный режим адресации.
Olej
 
Стаж: 49 лет 4 месяца 19 дней

Re: Пишем свой начальный загрузчик bootloader

Сообщение nezabudka » 08 окт 2016, 20:50

Olej Сперва повторюсь, это немного расширенный обзор по следам серии статей где и описано
все в подробных деталях. Здесь я хочу дополнить что уже опубликованно немного под другим взглядом.
Если выберите в интернете первый десяток публикаций по написанию начального загрузчика то
обнаружите копию своего текста. Думаю именно мой вариан будет неплохим дополнением к уже существующим. В любом случае большое спасибо за коментарии и может кому то будет лень ходить
в гости.
"I invented the term Object-Oriented and I can tell you I did not have C++ in mind." - Alan Kay
Аватар пользователя
nezabudka
Местный говорун
Местный говорун
 
Автор темы
Сообщений: 618
Фото: 180
Стаж: 4 года 1 месяц 2 дня
Откуда: Ростов на Дону
Благодарил (а): 287 раз.
Поблагодарили: 147 раз.


Вернуться в Другие языки

Кто сейчас на форуме

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 0

cron