Давайте загрузим программу без ОС на x86 — это вам может понадобиться, например, при написании ОС. Для загрузки будем использовать grub и Multiboot. Multiboot — открытый стандарт, который описывает как bootloader может загрузить ОС на x86.
Multiboot подразумевает определенный заголовок, который multiboot ищет, для того чтобы определить ОС (или в нашем случае просто программу), которую он будет загружать. Данный заголовок отличается для Multiboot 1 и Multiboot 2 (смотрите ссылки на спецификации в конце статьи) — в статье мы рассмотрим Multiboot 1, но в примерах кода на github будет так же и пример для Multiboot 2.
В multiboot 1 заголовок состоит из:
— magic number, 4 байта. Должен быть 0x1BADB002 (I BAD BOOT), идентифицирует заголовок.
— флаги, 4 байта. Флаги указывают на фичи, которые ОС запрашивает от загрузчика — в нашем случае может быть нулевым.
— контрольная сумма, 4 байта. Должно быть равно дополнению до нуля суммы значений двух предыдущих полей ( checksum = 0x1 0000 0000 — (magic number + flags) ).
Данный заголовок должен находиться в первых 8 килобайтах нашей программы. Если смотреть спецификацию на multiboot, то за данным заголовком есть и другие поля заголовка, в том числе и точка входа в программу. Но, если загружать elf формат (а не bin) нашей программы, то grub умеет сам разбирать его и определять точку входа, поэтому данные поля не нужны.
Напишем код, реализующий поддержку Multiboot 1 — используем NASM.
MAGIC equ 0x1BADB002
FLAGS equ 0
CHECKSUM equ -(MAGIC + FLAGS)
section .multiboot_header
align 4
dd MAGIC
dd FLAGS
dd CHECKSUM
section .bss
align 4
stack_bottom:
resb 16384
stack_top:
section .text
global start
extern main
start:
mov esp, stack_top
call main
cli
hang:
hlt
jmp hang
В коде мы определили значения, которые должны быть в полях заголовка, через equ. В секции multiboot_header положили заголовок в память, используя значения определенные выше.
Note:
db — 1 байт
dw — 2 байта — 1 слово
dd — 4 байта — 2 слова
Далее объявляется bss секция и выделяется 16 килобайт под стек. Секция text содержит точку входа программы — start. В ней мы записываем указатель на стек в соответствующий регистр и вызываем функцию main, которая будет написана на C далее.
Если есть возврат из функции main, то программа зацикливается на метке hang. Где hlt — это команда приостановки процессора до тех пор, пока не возникнет аппаратное прерывание.
Note: grub переводит процессор из реального режима в защищенный, но не настраивает MMU.
Код на C будет содержать вывод в video memory в text mode — адрес 0xB8000.
void hello_world(int number);
void main(void) {
hello_world(4);
}
void hello_world(int number) {
char * vidmem = (char*)0xB8000;
int i;
for(i = 0; i < number; i++) {
vidmem[0 + i*30] = 'H';
vidmem[2 + i*30] = 'e';
vidmem[4 + i*30] = 'l';
vidmem[6 + i*30] = 'l';
vidmem[8 + i*30] = 'o';
vidmem[10 + i*30] = ' ';
vidmem[12 + i*30] = 'W';
vidmem[14 + i*30] = 'o';
vidmem[16 + i*30] = 'r';
vidmem[18 + i*30] = 'l';
vidmem[20 + i*30] = 'd';
vidmem[22 + i*30] = '!';
}
}
Теперь компилируем и линкуем. Для начала создадим скрипт для компоновщика:
ENTRY(start)
SECTIONS {
. = 1M;
.boot :
{
*(.multiboot_header)
}
.text :
{
*(.text)
}
.bss :
{
*(.bss)
}
}
Компилируем и линкуем:
nasm -f elf32 ./boot.asm
gcc -m32 -march=i386 -c ./boot.s -o boot.o
ld -m elf_i386 -T linker.ld -o mykernel.elf ./main.o ./boot.o
Теперь у нас готова программа mykernel.elf — сейчас ее нужно положить на образ диска, на котором установлен grub.
Как создать образ и положить на него загрузчик и программу описано здесь:
https://badembed.ru/qemu-busybox-linux-kernel-chast-2-dobavlenie-grub/ .
Только за место Linux kernel, на диск копируете mykernel.elf.
Так же по созданию образа с grub и копирования на него программы:
http://wiki.badembed.ru/#Create%20grub%20inside%20raw%20image
http://wiki.badembed.ru/#%D0%BC%D0%BE%D0%BD%D1%82%D0%B8%D1%80%D1%83%D0%B5%D0%BC%20%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%20%D0%B4%D0%B8%D1%81%D0%BA%D0%B0%20%D1%81%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E%20qemu
Загружаем все это в qemu:
qemu-system-i386 -hda ./disk.raw
После загрузки grub в его командной строке вводим:
grub> multiboot /mykernel.elf
grub> boot
Все, программа должна запуститься и напечатать 4 раза hello world.
Можно отлаживать ее, подключившись GDB к qemu, — давайте проделаем это. qemu запускаем со следующими флагами:
qemu-system-i386 -s -S -hda ./disk.raw
где
-s shorthand for -gdb tcp::1234
-S freeze CPU at startup (use 'c' to start execution)
Далее во втором терминале запускаем gdb, указываем архитектуру, подключаемся к qemu, загружаем elf файл и ставим точки останова:
> gdb
(gdb) set architecture i386
(gdb) target remote localhost:1234
(gdb) file /path_to_mykernel.elf/mykernek.elf
(gdb) b *0x7c00
(gdb) b start
(gdb) c
(gdb) x/10i 0x00100070 # ассемблер по 0x00100070 (10 строк)
По адресу 0x7c00 расположен загрузчик grub, туда bios передает управление загрузчику. После подгрузки символов мы можем поставить точку останова на метку start.
После того как мы пошлем c (continue) команду в gdb, программа в qemu начнет выполняться — сработает первая точка останова по адресу 0x7C00 — это начало работы grub. Шлем еще раз continue, далее загрузчик загружается и вам надо ввести в него (переключившись на qemu) команды multiboot и boot (как описано выше в статье).
Далее сработает точка останова на метке start и выведется адрес(допустим 0x00100070) на котором эта точка останова сработала. Для того что бы посмотреть ассемблер по этому адресу, выполняем команду:
x/10i 0x00100070
где 10 количество строк ассемблера.
На этом все.
Код на github:
https://github.com/badembed/simple_fw_with_grub_and_without_OS_x86
Дополнительные ссылки:
http://wiki.osdev.org/Bare_Bones
https://habrahabr.ru/company/neobit/blog/173263/
http://os.phil-opp.com/multiboot-kernel.html
http://alex.dzyoba.com/programming/multiboot.html
Multiboot specification:
https://www.gnu.org/software/grub/manual/multiboot/multiboot.html
http://nongnu.askapache.com/grub/phcoder/multiboot.pdf