§ Загрузка BIOS

Когда компьютер начинает работу, то он проходит множество этапов загрузки. Самым первым делом, устанавливается адрес F000:FFF0, в реальном режиме работы процессора (правда, я сейчас не знаю, как современные компьютеры загружаются), и вот оттуда начинает работать биос (или его аналог), который проверяет диски, память, все внутренние подсистемы, инициализирует все что может.
И вот как раз именно после всех инициализации, условно будем называть это биосом (efi, uefi, bios, у него куча разных названий сейчас появилось), вызывает первый сектор (Master Boot Record, если не по-нашему) с какого-нибудь диска, даже с сети может, неважно, и укладывает этот сектор адресу в памяти 0000h : 7C00h. Понятное дело, что размер сектора равен 512 байтам. Попробуй развернись в этом количестве байт и сделай что-то! Нет, находятся умельцы, которые и кубы крутят, и шахматы мутят.

§ Что делать дальше

Чем заняться после загрузки в память? Конечно, надо что-то инициализировать, как же без инициализации, все инициализируют, а я что, хуже что ли? Я тоже буду.
org     7C00h       ; Указываем ассемблеру, что код начинается с 7C00h
cli                 ; IF=0 теперь прерывания не вызываются
cld                 ; DF=0 теперь все lods/movs/stos/etc возрастают
xor     ax, ax      ; AX=0000
mov     ds, ax      ; DS=0000
mov     es, ax      ; ES=0000
mov     ss, ax      ; SS=0000
mov     sp, 7C00h   ; SP=7C00
; ... остальной код ...
times   (7C00h-$)+510 db 255 ; Заполнить FFh оставшиеся 510 байт
dw      0xAA55      ; Сигнатура бутсектора
Замечу, что если у бутсектора не будет в конце сектора 2 байта с сигнатурой 55 AA, то такой бутсектор будет считаться биосом не бутсектором, а мусором и ничего он не загрузит.
А где же CS=0000? Он и так ноль, так что все в порядке. После инициализации, конечно, интересно узнать, а как же этот бутсектор записать, чтобы он вообще как-нибудь заработал? И вот тут начинаются проблемы вселенского масштаба.
Как и обычно, надо ассемблер включить. Он всегда находится под рукой и называется flat assembler. Очень удобный.
После сложных установок, настроек, калибровки, интеграции и инкапсуляции в хост-систему, можно будет написать приблизительно такой запускной файл, например, makefile:
fasm boot.asm boot.bin
С помощью этого набора команд будет сформирован новый файл boot.bin, в котором и будет содержаться бутсектор.

§ Отладчик bochs

Про этот отладчик я узнал очень давно, еще когда сидел на windows xp, а не в линуксе, как сейчас, и потому пользоваться я им, конечно, не научился толком, но кое-что загружать получалось. Как настроить отладчик, я уже сто раз писал везде где ни попадя. Настраиваю я его под winxp, конечно же. А как же линукс? Какой еще линукс? Нет никакого линукса, есть винда и она навсегда.
Проблема следующая. Файловую систему разметить с помощью винды мне не удастся, потому придется изобретать свои велосипеды на языке программирования php, чем далее я и буду заниматься после того, как бутсектор напишу.
Для того, чтобы загрузить сектор, надо диск создать. Утилита bximage в этом мне поможет хотя бы:
  • Тип диска — hd
  • Способ записи — flat
  • Количество мегабайт — 256 (сойдёт для сельской местности)
  • Имя диска — c.img
Вот что он мне предлагает сделать:
ata0-master: type=disk, path="c.img", mode=flat
Поэтому эту строку можно добавить в файл конфигурации c.bxrc.
megs: 32
romimage: file=c:/bochs/BIOS-bochs-latest
vgaromimage: file=c:/bochs/VGABIOS-lgpl-latest
ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
ata0-master: type=disk, path="c.img", mode=flat, biosdetect=auto, translation=auto
boot: c
display_library: win32, options="gui_debug"
log: bochsout.txt
mouse: type=ps2, enabled=0, toggle=ctrl+mbutton
cpu: ips=15000000
clock: sync=both
keyboard: keymap=c:/bochs/keymaps/x11-pc-us.map
magic_break: enabled=1
Тут есть зависимая конфигурация keymap, если она находится в другом месте, надо поправить путь. Тут есть такая опция как magic_break, если она активирована, то отладчик будет останавливаться на инструкции xchg bx,bx что просто крайне полезно, потому что без этого отладка почти что невозможна.
Запускать этот файл надо так (предварительно в makefile какой-нибудь оформить или bat-файл):
bochsdbg -f c.bxrc -q
Я сейчас проверил, у меня все заработало. Конечно, чтобы это все заработало, надо, чтобы все было правильно настроено. Без точной настройки ничего работать не будет.

§ Как запустить загрузчик

Есть несколько идей, как это сделать. Первая — велосипедом, вторая... не знаю. Придется велосипедом. Создам файл main.cc, например:
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char* argv[]) {
    return 0;
}
Компилировать известно как: g++ main.cc -o bootwriter. Должно заработать. Makefile будет выглядеть примерно так:
NAME=bootwriter.exe
all: $(NAME)
	fasm boot.asm boot.bin
$(NAME): main.cc
	g++ main.cc -o $(NAME)
	strip $(NAME)
То есть, если нет файла bootwriter.exe, то он его создает. На линуксе это может и не будет работать, не проверял — страшно.
Вопрос такой — как нарисовать сову? Делается очень легко! Сначала сделайте два кружочка. Если два кружочка готовы, нарисуйте сову целиком. Сова готова! Следующий код для main.cc будет это правило демонстрировать:
unsigned char buf[512] = {0};

if (argc < 3) return -1; // Аргументов должно быть 2

// Открыть файл на чтение и второй файл на запись
FILE* fr = fopen(argv[1], "rb");
FILE* fw = fopen(argv[2], "rb+");

// Если один из них открыть не удалось
if (fr == NULL || fw == NULL) return -2;

// Прочесть и записать 446 байт (не 512!)
fread(buf, 1, 446, fr);
fwrite(buf, 1, 446, fw);

// Подготовить 55 AA и записать в последние 2 байта первого сектора
buf[0] = 0x55; buf[1] = 0xAA;

fseek(fw, 510, SEEK_SET);
fwrite(buf, 1, 2, fw);

// Закрыть файлы
fclose(fr);
fclose(fw);
Что делает этот код?
$(NAME) boot.bin c.img
Если его запустить так, то код перепишет первые 446 байт в первый сектор и запишет сигнатуру, чтобы биос распознал этот сектор. Вот теперь можно уже попробовать написать что-нибудь, например, Hello World, в самом main.asm:
        mov     si, Hello        ; Ссылка на строку Hello
@@:     lodsb                    ; Загрузка нового символа в AL
        and     al, al           ; Проверить AL на 0
        je      $                ; Если 0, то остановить процессор
        mov     ah, 0Eh          ; AH=0Eh код печати символа
        int     10h              ; Печать символа
        jmp     @b               ; Повтор
Hello:  db      "Hello World",0
Осталось только скомпилировать и запустить. У меня все нормально получилось.
snap.png
Самое главное в программировании, это конечно, написать "HELLO WORLD". Если этого не будет, то зачем тогда программировать?
Файл как обычно, можно скачать: hello-world.zip