§ Загрузка BIOS

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

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

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

§ Отладчик bochs

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

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

Есть несколько идей, как это сделать. Первая — велосипедом, вторая... не знаю. Придется велосипедом. Создам файл main.cc, например:
1#include <stdlib.h>
2#include <stdio.h>
3
4int main(int argc, char* argv[]) {
5    return 0;
6}
Компилировать известно как: g++ main.cc -o bootwriter. Должно заработать. Makefile будет выглядеть примерно так:
1NAME=bootwriter.exe
2all: $(NAME)
3	fasm boot.asm boot.bin
4$(NAME): main.cc
5	g++ main.cc -o $(NAME)
6	strip $(NAME)
То есть, если нет файла bootwriter.exe, то он его создает. На линуксе это может и не будет работать, не проверял — страшно.
Вопрос такой — как нарисовать сову? Делается очень легко! Сначала сделайте два кружочка. Если два кружочка готовы, нарисуйте сову целиком. Сова готова! Следующий код для main.cc будет это правило демонстрировать:
1unsigned char buf[512] = {0};
2
3if (argc < 3) return -1; // Аргументов должно быть 2
4
5// Открыть файл на чтение и второй файл на запись
6FILE* fr = fopen(argv[1], "rb");
7FILE* fw = fopen(argv[2], "rb+");
8
9// Если один из них открыть не удалось
10if (fr == NULL || fw == NULL) return -2;
11
12// Прочесть и записать 446 байт (не 512!)
13fread(buf, 1, 446, fr);
14fwrite(buf, 1, 446, fw);
15
16// Подготовить 55 AA и записать в последние 2 байта первого сектора
17buf[0] = 0x55; buf[1] = 0xAA;
18
19fseek(fw, 510, SEEK_SET);
20fwrite(buf, 1, 2, fw);
21
22// Закрыть файлы
23fclose(fr);
24fclose(fw);
Что делает этот код?
1$(NAME) boot.bin c.img
Если его запустить так, то код перепишет первые 446 байт в первый сектор и запишет сигнатуру, чтобы биос распознал этот сектор. Вот теперь можно уже попробовать написать что-нибудь, например, Hello World, в самом main.asm:
1        mov     si, Hello        ; Ссылка на строку Hello
2@@:     lodsb                    ; Загрузка нового символа в AL
3        and     al, al           ; Проверить AL на 0
4        je      $                ; Если 0, то остановить процессор
5        mov     ah, 0Eh          ; AH=0Eh код печати символа
6        int     10h              ; Печать символа
7        jmp     @b               ; Повтор
8Hello:  db      "Hello World",0
Осталось только скомпилировать и запустить. У меня все нормально получилось.
snap.png
Самое главное в программировании, это конечно, написать "HELLO WORLD". Если этого не будет, то зачем тогда программировать?
Файл как обычно, можно скачать: 01-hello-world.zip