с использованием языка ассемблера на голом железе

Вы когда-нибудь задумывались, что происходит, когда вы нажимаете кнопку питания на своем ПК? Что происходит в тот момент, когда на вашу материнскую плату подается электричество? Как ваше устройство стало загрузочным?

Я имел. И я собираюсь поделиться с вами этими знаниями вместе с кодом загрузчика, который вы можете запустить через эмулятор 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, Киев, Украина.

Подписывайтесь на меня в Medium, Twitter, Facebook.