Как создать программу для BIOS: пошаговое руководство

Для написания программы для BIOS необходимо освоить язык ассемблера, так как BIOS работает на низком уровне и требует прямого взаимодействия с аппаратным обеспечением. Важно понимать архитектуру целевой платформы, а также спецификации самого BIOS, чтобы программный код мог корректно взаимодействовать с устройствами и обеспечивать загрузку операционной системы.

Кроме того, разработка BIOS требует использования специального инструмента для компиляции и отладки кода. Рекомендуется работать с симуляторами для тестирования, прежде чем загружать код на реальную плату, так как ошибки могут привести к неработоспособности системы. Специализированные учебные материалы и документация по BIOS также смогут значительно упростить процесс разработки.

Создание собственного UEFI загрузчика

Здравствуйте, Хабр! Мне 16 лет, я учусь на первом курсе колледжа по специальности программиста. В последнее время я заинтересовался низкоуровневым программированием с использованием Ассемблера и C/C++.

В какой-то момент я решил, что хочу создать свой простой загрузчик на ассемблере для саморазвития, который сможет загружать ядро, написанное на C, и на экран выводить что-то вроде «Привет, мир!». Я изучил множество статей на Хабре и других ресурсах на эту тему. После ряда ошибок я наконец добился успеха, и это принесло мне искреннюю радость.

Меня расстроило, что большинство таких статей сосредоточены на коде загрузчиков для устаревшего BIOS-MBR, которому уже много лет. В то же время, относительно недавно появился UEFI-GPT, и очевидно, что будущее именно за ним. Однако я не нашел на Хабре ни одной статьи, которая бы подробно описывала создание простого UEFI загрузчика для этой системы! Конечно, есть несколько авторов, которые писали об этом, но их очень мало, и информация, которую они предоставляют, кажется слишком сложной и не совсем доступной. Эта мысль подтолкнула меня к тому, чтобы разобраться в теме самостоятельно и создать данную статью.

Дисклеймер

Эта статья написана новичком для новичков. Я всего-навсего попытался самостоятельно разобраться в данной теме и поделится своим опытом тут, я НЕ дипломированный специалист, и НЕ хочу его из себя строить. По этому прошу простить мне возможные ошибки или неточности которые я мог допустить.

BIOS

BIOS представляет собой базовую систему ввода-вывода (Basic Input Output System). Эта низкоуровневая программа находится в чипе материнской платы компьютера.

BIOS запускается при включении компьютера и отвечает за пробуждение аппаратных компонентов, убеждается в том, что они правильно работают, после чего определяет загрузочное устройство.Как только BIOS определил загрузочное устройство, он считывает первый дисковой сектор этого устройства в память. Первый сектор диска — это главная загрузочная запись — Masted Boot Record (MBR) размером 512 байт. В MBR расположена программа‑загрузчик, которая уже в свою очередь запускает операционную систему.

Недостатки BIOS

BIOS существует на рынке довольно долго и практически не подвергался значительным изменениям. Он остался практически неизменным с момента своего создания, в отличие от многих других компьютерных технологий. В результате со временем стали возникать различные проблемы, среди которых:

  1. Ограничение на загрузку с дисков размером не более 2 ТБ
  2. Загрузчик не может превышать 512 байт
  3. BIOS функционирует в 16-битном режиме процессора и может использовать лишь 1 Мб памяти.
  4. Трудности с одновременной инициализацией нескольких устройств, что приводит к замедлению загрузки, когда происходит инициализация всех аппаратных интерфейсов и устройств.

UEFI

UEFI представляет собой унифицированный расширяемый интерфейс прошивки (Unified Extensible Firmware Interface) и является более современным решением по сравнению с BIOS. Он способен анализировать файловые системы и самостоятельно осуществлять загрузку файлов. В отличие от BIOS, UEFI не применяется для загрузки через MBR, вместо этого используется GPT.

Как происходит загрузка UEFI-загрузчиков?

UEFI идентифицирует диски с поддерживаемыми файловыми системами и ищет на них по адресу /EFI/BOOT/ файл с расширением .efi, который называется bootX.efi, где X обозначает платформу, для которой создан загрузчик. Вот и все.

GPT (GUID)

GPT — это более новый стандарт для определения структуры разделов на диске. Это часть стандарта UEFI, то есть систему на основе UEFI можно установить только на диск использующий GPT.GPT допускает создание неограниченного количества разделов, хотя некоторые операционные системы могут ограничивать их число 128 разделами. В GPT практически нет ограничения на размер раздела.

Что нам понадобится?

  1. Операционная система Linux (в моем случае Kali Linux, работающая на Virtual Box)
  2. Компилятор GCC
  3. GNU-EFI (инструкция по установке доступна на OSDev здесь)
  4. Умение программировать на языке Си
  5. QEMU (виртуальная машина для проведения тестов)

Начало

Сначала необходимо создать директорию для работы с названием gnu-efi-dir и перейти в неё:

mkdir gnu-efi-dir cd gnu-efi-dir

Затем установим и скомпилируем GNU-EFI:

git clone https://git.code.sf.net/p/gnu-efi/code gnu-efi cd gnu-efi make

Теперь пришло время написания самой программы. Создаём файл, я его назову boot.c и начинаем писать код! Для начала хватит загрузчика который ничего не загружает выводит на экран «Hello World!»

#include #include EFI_STATUS EFIAPI efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)

Сборка

Теперь необходимо собрать и слинковать все элементы, чтобы получить EFI файл. Для упрощения процесса я создал файл Makefile:

run: boot.o boot.so boot.efi make clean boot.o: gcc -I gnu-efi/inc -fpic -ffreestanding -fno-stack-protector -fno-stack-check -fshort-wchar -mno-red-zone -maccumulate-outgoing-args -c boot.c -o boot.o boot.so: ld -shared -Bsymbolic -L gnu-efi/x86_64/lib -L gnu-efi/x86_64/gnuefi -T gnu-efi/gnuefi/elf_x86_64_efi.lds gnu-efi/x86_64/gnuefi/crt0-efi-x86_64.o boot.o -o boot.so -lgnuefi -lefi boot.efi: objcopy -j .text -j .sdata -j .data -j .rodata -j .dynamic -j .dynsym -j .rel -j .rela -j .rel.* -j .rela.* -j .reloc —target efi-app-x86_64 —subsystem=10 boot.so boot.efi clean: rm *.o *.so

Теперь нам остаётся лишь написать команду make и мы получим итоговый файл boot.efi.

Подготовка к запуску

Как уже упоминалось ранее, для запуска нашего EFI-приложения мы воспользуемся виртуальной машиной QEMU. Также потребуется OVMF. Устанавливаем все необходимые компоненты:

sudo apt install qemu-kvm qemu sudo apt install ovmf

Нам понадобятся файлы OVMF_CODE.fd и OVMF_VARS-1024×768.fd. Их можно загрузить по следующей ссылке. Установим их с помощью wget в отдельную папку:

mkdir ovmf cd ovmf wget https://github.com/kholia/OSX-KVM/blob/master/OVMF_CODE.fd wget https://github.com/kholia/OSX-KVM/blob/master/OVMF_VARS-1024×768.fd

Сразу создадим ещё одну директорию build в которой будет собираться наше приложение:

mkdir build

Почти всё готово! Давайте создадим небольшой Python-скрипт под названием Build.py (он был взят из этой статьи), который организует необходимые директории в папке build, скопирует наш файл туда и запустит QEMU:

import argparse

import os

import shutil

import sys

import subprocess as sp

from pathlib import Path

ARCH = "x86_64"

TARGET = ARCH + "-none-efi"

CONFIG = "debug"

QEMU = "qemu-system-" + ARCH

WORKSPACE_DIR = Path(__file__).resolve().parents[0]

BUILD_DIR = WORKSPACE_DIR / "build"

OVMF_FW = WORKSPACE_DIR / "ovmf" / "OVMF_CODE.fd"

OVMF_VARS = WORKSPACE_DIR / "ovmf" / "OVMF_VARS-1024×768.fd"

def build():

boot_dir = BUILD_DIR / "EFI" / "BOOT"

boot_dir.mkdir(parents=True, exist_ok=True)

built_file = "boot.efi"

output_file = boot_dir / "BootX64.efi"

shutil.copy2(built_file, output_file)

startup_file = open(BUILD_DIR / "startup.nsh", "w")

startup_file.write("\EFI\BOOT\BOOTX64.EFI")

startup_file.close()

def run():

qemu_flags = [

"-nodefaults", # Отключение стандартных устройств

"-vga", "std", # Использовать стандартный VGA для графики

"-machine", "q35,accel=kvm:tcg", # Выбрать современную машину с возможностью ускорения

"-m", "128M", # Выделить память

"-drive", f"if=pflash,format=raw,readonly,file=",

"-drive", f"if=pflash,format=raw,file=",

"-drive", f"format=raw,file=fat:rw:", # Монтирование локальной директории как FAT-раздела

"-serial", "stdio", # Подключение последовательного порта к хосту

"-monitor", "vc:1024×768", # Настройка монитора

]

sp.run([QEMU] + qemu_flags).check_returncode()

def main():

if len(sys.argv) < 2:

print("Ошибка!

Unknown command.") print("Example: python3.11 Build.py [build/run]") return False if sys.argv[1] == "build": build() elif sys.argv[1] == "run": run() else: print("Error! Unknown command.") print("Example: python3.11 Build.py [build/run]") if __name__ == "__main__": main()

Запуск

Все подготовлено! Теперь собираем и запускаем наше приложение EFI:

python Build.py build python Build.py run

Пишем свой ROM BIOS

С давних времён у меня была мечта создать собственный ROM BIOS, который будет записан в отдельной микросхеме и выполнять некоторые полезные функции. К примеру, ROM BIOS присутствует в видеокартах (по крайней мере, в старых) и в контроллерах дисков, и это фактически расширение стандартного BIOS. Я осознаю, что это уже устарело и пытаться соперничать с современными возможностями UEFI нецелесообразно, но мне очень хотелось разработать свой уникальный образ, который будет физически находиться в отдельной микросхеме и функционировать при загрузке системы. Поэтому мне даже пришлось поискать старый компьютер для этих целей.

Когда я только решил влезть в этот вопрос, столкнулся с тем, что достаточно мало толковой информации, которая была бы хорошо и чётко изложена. Возможно, я плохо искал, примеров того, как писать свою программу в бутсектор жёсткого диска было много, а вот толкового мануала по созданию BIOS Extension — кот наплакал.

В данной статье мы обсудим процесс разработки собственного ROM BIOS. Я поделюсь с вами особенностями и нюансами этого процесса, а также продемонстрирую, как создать низкоуровневую программу «Hello world». Кроме того, мы создадим полноценный интерпретатор BASIC ROM, который будет запускаться при включении компьютера, как это было в первых IBM PC.

❯ Теория работы расширения BIOS

Думаю, все знают, что такое BIOS, многие заходили в его настройки при старте компьютера. Но, оказывается, есть возможность делать для него модули расширения и создавать там свой функционал.

Говоря об архитектуре IBM BIOS, стоит отметить, что она может быть дополнена отдельными модулями, и всё это будет функционировать как единое целое. Следовательно, в персональную компьютерную систему могут быть интегрированы дополнительные микросхемы ПЗУ, содержащие такие модули.

Многие из вас, вероятно, встречали подобные микросхемы на сетевых картах, но они Встречаются на видеокартах с VGA Video BIOS, основном системном BIOS, BIOS для IDE жёстких дисков и в ряде других компонентов.

Для того чтобы системный BIOS нашёл дополнительный модуль в памяти, есть специальная процедура, которая позволяет его найти. Во время самотестирования POST, после загрузки векторов прерывания в ОЗУ, резидентный код BIOS начинает проверять ПЗУ на наличие специальных байтов заголовка, которые помечают начало дополнительного модуля BIOS. Поиск этих заголовков идёт в диапазоне абсолютных адресов 0C8000h — 0F4000h .

  • Первоначальные два байта сигнализируют о начале секции: 0x55 и 0xAA.
  • Сразу после этих первых двух байтов заголовка располагается третий байт, который указывает на размер дополнительного BIOS-кода. Этот параметр обозначает количество блоков по 512 байт, необходимых для размещения данного кода. Это также подразумевает, что раздел кода расширения BIOS не может превышать следующей длины:

После обнаружения заголовка производится проверка, является ли последующий раздел действительным расширением BIOS. Для этого осуществляется проверка на целостность указанного количества блоков по 512 байт. Значения каждого байта в блоке складываются с применением операции по модулю 0x100 — это эквивалентно делению суммы всех байтов на 4096(d). Если остаток равен 0, это свидетельствует о том, что расширение BIOS содержит правильный код. Проще говоря, последний байт должен представлять собой контрольную сумму всех предыдущих байтов, которая в итоге дает ноль.

Как только заголовок идентифицирован, контрольная сумма проверена, то управление будет передано к четвёртому байту этого расширения, и начинается выполнение программы, содержащейся в этом расширении. Например, видеокарта инициализирует свои регистры, благодаря которым вы можете видеть что-либо на мониторе после включения. Или находят диски, подключённые к компу.

После выполнения кода управление снова возвращается к встроенному BIOS, и продолжается поиск дополнительных блоков BIOS в памяти. Этот этап завершится, когда будет достигнут абсолютный адрес 0F4000h, после чего начнётся загрузка с диска.

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

Например, в большинстве систем тех лет, есть три стандартных BIOS, которые, как правило, всегда расположены в одном и том же месте:

  • Системный BIOS: базовый системный BIOS хранится в области памяти объёмом 64 КБ, которая расположена в диапазоне от F0000h до FFFFFh.
  • Video BIOS VGA: этот BIOS необходим для настройки и управления видеокартой. Обычно он расположен в области размером 32 КБ в диапазоне от C0000h до C7FFFh и находится непосредственно на самой видеокарте.
  • BIOS для жёсткого диска IDE: этот BIOS отвечает за управление жёстким диском IDE, если он присутствует в системе, и находится от C8000h до CBFFFh. На старых материнских платах его можно найти на плате контроллера жёстких дисков, в современных системах — на материнской плате.

Изучение объема памяти BIOS настоящей видеокарты

Переходим по смещению C000h , что как раз соответствует физическому адресу C0000h . Можно сразу увидеть два «магических» числа заголовка: 55h и AAh , затем идёт число 7Ch , которое говорит нам, что это расширение занимает 63488 байта.

Из теоретической части следует, что для создания программы необходимо в начале кода установить заголовок, который включает специальные числа и размер кода в секторах, а в завершении кода произвести расчет контрольной суммы. Если с первой частью всё достаточно ясно, то сложность вызывает процесс добавления контрольной суммы.

В принципе, этой информации вполне достаточно, чтобы создать собственный BIOS.

❯ Пишем «Hello Wold» в расширении BIOS

Когда начинал искать информацию по теме, наткнулся на следующую PDF, которая и стала кладезем информации. Это какая-то презентация, как собирать BIOS-расширения. В конце неё даётся ссылка на репозиторий с прекрасным примером и описанием. Это замечательный пример того, для чего можно было создавать свои расширения в BIOS — защищённая загрузка на жёсткий диск с паролем.

Да, в эпоху UEFI все это утратило свою актуальность. Тем не менее, мы стремимся вникнуть в эту тему.

Попробовал это расширение, оно у меня высыпалось с ошибкой. Но я понял, что оно работает, потому что оно запустилось и напечатало ошибку. Значит можно взять код, который выводил ошибку и сделать из него «Hello World».

Упрощая этот пример, получаем настоящую версию «Привет мир». Разбирать код на ассемблере не буду, так как он достаточно понятен, но отмечу некоторые важные моменты. Весь код доступен в моём репозитории. Программа компилируется с помощью транслятора nasm, который, на мой взгляд, является самым удобным и простым для новичков. Он работает как в DOS, так и в Windows и Linux. Для повышения удобства сборки я подготовил Makefile. Давайте рассмотрим код hello.asm:

org 0 rom_size_multiple_of equ 512 bits 16 ; Заголовок PCI Expansion Rom ; ———————— db 0x55, 0xAA ; подпись db rom_size/512; начальный размер в 512-байтовых блоках entry_point: jmp start start:

Приступаем к работе с начальным смещением. Сначала мы устанавливаем определение define rom_size_multiple_of equ 512, которое указывает размер блока. Число 16 означает, что NASM должен создавать код для процессора, функционирующего в режиме 16 бит.

db 0x55, 0xAA — это заголовок, магические числа, которые обозначают начало область начала расширения BIOS. db rom_size/512 — это один байт, который определяет размер секторов нашей программы. Рассчитывается автоматически с помощью макроса, для этого в конце программы добавлен следующий макрос:

rom_end equ $-$$ rom_size equ (((rom_end-1)/rom_size_multiple_of)+1)*rom_size_multiple_of

Макрос rom_end определяет объём программы в байтах. Затем вычисляется размер памяти, который кратен блоку в 512 байт и сохраняется в макрос rom_size. Это значение затем используется для вычисления количества блоков в памяти. Надеюсь, это не вызвало у вас путаницы, здесь происходит простая рекурсивная трансляция.

После того как заголовок определён, идёт команда jmp start , которая осуществляет прыжок на начало исполнения программы. В конце кода программы, перед макросами расчёта её размеров, резервируется 1 байт для расчёта контрольной суммы:

db 0 ; резервируем минимум один байт для контрольной суммы

Для ввода-вывода применяется стандартное прерывание BIOS int 10h, описание которого можно найти в любом учебнике по ассемблеру, поэтому нет необходимости углубляться в эту тему. Многие студенты пишут подобные тексты в рамках лабораторных работ в университете.

Наиболее увлекательным является вычисление контрольной суммы, которое выполняется с помощью вспомогательной программы addchecksum.c. Исходные коды я тоже взял из проекта AHCI BIOS Security Extension. Этот код требует внимательного изучения, так как он интересен и полезен. Существует не так много работающих примеров, способных вычислять контрольные суммы для расширений BIOS.

. int main(int argc, char *argv[]) < . FILE *f=fopen(argv[1], "r+"); . fseek(f, 0, SEEK_END); int f_size=ftell(f); fseek(f, 0, SEEK_SET); unsigned char sum=0; int i; for(i=0;ifputc((0x100-sum)0xff, f); . >

С помощью перехода в конец файла fseek(f, 0, SEEK_END); мы получаем размер файла int f_size=ftell(f); . Далее с начала файла образа читаем побайтно и суммируем полученные значения в однобайтовой переменной sum (то есть отбрасывая бит переноса). После чего инвертируем полученное значение и записываем его в последний байт нашей программы fputc((0x100-sum)0xff, f); , где мы заранее зарезервировали нулевой байт данных. В результате, если просуммировать весь код программы, он должен быть равен нулю, вместе с нашей контрольной суммой (последний байт хранит отрицательное число контрольной суммы).

Можно выполнить сборку всего этого, используя следующие две команды:

gcc addchecksum.c -o addchecksum nasm hello.asm -fbin -o hello.rom ./addchecksum hello.rom rm hello.rom

Результатом всех операций, если вы всё сделали правильно, будет файл hello.rom . Осталось найти способ протестировать работоспособность этого бинарника.

❯ Способы отладки BIOS Extension

Наиболее явный и проблемный метод — это перепрошить микросхему ПЗУ с помощью программатора, установить её в специальный слот на плате ISA или PCI, затем вставить карту в компьютер, включить его и убедиться, что ничего не функционирует. Существуют также более сложные методы, такие как интеграция кода непосредственно в BIOS материнской платы, но это занимает много времени и требует значительных усилий. К счастью, есть более простые и эффективные альтернативы.

Уже много полезных решений было предложено для нас — например, виртуальные машины. В данном случае я использовал VirtualBox и qemu. Второй вариант быстрее и проще, а первый доступен для Windows. Поскольку я в основном работаю в wsl под Windows, это особенно актуально для меня. Да, я осознаю, что qemu также доступен для Windows, но такой у меня путь.

VirtualBox

Для того чтобы протестировать ROM BIOS Extension, я создал пустую виртуальную машину. Там не требуется даже подключать жёсткий диск, он не нужен. Далее необходимо подключить данный образ к виртуальной машине. В Windows делал следующим образом, запускаю PowerShell и выполняю:

cd "C:Program FilesOracleVirtualBox" .VBoxManage.exe setextradata testrom "VBoxInternal/Devices/pcbios/0/Config/LanBootRom" "c:tmphello.rom"

После этого можно легко изменять файл c:tmphello.rom, и всё будет функционировать. В Linux процесс аналогичен, команда такая же, только не нужно указывать полный путь, поскольку путь к VBoxManage уже добавлен в переменную PATH.

Копирую файл hello.rom в c:tmp и пробую запустить виртуальную машину.

Проверка ROM-образа в VirtualBox

Все функционирует прекрасно. Однако, на мой взгляд, qemu представляет собой более удобное и простое решение, без необходимости создавать виртуальные машины и указывать пути к образу. Кроме того, его можно запустить из любой директории.

Запуск BIOS в qemu

Протестировать в виртуальной машине qemu можно одной командой, прямо из папки с получившимся бинарником:

qemu-system-i386 -net none -option-rom hello.rom

  • -net none обозначает отсутствие сетевого подключения.
  • -option-rom — задает файл образа ROM.

Начинаем процесс тестирования приложения с помощью Makefile

Оба инструмента имеют право на жизнь, но мой выбор — это qemu, плюс там удобно будет ещё и посмотреть, что происходит с моей программой.

❯ Тестирование на реальном железе

Если вам показалось, что первая часть статьи была трудной, то на самом деле это была самая лёгкая часть проекта. Самая трудоёмкая задача, которая заняла у меня подавляющее количество времени, заключалась в запуске системы на реальном оборудовании.

На самом деле, существует лишь ограниченное количество способов установить свой собственный ROM в память ПК, и самый простой из них — это выбрать сетевую карту, имеющую специальный слот для установки ROM. Казалось бы, просто прошивай ПЗУ, ставь в слот и действуй, но, как это часто бывает, путь оказался сложным.

Попытка на 386 машине

Изначально планировал весь проект реализовать на 386 машине, железо которой мне было любезно предоставлено spiritus_sancti.

Материнская плата модели 386

Чтобы оживить его, пришлось пройти настоящий квест по разворачиванию DOS на Compact Flash, оказалась не такая простая задачка, с танцами. О чём подробно описал у себя в ЖЖ. Все эти мытарства были нужны для того, чтобы увидеть образ ПЗУ в памяти ЭВМ.

В качестве чипа для прошивки был выбран M2764, однако программатор долго не распознавал его и постоянно выдавал ошибки по выводам. Тем не менее, в конечном итоге он смог произвести прошивку.

Обновление программного обеспечения микросхемы

После прошивки вставил её в панельку сетевой карты и стал ждать чуда.

Чип ПЗУ M2764 на панели сетевой карты

Но чуда не произошло, сколько я не плясал с бубном, но видел при загрузке только такую картину:

BIOS не был обнаружен, была предпринята попытка загрузки с дискетки

В этот момент я напрасно старался найти свой код в памяти, используя утилиту debug (поэтому и установил DOS). Но все попытки были тщетны, и я в конечном итоге решил обновить аппаратное обеспечение для своих экспериментов.

Pentium 4 и сетевая карта Realtek RTL8139

Поскребя по сусекам в своём гараже, обнаружил там системный блок с Pentium 4, который долгое время стоял под дождём, но оказался живым. И подумал, что он вполне может подойти для моих целей. Винт был мёртв и хрустел французской булкой, но меня это не волновало, главное, чтобы работал BIOS и PCI.

Потерянные внутренности испытуемого, все соединения разъединены, чтобы избежать

При включении оказался вполне себе бодрым и живым, разве что пришлось заменить батарейку BIOS.

Запуск, всё, что живет, без дисков

Для того чтобы код с сетевой карты запустился, нужно обязательно установить загрузку через LAN. Удивительно, но по умолчанию он не выполнится, несмотря на то, что инструкции утверждают иначе.

Включаем исполнение кода на сетевой карте

Существует надежный метод выполнения задачи качественно — это следовать примеру более опытных. Поэтому я решил взять не только фрагменты кода из проекта AHCI BIOS, но и советы по выбору сетевой карты:

Я рекомендую использовать контроллер Realtek RTL8139. Сетевые карты с RTL8139 легко и дешево можно найти на вторичном рынке. Убедитесь, что сетевая карта имеет неподключенный сокет DIP-28, поскольку вам также понадобится EEPROM DIP-28 в качестве фактического ROM для опций. Любой ROM, который продается как 28C64, должен быть совместим.

Вместо 28C64 решил использовать микросхему M2764, которая у меня уже была в наличии. Карта очень распространённая и купить её не проблема, главное следить, чтобы в ней была панелька.

Изучая статью "Загрузчик для компьютера 486", я выяснил, что для функционирования BOOT ROM сетевую карту необходимо настроить. Мне удалось найти драйверы для RTL8139 под ДОС, и я даже произвел настройку, чтобы всё функционировало должным образом.

Конфигурация сетевого адаптера в драйверах

Ставлю прошитое ранее ПЗУ в эту сетевую карту, ииии…

Смонтированное ПЗУ. Оборудование демонтировано для упрощения повторной установки платы.

Устанавливаю её в системный блок, загружаюсь и ничего. Как я не бился, как ни крутился, что не делал — не получается. Именно тогда я озадачился поиском программы просмотра памяти. С помощью RAMVIEW смотрел всю память, но так и не встретил там участка с моим образом (ключевое слово «Hello Word» в начале сегмента).

Уяснили, что необходимо приобрести сетевую плату, оборудованную актуальным и работающим Boot ROM, и изучить её функционирование. Это сетевые карты формата PXE.

Рабочий вариант: сетевуха с образом PXE

Замысел был простым: обнаружить сетевую карту с функционирующим ПЗУ, а затем изучить её внутреннее устройство. Я решил отсоединить микросхему ПЗУ, загрузить свою прошивку и вернуть её на место. Но затем пришла мысль: а зачем вообще отсоединять, если можно установить просто панельку? Так я и сделал.

PXE-сетевые карты стоят на досках объявлений дешевле, чем микросхемы ПЗУ в магазине. Поэтому, купил сразу несколько плат, чтобы было место для экспериментов. Выбор пал на распространённую модель: 3Com 3C905C-TXM.

Сетевая плата с поддержкой PXE

Ставлю её в компьютер, и, о чудо, она грузится!

Успешно найден ROM PXE!

Вы можете также ознакомиться с тем, как образ PXE ROM представлен в оперативной памяти вашего компьютера, используя программу RAMVIEW.

Образ PXE ROM в памяти компьютера

Здесь я обнаружил важные данные, которые помогут мне идентифицировать образ своей программы в оперативной памяти компьютера. Всё аналогично, волшебные два числа и размер образа в секторах.

Дело стало за малым: отпаять микросхему и припаять панельку. На деле оказалась не такая простая процедура, как я думал, даже пришлось носить к профессиональному монтажнику (благо есть такая возможность), но у меня всё получилось.

Извлекаем микросхему ПЗУ

Устанавливаем панель для чипов

Прошиваем

Размещаем микросхему в её предназначенное место. Для сравнения приведены плата с панелью и микросхема, установленная по стандарту:

Всё, теперь всё готово к тестированию. Вставляем плату в компьютер, и всё работает!

Жесткий аппаратный привет миру

Я запечатлел этот потрясающий момент на видео.

Теперь даже можно проверить, что в памяти видно эту программу. Обратите внимание на строку «Hello world.» в конце.

На адресе PXE теперь располагается «Привет мир»

Всё это конечно прикольно, но хочется чего-то более полезного и функционального, ведь умение делать свой ROM BIOS дарит много новых возможностей. А давайте сделаем настоящий ROM BASIC?

❯ BIOS ROM BASIC

Старшие поколения помнят, что в первых ИП-устройствах IBM интерпретатор BASIC был интегрирован в ROM BIOS. Мне показалось интересным загружаться не в операционную систему, а прямо в интерпретатор BASIC.

Основной задачей, которую я поставил перед собой, было найти качественные исходные коды BASIC, написанные на ассемблере. Это может показаться странным, но Microsoft официально разместила в своем репозитории исходники GW-BASIC. Однако, после того как я внимательно ознакомился с ними и изучил мнения тех, кто пытался их скомпилировать, я понял, что дело бесперспективное. В конце концов, я отказался от идеи использования этих исходников, хотя это и выглядело бы очень подлинно.

Мне же посоветовали исходники bootBASIC, который занимает всего 512 байт, и рассчитан для работы в загрузочном секторе жёсткого диска. По счастливому стечению обстоятельств, в качестве транслятора там тоже использовался nasm.

Я создал аналог этого проекта и быстро дополнил его функциями, чтобы он мог также генерировать образ для сетевой карты ROM: добавил в начале магическое число, указал размер и в конце контрольную сумму — теперь у вас есть вся необходимая информация, и вы сами сможете это сделать.

Осталось только собрать образ ROM простой командой:

make basic.rom

Затем выполните проверку:

make runqemurom

Тем не менее, это не так увлекательно; хочется испытать на настоящем оборудовании. И, о чудо, всё функционирует!

Как говорится, лучше один раз увидеть, чем тысячу раз прочитать. Поэтому вот вам небольшое кинцо.

❯ Заключение

Никто не ожидал, что процесс разработки собственного образа BIOS станет таким увлекательным и затяжным приключением. Пришлось приобрести почти десять сетевых карт, прежде чем мне удалось наткнуться на подходящую. Проблемы с ROM, с которыми я столкнулся, скорее всего вызваны низким качеством микросхемы ПЗУ.

Очень много этапов экспериментов не вошло в статью, как минимум у меня около десятка различных вариаций «Hello world», написанных в разной стилистике ассемблера. Поле для экспериментов просто непаханое. Несмотря на то, что это старьё, такой опыт полезен для разработки на голом железе и может быть использован при программировании других модулей.

Несмотря на ограниченное количество источников по этой теме, процесс разработки собственного ПЗУ BIOS является относительно простым. Он не сильно отличается от процесса создания загрузчика в загрузочном секторе жёсткого диска. Важно лишь помнить о контрольной сумме, которую также необходимо уметь вычислять и добавлять в конец образа ROM.

❯ Полезные ссылки

  1. Краткое объяснение, что такое расширение BIOS.
  2. Презентация, которая вдохновила меня на действия.
  3. Репозиторий проекта AHCI BIOS Security Extension.
  4. Мой репозиторий BIOS «Привет, мир.».
  5. Первоначальный репозиторий bootBASIC.
  6. Мой репозиторий, который может загружаться в ROM BIOS.
  7. Интересная статья на тему "Загрузчик для 486-го компьютера".

Написание собственной Операционной Системы №1

В данной серии публикаций я планирую поделиться своими мыслями о разработке собственной операционной системы (предупреждаю, лучше удержитесь от смеха по этому поводу). Конечно, я не собираюсь углубляться на самый детализированный уровень, но постараюсь осветить некоторые основы, с которых будет полезно начать. Хочу отметить, что предполагаю наличие у вас некоторого опыта программирования на Assembler, а также знание таких фундаментальных понятий, как сегментная память и реальный режим. Если вы этого не знаете, рекомендую начать с книги Зубкова.

Итак, начнем. Давайте рассмотрим приблизительно работу известных ОС.

Структура адресного пространства в DOS:

РазмерФизический адресСегментный адрес
1КбайтВекторы прерываний00000h0000h
256байтЗона данных BIOS00400h0040h
Операционная система MS-DOS00500h0050h
Область для приложений
64КбайтГрафический видео буферA0000hA000h
32КбайтДоступные адресаB0000hB000h
32КбайтТекстовый видеобуферB8000hB800h
64КбайтПЗУ расширения BIOSC0000hC000h
128КбайтДоступные адресаD0000hD000h
64КбайтПЗУ BIOSF0000hF000h
64КбайтHMA100000h
До 4ГбайтXMS10FFF0h

Первые 640 Кбайт (до графического видеобуфера) называются стандартной (conventional) памятью. Начинается стандартная память с килобайта, который содержит векторы прерываний, их 256 на каждый отводится по 4 байта.

После этого следует область данных BIOS, отвечающая за начальную диагностику. При включении компьютера BIOS выполняет POST – проверку, которая выявляет ошибки в оборудовании. Если всё прошло успешно, BIOS загружает первый сектор, где располагается загрузочная программа операционной системы, с выбранного устройства (дискеты или жесткого диска) по адресу 0x7C00h, передавая ему управление. В этой области находятся данные, необходимые для правильного функционирования BIOS. Также существует возможность модифицировать эту область, что позволяет влиять на выполнение системных функций. Изменяя что-либо в ней, мы передаем параметры BIOS и его функциям, делая их более адаптивными. При установленной DOS операционная система начинается с сегментного адреса 500h. После ОС располагаются прикладные или системные программы, загруженные операционной системой, и занимающие память до 640 Кбайт.

С 640 килобайт начинается старшая память, также известная как верхняя (upper) память, которая расположена до 1 мегабайта (до HMA), охватывая 384 Кбайт. В этом сегменте находятся ПЗУ (постоянно запоминающее устройство): текстовый видеобуфер, рассчитанный на диапазон B8000h…BFFFFh, и графический видеобуфер (A0000h…AFFFFh). Для вывода текста необходимо записать его ASCII коды в текстовый видеобуфер, и вы сразу увидите соответствующие символы. В диапазоне F0000h…FFFFFh расположен сам BIOS. Кроме того, существует еще одно ПЗУ – расширения BIOS (C0000h…CFFFFh), которое предназначено для обслуживания графических адаптеров и дисков.

За первым мегабайтом, с адреса 100000h, располагается память именуемая как расширенная память, конец которой до 4 гигабайт. Расширенная память состоит из 2х подуровней: HMA и XMS. Высокая память (High Memory Area, HMA) доступна в реальном режиме, а это еще плюс 64 Кбайт (точнее 64 Кбайт – 16 байт), но для этого надо разрешить линию A20 (открыв вентиль GateA20). Функционирование расширенной памяти подчиняется спецификации расширенной памяти (Expanded Memory Specification, XMS), поэтому саму память назвали XMS-памятью, но она доступна только в защищенном режиме.

Вернемся к началу адресного пространства в контексте DOS и изучим его более детально.

1) Векторы прерываний таковы (это нам понадобится, когда мы будем составлять свою таблицу прерываний):

IRQ INT Причины возникновения IRQ0 8h Системный таймер IRQ1 9h Клавиатура IRQ2 10h Ведомый контроллер IRQ3 11h Порт COM2, модем IRQ4 12h Порт COM1, мышь IRQ5 13h Порт LPT2 IRQ6 14h Дисковод IRQ7 15h Порт LPT1, принтер IRQ8 70h Часы реального времени IRQ9 71h Прерывание обратного хода луча IRQ10 72h Дополнительные устройства IRQ11 73h Дополнительные устройства IRQ12 74h PS-мишень IRQ13 75h Ошибка математического сопроцессора IRQ14 76h Первый IDE-контроллер IRQ15 77h Второй IDE-контроллер, жесткий диск

2) BIOS. Это, по сути, небольшое отступление, так как мы будем обсуждать не столько устройство BIOS, сколько методы добавления в него собственных функций. Хотя использование таких функций сделает нашу операционную систему менее универсальной, на начальном этапе они могут быть весьма полезными. В процессе написания ОС я больше не буду это упоминать; это остается на усмотрение заинтересованных читателей в качестве самостоятельного задания. =)

Итак, BIOS (я буду говорить об AWARD BIOS, так как это наиболее популярные версии, поэтому возможно незначительные расхождения с другими BIOS) – это последовательность запакованных файлов, которые заканчиваются файлом bootblock. Структура первого мегабайта памяти, отведенного под BIOS такова:

00000 – xxxxx+1оригинальный.tmp и данные для CRC
xxxxx+1 – yyyyyСжатый модуль
yyyyy – zzzzzПрочие сжатые модули
zzzzz — ~17FFEhДоступное свободное место
~1C000* – 1FFFFhBootblock

Перед свободным пространством находится основная часть BIOS, а именно:

original.tmp – главная часть, в которой располагается подпрограмма BIOS Setup, а так же части, необходимые для инициализации. CRC – контрольная сумма BIOS awardext.rom – подпрограмма вывода конфигурации компьютера awardepa.bin – изображение Так же могут встречаться другие необязательные модули.

При запуске компьютера bootblock активирует регистры чипсета, распаковывает модули, сжатые с использованием LHA, и загружает их в память. Таким образом, эти файлы можно перепрограммировать, модифицируя или добавляя элементы в BIOS. Это позволяет настроить все параметры BIOS, начиная от текстовых надписей и заканчивая добавлением поддержки новых устройств, информация о которых отсутствует в текущей версии BIOS. Этот процесс довольно прост: например, с помощью утилиты modbin (стандартная программа от Award) возможно распаковать указанные файлы (например, скачанные из Интернета), внести необходимые изменения и заново записать в BIOS. Обратите внимание, что при редактировании сжатых модулей необходимо будет также обновить CRC, иначе BIOS может принять их за поврежденные.

Итак, что требуется для более комплексной перепрошивки BIOS, чем просто небольшие изменения уже имеющихся кодов? В первую очередь, стоит отметить, что существует большое количество компаний, которые занимаются производством материнских плат и процессоров, и единого стандарта для чипсетов не существует. Это означает, что создание универсального BIOS для всех материнских плат невозможно; для каждой модели материнской платы необходимо разработать свои функции и интегрировать их в единый BIOS. Однако для этого потребуется значительное количество человеко-часов, поэтому реализация подобного проекта в одиночку представляется очень сложной или даже невозможной.

Итак, наша программа будет располагаться в ПЗУ (постоянное запоминающее устройство). BIOS передаст ей управление, но для этого он должен ее найти. Соответственно наша программа должна находиться в области с С800:0 до E000:0 в памяти, так как эта область сканируется BIOS на наличие определенной сигнатуры 0AA55H. В байте за этой подписью количество байт для подсчета их контрольной суммы. Если контрольная сумма равно нулю, то это ПЗУ и управление передается в область памяти, где была найдена данная сигнатура со смещением 3. Для того, чтобы «уровнять» контрольную сумму, необходимо в конце программы дописать байт, в котором будет число, равное разнице 100h и полученной контрольной суммы.

Таким образом, ваша программа, предназначенная для записи в ПЗУ, должна выглядеть именно так.

LENGTHROM EQU 2000H ; Размер ПЗУ в байтах = числу после подписи * 200H CODE SEGMENT BYTE PUBLIC ASSUME CS:CODE,DS:CODE ORG 0 START: DB 55h DB 0AAh; Размер ПЗУ по модулю 200H DB LENGTHROM SHR 9; Первая выполняемая команда JMP BEGIN BEGIN: ; Заносим в регистры нужные значения MOV AX,CS MOV DS,AX ; Код программы ; Вернуть управление БИОС RETF ; Сюда запишем дополняющий байт DB (0) CodeEnd: ; заполнение оставшегося кода нулями DB (LENGTHROM-(OFFSET CodeEnd-OFFSET START)) DUP (0FFH) LastByte: CODE ENDS END START

Загрузка Linux и Windows

Данная тема является важнейшей и основной. Вспомним о BIOS, который инициирует загрузку первого сектора (Master Boot Record) с устройства, выбранного в его настройках (дискеты, жесткого диска, CD-ROM и прочих) по адресу 0x7C00h, куда и передает контроль. Программа, находящаяся в этой области памяти, называется первичным загрузчиком. У нее довольно ограниченные возможности, поскольку ее размер не превышает 512 байт.

Его задачей является подготовка компьютера, а именно: запись в память вторичный загрузчик, предварительно считанный с HDD, включить линию A20 и перевести процессор в защищенный режим. После этого управление передается вторичному загрузчику, цели работы которого точно не определены. Я считаю, что его главными задачами являются формирование таблицы прерываний, подготовка компьютера к работе с файловой системой, определение периферийных устройств, подключенных к компьютеры, передача управления ядру, скачанному им с диска заложенному в памяти.

Для более глубокого восприятия процесса загрузки операционной системы, прежде чем переходить к исходным тестам, изучим основные принципы загрузки самых распространённых на сегодняшний день ОС: Linux и Windows.

Linux может загружаться как через специализированный загрузчик (Lilo), так и через boot sector диска. Поскольку загрузчика у нас нет, а есть только желание более полно узнать об устройстве загрузки, рассмотрим второй случай:

1) Бут-сектор записывает свой код по адресу 9000h. 2) Он загружает с диска загрузчик Setup, который располагается в нескольких следующующих секторах (9000h:0200h). 3) Ядро загружается по адресу 1000h и следует сразу за Setup. Размер ядра должен составлять менее 508 килобайт. 4) Управление передается загрузчику Setup. 5) Производится проверка Setup на корректность. 6) BIOS определяет оборудование, объем оперативной памяти, наличие жестких дисков, поддержку шины Micro Channel, наличие мыши PC/2, управление питанием, а также инициализирует клавиатуру и видеосистему. 7) Процессор переводится в защищенный режим. 8) Управление передается ядру. 9) Ядро перезаписывается по адресу 100000h (если оно было сжато, то предварительно его разархивируют). 10) Передача управления осуществляется ядру. 11) Активируется страничная адресация. 12) Инициализируются таблицы IDT и GDT, при этом кодовый сегмент и сегмент данных ядра охватывают всю виртуальную память. 13) Загружаются драйверы. 14) Управление передается процессу init. 15) Процесс init запускает все остальные необходимые программы в соответствии с файлами конфигурации (init.X).

Теперь давайте изучим процесс загрузки Windows (версии NT, так как более ранние уже устарели):

1) boot sector загружает NTLDR 2) Процессор переходит в защищенный режим; 3) Делаются таблицы страниц 4) Механизм преобразования страниц; 5) Чтение boot.ini, используя код FS под названием read only. Выводит на экран выбор загрузки ОС (из boot.ini) 6) Из boot.ini считывается адрес директории Windows 7) Управление получает ntdetect.com, определяющий устройства, установленные на компьютере 8) Из %dir%system32 загружается ntoskrnl.exe, в котором находится ядро. 9) Управление передается hal.dll с информацией об аппаратном обеспечении; 10) Загружаются драйвера и важные файлы 11) Стартует графическая оболочка и пр.

Национальный подход

Итак, мы рассмотрели на примерах уже готовых ОС этапы загрузок, а так же устройство памяти. Приступим непосредственно к написанию своей ОС. Начнем мы с написания загрузчика, который должен обеспечить загрузку и подготовить все для старта ОС. Он будет делиться на два (деление условное). Задача первого подготовить базу, а точнее занести в память код с дискеты, после чего передать управление второму загрузчику, задача которого перевести процессор в защищенный режим и сделать другие подготовки для передачи управления уже собственно ядру.

1) Начальный загрузчик

Мы будем загружать информацию с дискеты, поэтому нам нужно считывать с нее данные по секторам.

// Принцип работы такой: читать можем только в первые 64к, поэтому сначала считывается цилиндр в 0x50:0 — 0x50:0x2400, а затем копируется туда, куда необходимо. При этом первый цилиндр считываем в конце.

section .text BITS 16 org 0x7c00 // Загружаем ядро по адресу 0x7c00 %define CTR 10 %define MRE 5 // Определяем переменные enter: cli ; mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0x7c00 sti // Поскольку значения различных регистров (кроме CS, равного 0) неизвестны, мы должны сами инициализировать регистры, то есть “обнулить” SS, SP и DS. Также необходимо отключить прерывания, чтобы работа загрузчика проходила без помех. // Далее: // Собираемся переместить данные с дискеты, они попадут в текущий код, поэтому нужно загрузить его в верхнюю область доступной памяти. // В DS — адрес исходного сегмента mov ax, 0x07c0 mov ds, ax // В ES — адрес целевого сегмента mov ax, 0x9000 mov es, ax // Обнуляем регистры xor si, si xor di, di // Копируем 128 двойных слов mov cx, 128 rep movsd // Выполняем переход в загруженный bootsector (0x9000: 0) jmp 0x9000:start // следующий код будет выполняться по адресу 0x9000:0 begin: // Заполняем регистры новыми значениями mov ax, cs mov ds, ax mov ss, ax // Уведомляем пользователя о запуске mov si, msg_startup call ps // Считываем цилиндр, начиная с указанного в DI, также нулевой цилиндр (в последнем случае) в AX (адрес, куда будут загружены данные) mov di, 1 mov ax, 0x290 xor bx, bx .loop: mov cx, 0x50 mov es, cx push di // Подсчитываем головки для использования shr di, 1 setc dh mov cx, di xchg cl, ch pop di // Завершили ли мы считывание всех цилиндров? cmp di, CTR je .quit call r_cyl // Считанные данные находятся в 0x50:0x0 — 0x50:0x2400 (в линейном представлении — 0x500 — 0x2900) // Копируем этот блок в нужный адрес: pusha push ds mov cx, 0x50 mov ds, cx mov es, ax xor di, di xor si, si mov cx, 0x2400 rep movsb pop ds popa // Увеличиваем DI, AX и повторяем заново inc di add ax, 0x240 jmp short .loop .quit: // Поскольку часть памяти занята, а мы считали только с первого цилиндра, стоит не забыть и загрузить нулевой mov ax, 0x50 mov es, ax mov bx, 0 mov ch, 0 mov dh, 0 call r_cyl // Переходим на загруженный код jmp 0x0000:0x0700 r_cyl: // Считываем указанный цилиндр, ES:BX – буфер, CH – цилиндр, DH — головка // Сбрасываем счетчик ошибок mov [.err], byte 0 pusha // Сообщаем, какая головка/цилиндр считывается mov si, msg_cyl call ps mov ah, ch call pe mov si, msg_head call ps mov ah, dh call pe mov si, msg_crlf call ps popa pusha .start: mov ah, 0x02 mov al, 18 mov cl, 1 // Прерывание BIOS int 0x13 jc .r_err popa ret .err: db 0 .r_err: // Об ошибках сообщаем и выводим их код inc byte [.err] mov si, msg_err call ps call pe mov si, msg_crlf call ps // Если ошибок больше нормы: cmp byte [.err], mre jl .start mov si, msg_end call ps hlt jmp short $

table: db "0123456789ABCDEF" pe: // ASCII-код преобразуем в его шестнадцатеричного представления и выводим pusha xor bx, bx mov bl, ah and bl, 11110000b shr bl, 4 mov al, [table+bx] call pc mov bl, ah and bl, 00001111b mov al, [table+bx] call pc popa ret // Из AL выводим символ на экран pc: pusha mov ah, 0x0E int 0x10 popa ret // Строку из SI выводим на экран ps: pusha .loop: lodsb test al, al jz .quit mov ah, 0x0e int 0x10 jmp short .loop .quit: popa ret // Служебные сообщения msg_startup: db "OS loading. ", 0x0A, 0x0D, 0 msg_cyl: db "Cylinder:", 0 msg_head: db ", head:",0 msg_er: db "Error! Code of it:",0 msg_end: db "Errors while reading",0x0A,0x0D, "Reboot the computer, please", 0 msg_crlf: db 0x0A, 0x0D,0

// Подпись загрузочного сектора: TIMES 510 — ($-$$) db 0 db 0xAA, 0x55

2) Вспомогательный загрузчик

А теперь вторичный загрузчик:

[BITS 16] [ORG 0x700] // Инициализируем регистры, устанавливаем стек cli mov ax, 0 mov ds, ax mov es, ax mov ss, ax mov sp, 0x700 sti // Приветственное сообщение mov si, msg_start call kputs // Сообщение о переходе в защищенный режим mov si, msg_entering_pmode call ps // Скрытие курсора (так просто) mov ah, 1 mov ch, 0x20 int 0x10 // Задаем базовый вектор для контроллера прерываний в 0x20 mov al,00010001b out 0x20,al mov al,0x20 out 0x21,al mov al,00000100b out 0x21,al mov al,00000001b out 0x21,al // Отключаем прерывания cli // Загружаем регистр GDTR: lgdt [gd_reg] // Активация A20: in al, 0x92 or al, 2 out 0x92, al // Установка бита PE в регистре CR0 mov eax, cr0 or al, 1 mov cr0, eax // С помощью длинного перехода загружаем селектор нужного сегмента в регистр CS jmp 0x8: _protect ps: pusha .loop: lodsb test al, al jz .quit mov ah, 0x0e int 0x10 jmp short .loop .quit: popa ret // Следующий код — 32-битный [BITS 32] // При переходе в защищенный режим управление будет передано сюда _protect: // Загружаем регистры DS и SS с помощью селектора сегмента данных mov ax, 0x10 mov ds, ax mov es, ax mov ss, ax // Ядро слинковано по адресу 2мб, копируем его туда. ker_bin — метка, после которой имеет место ядро mov esi, ker_bin // Адрес для копирования mov edi, 0x200000 // Размер ядра в двойных словах (65536 байт) mov ecx, 0x4000 rep movsd // Ядро скопировано, передаем управление jmp 0x200000 gdt: dw 0, 0, 0, 0 // Нулевой дескриптор db 0xFF // Сегмент кода с DPL=0 Базой=0 и Лимитом=4 Гб db 0xFF db 0x00 db 0x00 db 0x00 db 10011010b db 0xCF db 0x00 db 0xFF // Сегмент данных с DPL=0 Базой=0 и Лимитом=4Гб db 0xFF db 0x00 db 0x00 db 0x00 db 10010010b db 0xCF db 0x00 // Значение, загружаемое в GDTR: gd_reg: dw 8192 dd gdt msg_start: db "Get fun! New loader is on", 0x0A, 0x0D, 0 msg_epm: db "Protected mode is greeting you", 0x0A, 0x0D, 0

Оба загрузчика готовы. Осталось лишь откомпилировать их и отправить на bootsector дискеты. За сим с первой частью я заканчиваю, в следующей части мы будем писать ядро системы.

Оцените статью
LeeReload
Добавить комментарий