с использованием языка ассемблера на голом железе
Вы когда-нибудь задумывались, что происходит, когда вы нажимаете кнопку питания на своем ПК? Что происходит в тот момент, когда на вашу материнскую плату подается электричество? Как ваше устройство стало загрузочным?
Я имел. И я собираюсь поделиться с вами этими знаниями вместе с кодом загрузчика, который вы можете запустить через эмулятор QEMU.
Загрузчик && голый металл
Прежде чем мы углубимся, несколько слов о загрузчике и bare-metal, о том, что именно мы собираемся здесь реализовать.
Загрузчик - это программа, которая загружает операционную систему (обычно, хотя загрузчик может использоваться для других целей). Он загружается в оперативную память из постоянной памяти, такой как жесткий диск или что-то еще.
Bare-metal означает программирование без оборудования. Мы не собираемся использовать какие-либо уровни абстракции, такие как загрузчик GRUB, язык C или операционная система (на этом этапе у нас ее нет). Мы будем использовать ассемблер (nasm compiler) и все. Мы собираемся взаимодействовать с системой на аппаратном уровне.
Хотя здесь мы применяем упрощения и собираемся реализовать простое «Hello, World!» печать. Этого будет достаточно для понимания принципов. Нажать на кнопку!
BIOS
Все начинается здесь - BIOS (Базовая система ввода / вывода). Позвольте мне скопировать объяснение из Википедии:
Для компьютеров, совместимых с IBM PC, BIOS - это энергонезависимая микропрограмма, используемая для выполнения аппаратной инициализации во время процесса загрузки (запуск при включении) и для предоставления сервисов времени выполнения для операционных систем и программ. Прошивка BIOS предустановлена на системной плате персонального компьютера, и это первое программное обеспечение, запускаемое при включении.
Что это значит для нас как разработчиков «загрузчиков»?
Это означает, что у нас уже есть какое-то программное обеспечение на нашем ПК, которое в первую очередь работает, и нам нужно как-то с ним интегрироваться. Итак, давайте начнем с того, что узнаем, что там происходит, нажав кнопку питания (конечно же, рассказ).
Вы нажали кнопку питания…
Светодиод на вашем компьютере начинает мигать…
BIOS готовится к вызову процедуры POST…
POST означает Power-On-Self-Test, и цель этой процедуры проста - проверить, все ли работает правильно. Бьюсь об заклад, вы все видели это хотя бы раз в жизни:
Самое интересное здесь то, к чему ведет нас эта последовательность. Эта последовательность процедур POST завершается поиском загрузочного устройства, такого как дискета, компакт-диск, жесткий диск или USB-накопитель, что угодно.
Загрузочное устройство
Как BIOS распознает устройство как загрузочное?
Оказывается, с помощью магических чисел. И эти числа - 0x55
и 0xAA
, или 85 и 170 в десятичной системе соответственно. Кроме того, эти магические числа должны находиться точно в байтах 511 и 512 нашего загрузочного устройства.
Вы уже получили это, эти магические числа - просто маркеры для BIOS, которые помогают идентифицировать загрузочные устройства от других устройств.
Когда BIOS находит такой загрузочный сектор, он загружается в память по определенному адресу - 0x0000:0x7C00
.
Вот картина, вот в чем дело. Мы знаем, где нам нужно сохранить программу, чтобы ее можно было загрузить с помощью BIOS в оперативную память.
Напишем код!
Подготовка окружающей среды
Я не хочу вас обременять, но прежде чем писать какой-то код, вы обязательно должны создать для этого среду. Я использую MacOS, поэтому приведенные ниже инструкции предназначены для MacOS.
Для тестирования нашего загрузчика вам потребуется Сборочный компилятор - nasm и эмулятор - QEMU. Мы можем установить их через brew:
brew install nasm brew install qemu
Это все, что нам нужно. А сейчас давайте напишем код!
Подпись загрузки
Создайте файл boot.asm
в папке тестирования, где вы собираетесь поиграть с ним. Самый простой загрузчик со своей подписью, чтобы BIOS мог его найти, будет выглядеть так:
; Our code jmp $ ; Magic numbers times 510 - ($ - $$) db 0 dw 0xAA55
Почему?
Помните два обязательных правила для обозначения вашего устройства как загрузочного:
- Магические числа -
0x55
и0xAA
; - Сохраните их в 511 и 512 байтах в нашем загрузочном секторе;
dw 0xAA55
записывает наши магические числа, а times 510 — ($ — $$) db 0
гарантирует, что они будут записаны точно в 511 и 512 байтах. Как?
dw
означает «запись данных», так что это просто глупая запись 2 байтов, интереснее с командой times
.
Мы знаем, что этот загрузочный сектор должен быть:
- 512 байт размером;
- 511 и 512 байтов должны быть
0x55
и0xAA
;
Исходя из этого, мы можем составить математическую формулу, вычислить, сколько нулей нам нужно написать после нашего кода, чтобы магические числа были в правильном месте: 510 — CURRENT_ADDRESS — START_ADDRESS
.
Для примера предположим, что у нас есть 100 байтов нашего кода, 2 байта магических чисел. Основываясь на приведенной выше формуле, нам нужно записать 410 байтов нулей после нашего кода, поэтому магические числа будут записаны в 511 и 512 байтах. Так работает команда times 510 — ($ — $$) db 0
.
Давай запустим:
- Скомпилируйте наш сборочный файл
boot.asm
через nasm, выполнив команду:nasm boot.asm -f bin -o boot.bin
; - Запустите скомпилированный бинарный файл через QEMU:
qemu-system-i386 -fda boot.bin
;
Поскольку сейчас у нас есть только одна команда: jmp $
, мы здесь ничего не делаем, только бесконечный цикл. Вот почему он останавливается на Booting from Floppy...
шаге. Давайте добавим здесь действие - напечатаем сообщение «Hello, World».
"Привет, мир"
Поскольку у нас есть boot.asm
файл с магическими числами, давайте изменим его так, чтобы он напечатал «Hello, World». Я буду добавлять к каждой команде комментарии, чтобы вы могли понять, что именно делает команда:
Скомпилируйте этот код nasm boot.asm -f bin -o boot.bin
и запустите qemu-system-i386 -fda boot.bin
.
Как видите, у нас есть «Hello, World!» сообщение в загрузочном секторе.
Цель достигнута!
Бонус
Если вы самый ленивый человек в мире, я сделал сценарий, который вы можете использовать для запуска на своем Mac с помощью одной команды:
curl https://gist.githubusercontent.com/ghaiklor/552d7f9c6c11e0c756ad305e55a0fff0/raw/cacfc2b3a84b84cc07d56e24e197ec51dc5d5133/hello-world-bootloader.sh | bash
Просто скопируйте приведенную выше команду и запустите ее в своем терминале. Я тоже довольно ленив, поэтому никаких проверок не проводил, только линейное выполнение. Итак, на вашем Mac должны быть установлены команды brew
и curl
.
Спасибо
Оставляйте свои мысли в комментариях, хотели бы вы узнать об этом больше, или, может быть, вы заметили некоторые ошибки, которых не заметил я. Я буду рад обсудить что-нибудь со всеми вами.
Если вас интересуют исходники моей простой операционной системы, вы можете найти их здесь - github.com/ghaiklor/ghaiklor-os-gcc.
Евгений Обрезков, старший инженер-программист, elastic.io, Киев, Украина.