MBRЭтой статьей я хочу помочь всем, кто начинает разбираться с созданием операционных систем и около того, а именно с написанием MBR. В этой статье я постараюсь рассказать о создании MBR.bin на практике, и вообще описать все это как можно более подробно и интересно. Ну и писать мы, конечно же, будем на FASM'е. О загрузчиках загрузчиков Загрузка системы начинается с того, что BIOS после успешного окончания процедуры POST считывает первый физический сектор жесткого диска, размещает его в памяти по адресу 0000:7С00h и передает сюда управление. Этот сектор называется Главной Загрузочной Записью (Master Boot Record - сокращенно MBR). В начале MBR расположен машинный код загрузчика, за ним идет Таблица Разделов (Partition Table), описывающая схему разбиения логических дисков. В конце загрузочного сектора находится сигнатура 55AAh, говорящая BIOS'у о том, что это действительно MBR, а не что-то еще. Загрузчик должен проанализировать Таблицу Разделов, найти предпочтительный логический диск, считать его первый сектор (он называется загрузочным - boot) и передать ему управление. Можно сказать, что он загружает загрузчик ОС.
Структура MBR Рассмотрим таблицу разделов. Она представляет собой 64 байтную структуру, в свою очередь разделенную на 4 по 16 байт каждая. Каждая из этих структур описывает отдельный раздел. Но это конечно не означает что на диске может быть до 4 разделов. Каждый раздел может указывать на целую цепочку разделов. Такие разделы называются расширенными.
Структура для описания раздела Начнем писать... Со структурой MBR разобрались, теперь приступим к формированию 512 байтного бинарника. Первое, что нам нужно сделать - написать код загрузчика. Потом мы должны составить таблицу разделов диска и добавить сигнатуру 55AAh. Сейчас мы попробуем написать простой загрузчик, выводящий сообщения об этапах своей работы. Итак, вот наш MBR:
Рассмотрим теперь этот код. Так как BIOS загружает MBR по адресу 0000:7C00h то необходимо поставить org 7C00h, чтобы компилятор правильно выставлял смещение при использовании меток и символов $ (текущее смещение кода) и $$ (адрес в памяти с которого начинается код). Загрузчик выполняется в реальном (16-разрядном) режиме, значит ставим use16, чтобы генерировать 16-разрядный код. Сначала нужно настроить сегменты кода, данных и стека. Данные и стек у нас будут располагаться в одном сегменте с кодом. Установим стек на 256 байт, нам этого вполне хватит. Для этого установим sp на 768 (300h) байт после начала кода (на 256 от конца), так как стек растет вниз. Далее мы функцией 00h прерывания int 10h устанавливаем текстовый режим. В al должен находится номер режима. У нас это самый первый текстовый режим 00h.
Режимы int 10h Далее мы используем две своих функции, которые разберем позднее - вывод строки и ожидание нажатия клавиши. Выводим строку мы функцией BIOSprintstring. Просто передаем ей смещение строки (word), длину строки (word), X строки в символах (byte) и Y строки в символах (byte). Так как класть в стек byte нельзя, а X и Y - это byte, то обьединяем их в word. Для нашей функции удобно в стек положить word, где старший байт - Y, а младший - X. После вывода строки подождем пока пользователь нажмет Enter. Для этого используем функцию BIOSreadkey. В нее мы передаем ASCII код Enter'a - 0Dh. Теперь, как загрузчик мы должны считать с диска в память boot sector. Делаем это функцией 42h прерывания int 13h. В dl должен находится номер диска для чтения 80h - первый диск, 81h - второй и так далее. В si мы должны положить смещение структуры для LBA чтения. Ее мы составим позднее.
Структура для LBA чтения Если чтение по какой то причине не удалось BIOS установит флаг переноса. В этом случае прыгаем на error, которая выведет сообщение об ошибке на 2й строке (на 1й - 'MBR loaded') и повесит машину. Если чтение успешно - проверяем загружен ли boot sector в память. Для этого мы как и BIOS проверим последние 2 байта считанного. Пусть это будет 66FFh. Считываем два байта (а считывать мы будем в память по адресу 8600h) в si по смещению 7FEh (пусть boot sector занимает 2 килобайта) от начала boot sector'а и сравниваем их с FF66h (байты считываются в обратном порядке). Если байты совпадут, то выводим сообщение, что boot sector загружен на 2й строке (на 1й - 'MBR loaded') и переходим на его выпонение командой jmp. Если нет, то прыгаем на error, которая выводит сообщение об ошибке и уходит в бесконечный цикл. Далее идет код функций, которые выводят сообщения о нахождении boot sector'а и об ошибке. Впринципе их работа уже описана выше, нужно только сказать, что они тоже работают через BIOSprintstring. На этом основной исполняемый код MBR заканчивается. Но это еще не все. Мы же еще не рассмотрели функции BIOSprintstring и BIOSreadkey, а также структуру для LBA чтения. Следующая строка по коду - include 'BIOSFunctions.ink', значит пора рассмотреть BIOSreadkey и BIOSprintstring которые, как видно из названия используют прерывания BIOS. Чтобы сэкономить место (512 байт не резиновые) вынесем часто используемые прерывания в функции, которые в свою очередь поместим в .ink файл, который подключим в конце нашего MBR. Вот как у нас будет выглядеть файл BIOSFunctions.ink:
Здесь описаны функции, используемые в нашем загрузчике. Начнем с BIOSprintstring. Она извлекает переданный код клавиши из стека, сравнивает его со считанным с клавиатуры и если они совпадут возвращает управление вызвавшему коду, если нет - повторяет эти действия снова. Итак, функция BIOSreadkey ждет нажатия клавиши, переданной параметром функции или, проще говоря, через стек. Но так как передавать через стек byte нельзя, передаем word, а потом читаем байт по адресу sp+02h. Думаю стоит обьяснить почему именно sp+02h. В первых двух байтах (так как код 16-и разрядный) у нас точка возврата из функции. Так как байты кладутся в память (а стек - это память) в обратном порядке, то переданный байт, который мы кладем в стек последним (передаем код клавиши как 00XXh) окажется спереди. Читаем символ с клавиатуры мы функцией 00h прерывания int 16h. После вызова прерывания в al будет помещен код нажатой клавиши. Теперь вывод строки - функция BIOSprintstring. Она получает смещение строки, длину строки, строку и столбец начала строки, выводит строку и возвращает управление вызвавшему коду. Здесь мы получаем данные из стека инструкцией pop. Поэтому сохраняем точку возврата. Сохраняем мы ее в регистр si, так как регистр bp занят для прерывания в функции. Строку выводим функцией 13h прерывания int 10h. В es:bp должен находится адрес выводимой строки. В cx кладем длину выводимой строки. В регистре dx должна быть позиция строки на экране. В dl - позиция по X, а в dh - по Y. В bx настроки отображения строки на экране. В bh - видеостраница, в bl - аттрибут строки. В al у нас код подфункции. Так как мы передаем только смещение, длину, X и Y строки, остальное заполняет сама функция. Здесь установлена нулевая видеостраница, белый цвет строки, перемещение курсора в конец строки.
Подфункции функции 13h прерывания int 10h
Аттрибут символа
Цвета текста (еще 8 цветов можно получить установив бит яркости) Функции рассмотрели - на этом код заканчивается. Остались только структуры данных. Сначала здесь идут выводимые MBR строки, здесь мы видим строки на английском языке, так как русских символов у нас нет следующего содержания: 'MBR загружен', Boot sector найден' и 'Ошибка'. Теперь рассмотрим нашу LBA структуру. Здесь указан размер структуры 10h байт, читать 4 сектора, то есть наш boot sector будет иметь размер 2 килобайта, считанное поместить в память по адресу 8600h и стартовый сектор для чтения - первый (MBR - нулевой). Далее идет директива times, которая указывает компилятору заполнить оставшееся (до 1BEh байт) место нулями. Количество нулевых байтов здесь равно 1BEh (то есть все место, отводимое по код загрузчика) минус разница в стартовом адресе программы в памяти и текущего смещения кода (то есть место, занятое кодом). Код загрузчика закончился официально. Заполняем таблицу разделов. Указываем, что данный раздел загрузочный, CHS адресацию мы использовать не будем - заполняем все ее поля FFh, код типа раздела устанавливаем 01h - важно, чтобы здесь был не ноль. И выставляем раздел от первого до произвольного сектора. Остальные 3 раздела заполним нулями директивой times. Все! Последние 2 байта указывают BIOS'у, что это действительно MBR. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||