[
  {
    "path": ".bashrc",
    "content": "function emu() {    # compile and run emulator\n    nasm $1.asm -f bin -o temp.bin\n\tqemu-system-x86_64 temp.bin\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "*.bin\n*.raw\n\n# -- automatically generated --\n\n# Prerequisites\n*.d\n\n# Object files\n*.o\n*.ko\n*.obj\n*.elf\n\n# Linker output\n*.ilk\n*.map\n*.exp\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Libraries\n*.lib\n*.a\n*.la\n*.lo\n\n# Shared objects (inc. Windows DLLs)\n*.dll\n*.so\n*.so.*\n*.dylib\n\n# Executables\n*.exe\n*.out\n*.app\n*.i*86\n*.x86_64\n*.hex\n\n# Debug files\n*.dSYM/\n*.su\n*.idb\n*.pdb\n\n# Kernel Module Compile Results\n*.mod*\n*.cmd\n.tmp_versions/\nmodules.order\nModule.symvers\nMkfile.old\ndkms.conf\n"
  },
  {
    "path": "README.md",
    "content": "# os-project\n>Пишем свою собственную операционную систему с нуля!\n\nИдея написать ОС возникла у меня в процессе поиска идеи для сайд-проекта. Это исключительно хобби-проект, не рассчитанный на серьезность и достоверность, и хотя я пытался объяснить многие новые и неочевидные концепты, с которыми я столкнулся в процессе разработки, я мог что-то упустить, так как я сам только учусь - именно поэтому я настоятельно рекомендую пользоваться гуглом и любыми другими источниками информации когда вы познакомитесь с чем-то новым в гайде. Гуглите абсолютно всё. Я серьезно.\n\n**Prerequisites: **Для комфортного прохождения гайда нужно уметь программировать на языке Си на базовом уровне (одно из обязательных требований: понимать принципы работы с указателями), иметь опыт разработки на высокоуровневых ЯП. С синтаксисом ассемблера можно ознакомиться по ссылке ниже, но все же рекомендую побольше почитать или посмотреть по нему туториалов.\n\n## Навигация по репозиторию\n<ins>**`guide/`**</ins> --- гайд с последовательными уроками, теорией и задокументированным кодом\n* Гайд разделен на главы, например `00-BOOT-SECTOR`\n* Главы разделены на упражнения, например `ex00`\n* Упражнения содержат в себе код и теорию. Выглядят как `main.asm`\n\n<ins>**`src/`**</ins> --- исходный код ОС\n\n## Установка и запуск\n\n1. Установить эмулятор QEMU (подробнее: https://www.qemu.org/download/)\n```\nsudo apt install qemu-kvm qemu\n```\n2. Собрать кросс-компилятор gcc для i386 архитектуры процессора. Удобнее использовать готовый отсюда: https://wiki.osdev.org/GCC_Cross-Compiler#Prebuilt_Toolchains. Для компьютеров на Linux с x86_64 архитектурой:\n```\nwget http://newos.org/toolchains/i386-elf-4.9.1-Linux-x86_64.tar.xz\nmkdir /usr/local/i386elfgcc\ntar -xf i386-elf-4.9.1-Linux-x86_64.tar.xz -C /usr/local/i386elfgcc --strip-components=1\nexport PATH=$PATH:/usr/local/i386elfgcc/bin\n```\n3. Клонировать и собрать проект\n```\ngit clone https://github.com/thedenisnikulin/os-project\ncd os-project/src/build\nmake\n```\n4. Запустить образ ОС с помощью эмулятора\n```\nqemu-system-i386 -fda os-image.bin\n```\n\n## Справочник по синтаксису ассемблера NASM\nhttps://www.opennet.ru/docs/RUS/nasm/nasm_ru3.html\n\n\n---\n## Дополнительная информация\nСсылки на полезный материал которым я пользовался в качестве теории.\n> На русском языке:\n- Серия статей о ядре Linux и его внутреннем устройстве: https://github.com/proninyaroslav/linux-insides-ru\n- Статья \"Давай напишем ядро!\": https://xakep.ru/2018/06/18/lets-write-a-kernel/\n> На английском языке:\n- Небольшая книга по разработке собственной ОС (70 страниц): https://www.cs.bham.ac.uk/~exr/lectures/opsys/10_11/lectures/os-dev.pdf\n- Общее введене в разработку операционных систем: https://wiki.osdev.org/Getting_Started\n- Туториал по разработке ядра операционной системы для 32-bit x86 архитектуры. Первые шаги в создании собсвтенной ОС: https://wiki.osdev.org/Bare_Bones\n- Продолжение предыдущего туториала: https://wiki.osdev.org/Meaty_Skeleton\n- Про загрузку ОС (booting): https://wiki.osdev.org/Boot_Sequence\n- Список туториалов по написанию ядра и модулей к ОС: https://wiki.osdev.org/Tutorials\n- Внушительных размеров гайд по разработке ОС с нуля: http://www.brokenthorn.com/Resources/OSDevIndex.html\n- Книга, описывающая ОС xv6 (не особо вникал, но должно быть что-то годное): https://github.com/mit-pdos/xv6-riscv-book, сама ОС: https://github.com/mit-pdos/xv6-public\n- \"Небольшая книга о разработке операционных систем\" https://littleosbook.github.io/\n- Операционная система от 0 до 1 (книга): https://github.com/tuhdo/os01\n- ОС, написанная как пример для предыдущей книги: https://github.com/tuhdo/sample-os\n- Интересная статья про программирование модулей для Линукса и про системное программирование https://jvns.ca/blog/2014/09/18/you-can-be-a-kernel-hacker/\n- Еще одна статья от автора предыдущей https://jvns.ca/blog/2014/01/04/4-paths-to-being-a-kernel-hacker/\n- Пример простого модуля к ядру линукса: https://github.com/jvns/kernel-module-fun/blob/master/hello.c\n- Еще один туториал о том, как написать ОС с нуля: https://github.com/cfenollosa/os-tutorial\n- Статья \"Давайте напишем ядро\": https://arjunsreedharan.org/post/82710718100/kernels-101-lets-write-a-kernel\n- Сабреддит по разработке ОС: https://www.reddit.com/r/osdev/\n- Большой список идей для проектов для разных ЯП, включая C/C++: https://github.com/tuvtran/project-based-learning/blob/master/README.md\n- Еще один список идей для проектов https://github.com/danistefanovic/build-your-own-x\n- \"Давайте напишем ядро с поддержкой ввода с клавиатуры и экрана\": https://arjunsreedharan.org/post/99370248137/kernel-201-lets-write-a-kernel-with-keyboard-and \n"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex00/README.md",
    "content": "# Интро\n\nКогда мы включаем компьютер, он должен каким-то образом загрузить операционную систему. Он может загрузить ее с дискеты, с флэшки, с жесткого диска или с каих-либо других носителей.\nСреда, которую дает нам компьютер вне ОС, может предоставить нам не так уж и много. Например, мы имеем BIOS (англ. Basic Input/Output Software), набор програм которые изначально загружены в памят и инициализированы как только компьютер включается. БИОС предоставляет базовый контроль над важными девайсами компьютера: экран, клавиатура, жесткий диск.\nЧтобы БИОСу загрузить ОС, ему нужно узнать, есть ли ОС на определенном носителе. Для этого он читает первые 512 байтов носителя которые называются загрузочным сектором (англ. boot sector) и если они кончаются магическим числов 0xaa55,то он загружает код бут сектора в память, а процессор его исполняет.\n"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex00/main.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide: \t00-BOOT-SECTOR\n; File:\t\tex00 / main.asm\n; Title:\tПростая программа загрузочного сектора (boot sector), которая\n;\t\t\tзапускает бесконечный цикл.\n; ------------------------------------------------------------------------------\n; Description:\n;\tЧтобы дать понять BIOS, что на текущей флэшке, компакт диске или жестком\n;\tдиске расположена ОС, на этом носителе должен быть загрузочный сектор. BIOS\n;\tузнает загрузочный сектор по \"магическому числу\" из 2-х байт, равное 0xaa55\n;\t(в шестнадцатеричной системе исчисления). Простейший загрузочный сектор в\n;\tмашинном коде выглядит так:\n;\n; \t\te9 fd ff 00 00 00 00 00 00 00 00 00 00 00 00 00\n; \t\t00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n;\t   *\n; \t\t00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa\n;\n;\tКак видим, последние 2 байта действительно являются \"магическим числом\".\n;\tПервые 3 байта являются машинными инструкциями, которые запускают\n;\tбесконечный цикл, а остальное просто заполнено нулями т.к. наша программа\n;\tдолжна быть размером ровно 512 байт.\n;\n;\tЧтобы перевести наш файл boot_sect.asm в машинный код, нужно написать:\n;\tnasm boot_sect.asm -f bin -o boot_sect.bin\n;\tПопробуйте запустить boot_sect.bin с помощью эмулятора (например, qemu), и\n;\tвы увидите \"Booting from Hard Disk...\", хотя если вы измените \"магическое\n;\tчисло\", соберете файл boot_sect.bin заново и запустите с помощью эмулятора,\n;\tто BIOS попытается загрузить ОС, но в итоге напишет \"No bootable device\".\n;\tА значит, у нас получилось создать загрузочный сектор!\n; ------------------------------------------------------------------------------\n\n\t\t\t\t\t\t; Бесконечный цикл:\nloop:\t\t\t\t\t; Определяем метку \"loop\"\n\n\tjmp loop\t\t\t; Инструкция \"jmp\" позволяет нам перейти к метке\n\t\t\t\t\t\t; \"loop\" (англ. jump - прыжок), т.е. создается\n\t\t\t\t\t\t; бесконечный цикл.\n\ntimes 510-($-$$) db 0\t; Инструкция \"times\" заставляет идущую после нее\n\t\t\t\t\t\t; команду выполняться опредленное количество раз, т.е.\n\t\t\t\t\t\t; times <кол-во раз> <команда>.\n\t\t\t\t\t\t; Токен $ высчитывает позицию начала текущей строки,\n\t\t\t\t\t\t; токен $$ - позицию начала текущей секции.\n\t\t\t\t\t\t; 510-($-$$) = 510-(2-0) = 508 байт. Чтобы лучше понять\n\t\t\t\t\t\t; как это работает, рекомендую посмотреть как программа\n\t\t\t\t\t\t; выглядит в машинном коде с помощью утилиты ghex.\n\t\t\t\t\t\t; Таким образом мы заполняем пространство нулями (db 0),\n\t\t\t\t\t\t; приводя нас к 510-му байту.\n\t\t\t\t\t\t; db расшифровывается как \"declare bytes\", т.е.\n\t\t\t\t\t\t; \"объявить байты\"\n\ndw 0xaa55\t\t\t\t; В последние два байта кладем \"магическое число\",\n\t\t\t\t\t\t; чтобы BIOS знал, что это загрузочный сектор\n"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex01/main.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex01 / main.asm\n; Title:\tПростая программа загрузочного сектора (boot sector), которая \n;\t\t\tвыводит \"Hello\" на экран, используя рутину BIOS'а \n; ------------------------------------------------------------------------------\n; Description:\n;\tЧтобы вывести символ на экран, мы воспользуемся \"scrolling tele-type BIOS\n;\troutine\", то есть специальной рутиной BIOS, которая выводит символ на экран\n;\tи перемещает курсор, чтобы быть готовым напечатать следующий символ. Чтобы \n;\tвоспользоваться этой рутиной, нужно переместить в регистр ah число 0x0e, \n;\tа также использовать прерывания (синтаксис прерываний в ассемблере - \n;\t\"int <номер прерывания>\"). Прерывания - это механизм, с помощью которого \n;\tпроцессор может быть прерван от выполнения того, чем он сейчас занят, чтобы \n;\tвыполнить какую-то другую команду. Мы будем использовать прерывание 0x10 \n;\t(это число - индекс обработчика прерывания в ISR (interrupt service \n;\troutines). ISR это последовательность команд, ответсвенных за прерывание. \n;\tНам нужна команда под индексом 0x10), функцией которого является \n;\tпредоставление графических сервисов, вывод строк на экран и т.д.\n; ------------------------------------------------------------------------------\n\n\nmov ah, 0x0e\t\t\t; Перемещаем число 0x0e в регистр ah, указывая BIOS'у\n\t\t\t\t\t\t; что нам нужна рутина tele-type, то есть режим вывода \n\t\t\t\t\t\t; информации на экран\n\nmov al, 'H'\t\t\t\t; Перемещаем ASCII код символа 'H' в регистр al (команда\nint 0x10\t\t\t\t; mov), вызываем прерывание 0x10, которое выводит\nmov al, 'e'\t\t\t\t; на экран значение регистра al\nint 0x10\nmov al, 'l'\nint 0x10\nmov al, 'l'\nint 0x10\nmov al, 'o'\nint 0x10\n\n\t\t\t\t\t\t; ------------------------------------------------------\n\njmp $\t\t\t\t\t; Знак $ вычисляет позицию начала строки, содержащей\n\t\t\t\t\t\t; выражение, то есть мы прыгаем к началу этой же строки,\n\t\t\t\t\t\t; создавая бесконечный цикл\n\ntimes 510-($-$$) db 0\t; Заполняем ненужные байты нулями\n\ndw 0xaa55\t\t\t\t; Вставляем в конец \"магическое число\""
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex02/main.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex02 / main.asm\n; Title:\tПростая программа загрузочного сектора (boot sector), которая \n;\t\t\tдемонстрирует работу адресов\n; ------------------------------------------------------------------------------\n; Description: \n;\tНаша цель - разобраться как работать с памятью на языке Ассемблер и узнать,\n;\tпо какому адресу хранится загрузочный сектор (спойлер: по адресу 0x7c00).\n;\tДля этого мы попробуем вывести символ 'Х' на экран 4-мя разными способами.\n;\tПодготовка: если addr - адрес, то [addr] - значение на которое указывает \n;\tадрес\n; ------------------------------------------------------------------------------\n\n\nmov ah, 0x0e\t\t\t; Перемещаем число 0x0e в регистр ah, указывая BIOS'у\n\t\t\t\t\t\t; что нам нужна рутина tele-type, то есть режим вывода \n\t\t\t\t\t\t; информации на экран\n\nmov al, '1'\t\t\t\t; Выводим номер способа (нужно лишь нам для понятности)\nint 0x10\nmov al, the_secret\t\t; #1: перемещаем адрес the_secret.\nint 0x10\t\t\t\t; На экран выведется мусор, т.к. мы переместили в al \n\t\t\t\t\t\t; сам адрес, а не хранящееся в нем значение.\n\nmov al, '2'\t\t\t\t; Выводим номер способа\nint 0x10\nmov al, [the_secret]\t; #2: перемещаем значение по адресу the_secret.\nint 0x10\t\t\t\t; Может показаться вполне корректным, но на экран снова\n\t\t\t\t\t\t; выведется мусор, т.к the_secret объявлен не в том же\n\t\t\t\t\t\t; месте, что и наш загрузочный сектор, и поэтому мы не\n\t\t\t\t\t\t; сможем таким образом забрать из него 'X'.\n\nmov al, '3'\t\t\t\t; Выводим номер способа\nint 0x10\nmov bx, the_secret\t\t; #3: перемещаем адрес the_secret в регистр bx,\nadd bx, 0x7c00\t\t\t; добавляем к этому адресу адрес загрузочного сектора,\nmov al, [bx]\t\t\t; выводим значение по адресу bx.\nint 0x10\t\t\t\t; Вот теперь то мы и сможем вывести 'X'. Суть в том, что\n\t\t\t\t\t\t; чтобы мы смогли добраться до корректного значения\n\t\t\t\t\t\t; которое мы объявили, адрес этого значения должен быть\n\t\t\t\t\t\t; в пределах адресного пространства загрузочного \n\t\t\t\t\t\t; сектора. Т.е. мы должны прибавить к адресу the_secret\n\t\t\t\t\t\t; адрес, с которого начинается загрузочный сектор.\n\t\t\t\t\t\t; то есть если the_secret = 0x2d, то прибавляя к нему\n\t\t\t\t\t\t; адрес загрузочного сектора 0x7c00 мы получим 0x7c2d.\n\t\t\t\t\t\t; Такая техника называется смещением (англ. offset)\n\t\t\t\t\t\t; Именно по этому адресу мы сможем достать 'X'.\n\nmov al, '4'\t\t\t\t; Выводим номер способа\nint 0x10\nmov al, [0x7c2d]\t\t; #4: перемещаем значение адреса, который мы\nint 0x10\t\t\t\t; захардкодили (т.е. специально сами посчитали, заранее, \n\t\t\t\t\t\t; не программно) Таким способом у нас тоже получится \n\t\t\t\t\t\t; вывести 'X', но это плохой способ, т.к. адрес может \n\t\t\t\t\t\t; в итоге отличаться.\n\n\t\t\t\t\t\t; ------------------------------------------------------\n\njmp $\t\t\t\t\t; бесконечный цикл\n\nthe_secret:\t\t\t\t; Объявляем метку the_secret,\n\tdb 'X'\t\t\t\t; объявляем байт, и инициализируем его с ASCII кодом,\n\t\t\t\t\t\t; соответствующим символу 'X'\n\n\ntimes 510-($-$$) db 0\t; Заполняем ненужные байты нулями\n\ndw 0xaa55\t\t\t\t; Вставляем в конец \"магическое число\""
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex02/org_demo.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex02 / org_demo.asm\n; Title:\tПростая программа загрузочного сектора (boot sector), которая \n;\t\t\tдемонстрирует работу адресов, используя директиву [org <адрес>]\n; ------------------------------------------------------------------------------\n; Description:\n;\tМы выяснили в предыдущем упражнении, что чтобы процессор корректно нашел \n;\tобъявленное нами значение, нужно добавлять 0x7c00 к адресу этого значения \n;\t(т.е. \"сместить\" адрес на 0x7c00, англ. offset - смещение). Это довольно \n;\tнеудобно, и поэтому ассемблер позволяет нам указать \"глобальное смещение\" с \n;\tпомощью директивы [org <адрес>]. Посмотрим как это работает.\n; ------------------------------------------------------------------------------\n\n\n[org 0x7c00]\t\t\t; Указываем \"глобальное смещение\"\n\nmov ah, 0x0e\t\t\t; Перемещаем число 0x0e в регистр ah, указывая BIOS'у\n\t\t\t\t\t\t; что нам нужна рутина tele-type, то есть режим вывода \n\t\t\t\t\t\t; информации на экран\n\nmov al, '1'\t\t\t\t; Выводим номер способа для удобства\nint 0x10\nmov al, the_secret\t\t; #1: перемещаем адрес the_secret.\nint 0x10\t\t\t\t; На экран выведется мусор, т.к. мы переместили в al \n\t\t\t\t\t\t; сам адрес, а не хранящееся в нем значение.\n\nmov al, '2'\t\t\t\t; Выводим номер способа\nint 0x10\nmov al, [the_secret]\t; #2: перемещаем значение по адресу the_secret.\nint 0x10\t\t\t\t; В этот раз мы сможем вывести 'X', т.к. [the_secret]\n\t\t\t\t\t\t; будет разыменован по адресу the_secret + 0x7c00\n\t\t\t\t\t\t; благодаря \"глобальному смещению\"\n\nmov al, '3'\t\t\t\t; Выводим номер способа\nint 0x10\nmov bx, the_secret\t\t; #3: перемещаем адрес the_secret в регистр bx,\nadd bx, 0x7c00\t\t\t; добавляем к этому адресу адрес загрузочного сектора,\nmov al, [bx]\t\t\t; выводим значение по адресу bx.\nint 0x10\t\t\t\t; Мы не сможем вывести 'X', т.к. лишний раз делаем\n\t\t\t\t\t\t; смещение: 0x7c00 + the_secret + 0x7c00\n\nmov al, '4'\t\t\t\t; Выводим номер способа\nint 0x10\nmov al, [0x7c2d]\t\t; #4: перемещаем значение адреса, который мы\nint 0x10\t\t\t\t; захардкодили (т.е. специально сами посчитали, заранее, \n\t\t\t\t\t\t; не программно) Таким способом у нас тоже получится \n\t\t\t\t\t\t; вывести 'X', но это плохой способ, т.к. адрес может \n\t\t\t\t\t\t; в итоге отличаться.\n\n\t\t\t\t\t\t; ------------------------------------------------------\n\njmp $\t\t\t\t\t; бесконечный цикл\n\nthe_secret:\t\t\t\t; Объявляем метку the_secret,\n\tdb 'X'\t\t\t\t; объявляем байт, и инициализируем его с ASCII кодом,\n\t\t\t\t\t\t; соответствующим символу 'X'\n\n\ntimes 510-($-$$) db 0\t; Заполняем ненужные байты нулями\n\ndw 0xaa55\t\t\t\t; Вставляем в конец \"магическое число\""
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex03/main.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex03 / main.asm\n; Title:\tПростая программа загрузочного сектора, которая демонстрирует \n;\t\t\tработу стека\n; ------------------------------------------------------------------------------\n; Description:\n;\tЦель: разобраться как работать со стеком на языке Ассемблер.\n;\tТеория:\n;\t1. Стек - структура данных, которая действует по принциу LIFO (last in first\n;\tout). Со стеком можно провести две операции: добавить в стек элемент\n;\t(добавлять можно только в конец) и взять элемент из стека (взять можно \n;\tтоже только с конца). О стеке удобно думать как о стопке блинов.\n;\t2. Регистры для работы со стеком: BP и SP. BP содержит адрес начала стека,\n;\tа SP - адрес конца стека (т.е. начала стопки блинов и ее макушка соответсв.)\n;\t3. Стек, с которым мы будем работать, \"растет\" вниз, то есть мы добавляем \n;\tзначения в стек по адресу меньше конца стека. Допустим, если адрес конца \n;\tстека находится в регистре SP и равен 0x8000, то добавляя что-нибудь в стек, \n;\tзначение регистра SP станет 0x8000 - 0x2 (если размер этого \"чего-нибудь\"\n;\tравен 2 байтам).\n;\t4. Операции со стеком: \"push <значение>\" чтобы добавить какое-либо значение \n;\tв конец стека, \"pop <регистр>\" чтобы удалить последнее значение из стека и \n;\tдобавить его в указанный регистр.\n; ------------------------------------------------------------------------------\n\n\nmov ah, 0x0e\t\t\t; Перемещаем число 0x0e в регистр AH, указывая BIOS'у\n\t\t\t\t\t\t; что нам нужна рутина tele-type, то есть режим вывода \n\t\t\t\t\t\t; информации на экран\n\nmov bp, 0x8000\t\t\t; BP - регистр адреса начала стека, а SP - конца стека\nmov sp, bp\t\t\t\t; мы размещаем стек чуть выше адреса, из которого БИОС\n\t\t\t\t\t\t; загружает наш загрузочный сектор (в адресе 0x8000),\n\t\t\t\t\t\t; чтобы случайно не задеть загрузочный сектор.\n\t\t\t\t\t\t; Переносим адрес BP в SP, т.к. изначально стек пустой,\n\t\t\t\t\t\t; и поэтому адрес конца стека равен адресу начала\n\npush 'A'\t\t\t\t; Добавляем в стек символы\npush 'B'\npush 'C'\n\n\npop bx\t\t\t\t\t; Переносим последнее значение из стека в регистр BX\n\t\t\t\t\t\t; (помним, что значение sp увеличится, а не уменьшится, \n\t\t\t\t\t\t; т.к. стек \"растет\" вниз)\nmov al, bl\t\t\t\t; в bl находится значение*, взятое из стека с помощью \n\t\t\t\t\t\t; команды pop. Перемещаем его в регистр AL\nint 0x10\t\t\t\t; Выводим 'C'\n\n\npop bx\t\t\t\t\t; Переносим в регистр BX следующее значение из стека\nmov al, bl \nint 0x10\t\t\t\t; Выводим 'B'\n\n\nmov al, [0x8000 - 0x2]\t; Переносим в регистр AL значение по адресу 0x7ffe\n\t\t\t\t\t\t; (0x8000 - 0x2), т.е. адрес начала стека минус 2 байта,\n\t\t\t\t\t\t; для того чтобы подтвердить, что стек \"растет\" вниз\nint 0x10\t\t\t\t; Выводим 'A'\n\n\t\t\t\t\t\t; ------------------------------------------------------\n\njmp $\t\t\t\t\t; бесконечный цикл\n\nthe_secret:\t\t\t\t; Объявляем метку the_secret,\n\tdb 'X'\t\t\t\t; объявляем байт, и инициализируем его с числом,\n\t\t\t\t\t\t; соответствующим символу 'X' в таблице ASCII\n\n\ntimes 510-($-$$) db 0\t; Заполняем ненужные байты нулями\n\ndw 0xaa55\t\t\t\t; Вставляем в конец \"магическое число\"\n\n; ------------------------------------------------------------------------------\n; * почему BL, а не BX? Регистр bx устроен так, что состоит из двух других\n;\tрегистров: BH и BL (оба размером 8 байт, а размер bx соответственно 16 байт)\n;\tПодобным образом же устроен и регистр AX, т.е. AH и AL. Т.к. мы используем\n;\tрегистр AL, равный 8 байтам, чтобы выводить символы на экран, то нам нужно \n;\tперемещать в этот регистр значение из регистра такого же размера. Поэтому\n;\tмы перемещаем туда значение из BL. (гугли: регистры в ассемблере)\n"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex04/main.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex04 / main.asm\n; Title:\tПростая программа загрузочного сектора, которая выводит на экран \n;\t\t\tстроку с помощью функции\n; ------------------------------------------------------------------------------\n; Description:\n;\tКратко о том, как работают функции:\n;\tЧтобы вызвать функцию, используется команда call <метка>. Чтобы вернуть\n;\t(выйти) из функции, используется команда ret.\n;\tКоманды call и ret используются в паре. Команда call помещает значение \n;\tрегистра EIP (в нем установлен адрес, следующий после call <метка> \n;\tинструкции) в стек, а команда ret извлекает его и передаёт управление \n;\tинструкции по этому адресу. Вы также можете определить аргументы, для \n;\tвызываемой функции. Это можно сделать с помощью стека или регистров, т.е. \n;\tперед вызовом функции занести параметры в стек или в определенный регистр, \n;\tа в теле функции извлечь их.\n; ------------------------------------------------------------------------------\n\n\n[org 0x7c00]\n\nmov bx, HELLO_MSG\t\t\t\t; Перемещаем объявленную нами ниже строку в\n\t\t\t\t\t\t\t\t; регистр BX. В ассемблере строки представляют\n\t\t\t\t\t\t\t\t; из себя то же, что и строки в Си - идущие друг\n\t\t\t\t\t\t\t\t; за другом ячейки памяти, в которых занесено \n\t\t\t\t\t\t\t\t; значение символов этой строки, поэтому в BX\n\t\t\t\t\t\t\t\t; в данный момент находится первый символ строки\n\t\t\t\t\t\t\t\t; (а точнее его ASCII код).\n\ncall print_string\t\t\t\t; Вызываем функцию print_string с помощью\n\t\t\t\t\t\t\t\t; команды call\n\nmov bx, GOODBYE_MSG\ncall print_string\n\n\t\t\t\t\t\t\t\t; ------------------------------------------------------\n\njmp $\t\t\t\t\t\t\t; бесконечный цикл\n\n%include \"print_string.asm\"\t\t; директива %include вставляет весь код из\n\t\t\t\t\t\t\t\t; файла print_string.asm в место, откуда она\n\t\t\t\t\t\t\t\t; была вызвана.\n\nHELLO_MSG:\n\tdb \"Hello, world!\", 0\t\t; Объявляем байты, содержащие строку с \n\t\t\t\t\t\t\t\t; приветствием, заканчивающуюся нулем.\n\t\t\t\t\t\t\t\t; На самом деле эти байты размещаются прямо в\n\t\t\t\t\t\t\t\t; исполняемом файле, откомпилированном с помощью\n\t\t\t\t\t\t\t\t; nasm. Вот так вот.\n\nGOODBYE_MSG:\t\t\t\t\t; Объявляем метку GOODBYE_MSG\n\tdb \"Goodbye!\", 0\t\t\t; Объявляем массив из символом, заканчивающийся\n\t\t\t\t\t\t\t\t; нулем (гугли: нуль терминированная строка)\n\ntimes 510-($-$$) db 0\t\t\t; Заполняем оставшиеся байты нулями\ndw 0xaa55\t\t\t\t\t\t; Вставляем в конец \"магическое число\"\n"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex04/print_string.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex04 / print_string.asm\n; Title:\tФункция вывода строки на экран\n; ------------------------------------------------------------------------------\n; Description: null\n; ------------------------------------------------------------------------------\n\n\nprint_string:\t\t\t; Функция вывода строки на экран.\n\tpusha\t\t\t\t; Когда мы используем функции, мы можем модифицировать\n\t\t\t\t\t\t; регистры прямо в них, что нарушает чистоту функции,\n\t\t\t\t\t\t; т.е. мы можем перезаписывать какие-то внешние\n\t\t\t\t\t\t; данные. Для этого мы добавляем значение всех регистров\n\t\t\t\t\t\t; в стек с помощью команды pusha, а в конце функции\n\t\t\t\t\t\t; мы возвращаем регистрам их изначальные значения,\n\t\t\t\t\t\t; которые возьмем из стека (команда popa).\n\t\t\t\t\t\t\n\tmov ah, 0x0e\t\t; tele-type mode\n\nloop:\t\t\t\t\t; Метка loop (= цикл)\n\tmov al, [bx]\t\t; Перемещаем значение BX в AL, т.к. мы помним что в BX\n\t\t\t\t\t\t; лежит первый символ строки (см. ./main.asm)\n\tcmp al, 0\t\t\t; Команда cmp для сравнения AL и 0.\n\tje return\t\t\t; (if) je = \"jump if equal\",\n\t\t\t\t\t\t; т.е. перемещаемся к коду с меткой return если AL == 0\n\tjmp put_char\t\t; (else) в противном случае перемещаемся к put_char.\n\nput_char:\t\t\t\t; Метка put_char - вывод символа на экран.\n\tint 0x10\t\t\t; Вызываем прерывание, которое позволяет вывести \n\t\t\t\t\t\t; на экран значение регистра AL, в котором лежит [BX].\n\tinc bx\t\t\t\t; inc <регистр> - увеличить на 1.\n\tjmp loop\t\t\t; Возвращаемся обратно к циклу.\n\nreturn:\t\t\t\t\t; Метка return - завершаем функцию\n\tpopa\t\t\t\t; Возвращаем регистрам их изначальные значения.\n\tret\t\t\t\t\t; Заканчиваем выполнение функции.\n"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex05/main.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex05 / main.asm\n; Title:\tПростая программа загрузочного сектора, которая выводит на экран \n;\t\t\tшестнадцатеричное число (число 0x1fb6)\n; ------------------------------------------------------------------------------\n; Description: null\n; ------------------------------------------------------------------------------\n\n\n[org 0x7c00]\n\nmov dx, 0x1fb6\t\t\t\t\t; Перемещаем 0x1fb6 в регистр DX чтобы потом\n\t\t\t\t\t\t\t\t; задействовать его в функции print_hex\nmov bx, HEX_OUT\t\t\t\t\t; перемещаем HEX_OUT в BX\n\ncall print_hex\t\t\t\t\t; Вызываем функцию print_hex\n\n\t\t\t\t\t\t\t\t; ----------------------------------------------\n\njmp $\t\t\t\t\t\t\t; бесконечный цикл\n\n%include \"../ex04/print_string.asm\"\t; директива %include вставляет весь код из\n%include \"print_hex.asm\"\t\t\t; файла print_string.asm в место, откуда она\n\t\t\t\t\t\t\t\t\t; была вызвана.\n\nHEX_OUT:\t\t\t\t\t\t; Объявляем метку HEX_OUT. Она нужна как шаблон,\n\t\t\t\t\t\t\t\t; на котором мы будем отображать наше число.\n\tdb \"0x0000\", 0\t\t\t\t; Объявляем массив из символом, заканчивающийся\n\t\t\t\t\t\t\t\t; нулем (гугли: нуль терминированная строка)\n\ntimes 510-($-$$) db 0\t\t\t; Заполняем ненужные байты нулями\ndw 0xaa55\t\t\t\t\t\t; Вставляем в конец \"магическое число\"\n"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex05/print_hex.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex05 / print_hex.asm\n; Title:\tФункция вывода шестнадцатеричного числа на экран\n; ------------------------------------------------------------------------------\n; Description: \n;\tКак выглядит регистр EAX (32 b = 32 бита):\n;\n;\t\t\t\t\t\tEAX (32 b)\n;\t\t---------------------------------------------\n;\t\t|\t\t\t\t\t|\t\t\t|\t\t\t|\n;\t\t|\t\t\t\t\t|  AH (8 b)\t|  AL (8 b) |\n;\t\t|\t\t\t\t\t|\t\t\t|\t\t\t|\n;\t\t---------------------------------------------\n;\t\t\t\t\t\t\t\t\tAX (16 b)\n;\n;\tКак видим, eax содержит в себе регистр AX, который в свою очередь разделен \n;\tна AH (A HIGH, верхний) и AL (a low, нижний).\n; ------------------------------------------------------------------------------\n\n\n; Помним, что в main.asm:\n; DX  =  0x1fb6\n; BX  =  \"0x0000\" \n\nprint_hex:\n\tpusha\t\t\t\t\t\t; Сохраняем значения регистров в стеке\n\tmov cx, 0\t\t\t\t\t; Регистр CX будет служить счетчиком\n\nloop1:\n\tcmp cx, 4\t\t\t\t\t; if (CX < 4)\n\tjl print\t\t\t\t\t; Переходим к print\n\tjmp end\t\t\t\t\t\t; else переходим к end\n\nprint:\n\tmov ax, dx\t\t\t\t\t; В AX теперь 0x1fb6\n\tand ax, 0x000f\t\t\t\t; В AX теперь 6 (последняя цифра от 0x1fb6).\n\tcmp ax, 9\t\t\t\t\t; if (AX > 9) (проверяем обозначается ли число\n\t\t\t\t\t\t\t\t; буквой т.к. мы помним что цифра больше 9 в \n\t\t\t\t\t\t\t\t; 16-ричной системе исчисления обозначается\n\t\t\t\t\t\t\t\t; буквой латинского алфавита)\n\tjg num_to_abc\t\t\t\t; Переходим к num_to_abc\n\tjmp next\n\nnum_to_abc:\t\t\t\t\t\t; Перевод числа в букву (так, как она бы\n\t\t\t\t\t\t\t\t; выглядела в 16-ричной СИ), например число 15\n\t\t\t\t\t\t\t\t; в десятичной будет равно f в 16-ричной\n\tadd ax, 39\t\t\t\t\t; Добавляем к этому числу 39, чтобы затем еще\n\t\t\t\t\t\t\t\t; добавить 48 ('0'), получая код \n\t\t\t\t\t\t\t\t; соответсвующего символа в ASCII (например,\n\t\t\t\t\t\t\t\t; f (как число, то есть 15) + 39 + 48 (код '0')\n\t\t\t\t\t\t\t\t; = 102 (то есть 'f' в ASCII, как нам и нужно)\n\tjmp next\n\nnext:\n\tadd ax, '0'\t\t\t\t\t; Добавляем 48 в AX\n\tmov bx, HEX_OUT + 5\t\t\t; Теперь bx указывает на последний символ строки\n\t\t\t\t\t\t\t\t; HEX_OUT\n\tsub bx, cx\t\t\t\t\t; BX = BX - counter (для итерации)\n\tmov [bx], al\t\t\t\t; Так как мы разыменовываем BX (вот так: [BX]),\n\t\t\t\t\t\t\t\t; то [BX] это не регистр, а ссылка на память,\n\t\t\t\t\t\t\t\t; и так как AX = 16 бит (2 байта), чтобы нам не\n\t\t\t\t\t\t\t\t; перезаписать лишнюю память, в [BX] мы помещаем\n\t\t\t\t\t\t\t\t; не AX, а AL, размер которого равен 1-му байту.\n\tror dx, 4\t\t\t\t\t; было: 0x1fb6, стало: 0x61fb (переносим\n\t\t\t\t\t\t\t\t; последнюю цифру в начало)\n\tinc cx\t\t\t\t\t\t; counter++\n\tjmp loop1\t\t\t\t\t; переходим обратно к loop1\n\nend:\n\tmov bx, HEX_OUT\t\t\t\t; Делаем так, чтобы bx снова указывал на первый\n\t\t\t\t\t\t\t\t; символ строки HEX_OUT\n\tcall print_string\t\t\t; выводи на экран строку из регистра BX\n\tpopa\t\t\t\t\t\t; Возвращаем регистрам их изначальное значение\n\tret\t\t\t\t\t\t\t; Заканчиваем выполнение функции\n"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex06/main.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex06 / main.asm\n; Title:\tПростая программа загрузочного сектора (boot sector), которая \n;\t\t\tдемонстрирует работу сегментации\n; ------------------------------------------------------------------------------\n; Description:\n;\tКогда процессор запущен в его начальном режиме (16-bit real mode), \n;\tмаксимальный размер регистров = 16 бит, поэтому самый большой размер, \n;\tкоторый мы можем использовать это 0xffff, который равен примерно 64 KB.\n;\tЧтобы преодолеть этот лимит, существуют специальные регистры, которые \n;\tназываются регистры сегментов - CS, DS, SS, ES, означающие Code, Data, Stack\n;\tи Extra соответственно.\n;\tПамять разделена на сегменты, которые индексированы регистрами сегментов\n;\t(т.е. к примеру в регистре DS лежит адрес начала Data-сегмента). Поэтому, \n;\tкогда мы указываем какой-либо 16-битный адрес, процессор автоматически\n;\tвысчитывает абсолютный адрес, сдвигая указанный нами адрес от начала нужного\n;\tсегмента.\n;\tВысчитывая абсолютный адрес, процессор умножает на 16 значение в регистре \n;\tсегмента и добавляет указанный нами адрес. Например, если мы установим \n;\tзначение сегмента DS как 0x4d и попробуем сделать что-то вроде \n;\t\"mov ax, [0x20]\", то значение, добавляемое в AX, будет загружено \n;\tиз адреса 0x4f0 (16 * 0x4d + 0x20).\n;\tКак можно догадаться, с помощью сегментации мы можем добиться того же, что и\n;\tс помощью директивы [org <адрес>], как мы делали в ex02/org_demo.asm.\n; ------------------------------------------------------------------------------\n\n\nmov ah, 0x0e\t\t\t; Перемещаем число 0x0e в регистр AH, указывая BIOS'у\n\t\t\t\t\t\t; что нам нужна рутина tele-type, то есть режим вывода \n\t\t\t\t\t\t; информации на экран\n\nmov al, '1'\t\t\t\t; Выводим номер способа для удобства\nint 0x10\nmov al, [the_secret]\t; #1: так как мы не указывали никакого смещения ни с\nint 0x10\t\t\t\t; помощью org директивы ни с помощью сегментации,\n\t\t\t\t\t\t; 'X' не выведется\n\nmov al, '2'\t\t\t\t; Выводим номер способа\nint 0x10\nmov bx, 0x7c0\t\t\t; т.к. мы не можем напрямую написать \"mov ds, 0x7c0\",\nmov ds, bx\t\t\t\t; используем BX как промежуточное хранилище\nmov al, [the_secret]\t; 'X' выведется, так как мы сделали смещение.\nint 0x10\t\t\t\t; Стоит заметить, что мы переводим в DS 0x7c0, а не \n\t\t\t\t\t\t; 0x7c00, так как помним, что значение, перемещаемое в\n\t\t\t\t\t\t; AL будет загружено из адреса, который будет\n\t\t\t\t\t\t; высчитываться как 16 * ds + the_secret,\n\t\t\t\t\t\t; а 16 * 0x7c0 == 0x7c00, то есть так как нам и нужно.\n\nmov al, '3'\t\t\t\t; Выводим номер способа\nint 0x10\nmov al, [es:the_secret]\t; Явно указываем процессору, использовать сегмент ES\nint 0x10\t\t\t\t; (просто для демонстрации возможностей и чтобы понять\n\t\t\t\t\t\t; как это работает)\n\t\t\t\t\t\t; 'X' не выведется, т.к. мы не переместили в регистр ES\n\t\t\t\t\t\t; число 0x7c0, и поэтому смещение на адрес 16 * 0x7c0 \n\t\t\t\t\t\t; выполнено не будет.\n\nmov al, '4'\t\t\t\t; Выводим номер способа\nint 0x10\nmov bx, 0x7c0\t\t\t; Используя BX как промежуточное хранилище, перемещаем\nmov es, bx\t\t\t\t; в регистр ES 0x7c0.\nmov al, [es:the_secret]\t; Явно указываем процессору сегмент ES\nint 0x10\t\t\t\t; 'X' выведется.\n\n\t\t\t\t\t\t; ------------------------------------------------------\n\njmp $\t\t\t\t\t; бесконечный цикл\n\nthe_secret:\n\tdb 'X'\n\ntimes 510-($-$$) db 0\t; Заполняем ненужные байты нулями\ndw 0xaa55\t\t\t\t; Вставляем в конец \"магическое число\"\n"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex07/disk_load.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / disk_load.asm\n; Title:\tФункция чтения диска\n; ------------------------------------------------------------------------------\n; Description:\n;\tЧтобы лучше понять, что здесь происходит, разберитесь с тем, что такое CHS\n;\tпо ссылке https://ru.wikipedia.org/wiki/CHS\n; ------------------------------------------------------------------------------\n\n\ndisk_load:\n\tpush dx\n\tmov ah, 0x02\t\t\t; Указвыаем БИОСу что нам нужна рутина чтения диска\n\t\t\t\t\t\t\t; Указываем что нам нужно:\n\tmov al, dh\t\t\t\t; 1. Прочитать кол-во секторов, равное значению в DH\n\tmov ch, 0x00\t\t\t; 2. Выбрать нулевой цилиндр\n\tmov dh, 0x00\t\t\t; 3. Выбрать нулевую головку\n\tmov cl, 0x02\t\t\t; 4. Начинать считывать со второго сектора (т.е.\n\t\t\t\t\t\t\t; первый свободный сектор сразу после загруочного \n\t\t\t\t\t\t\t; сектора, т.к. загрузочный сектор находится по \n\t\t\t\t\t\t\t; адресу 0x01)\n\tint 0x13\t\t\t\t; Вызываем прерывание для чтения\n\n\t\t\t\t\t\t\t; У БИОСа может не получиться прочитать диск, и\n\t\t\t\t\t\t\t; чтобы дать нам знать что произошла ошибка, он,\n\t\t\t\t\t\t\t; во-первых, обновляет специальный флаг CF (carry \n\t\t\t\t\t\t\t; flag) специальным значением, которое означает \n\t\t\t\t\t\t\t; ошибку, а во-вторых, кладет в регистр AL кол-во \n\t\t\t\t\t\t\t; секторов, которые у него получилось прочитать.\n\t\n\tjc disk_error\t\t\t; jc - инструкция для прыжка на указанную метку,\n\t\t\t\t\t\t\t; которая выполняется только если CF (carry flag)\n\t\t\t\t\t\t\t; сигнализирует об ошибке\n\n\tpop dx\t\t\t\t\t; Восстанавливаем регистр DX из стека\n\tcmp dh, al\t\t\t\t; если AL (кол-во прочитанных секторов) != DH\n\t\t\t\t\t\t\t; (предполагаемое кол-во секторов),\n\tjne disk_sectors_error\t; то выводим на экран сообщение об ошибке и зависаем\n\t\t\t\t\t\t\t; (то есть запускаем бесконечный цикл)\n\tjmp disk_success\n\tjmp disk_exit\t\t\t; Заканчиваем выполнение функции\n\ndisk_success:\n\tmov bx, SUCCESS_MSG\n\tcall print_string\n\tjmp disk_exit\n\ndisk_error:\n\tmov bx, DISK_ERR_MSG\t; Перемещаем в BX сообщение об ошибке\n\tcall print_string\t\t; Выводим его на экран\n\tmov dh, al\n\tcall print_hex\n\tjmp disk_loop\t\t\t; бесконечный цикл\n\ndisk_sectors_error:\n\tmov bx, SECTORS_ERR_MSG\n\tcall print_string\n\nSUCCESS_MSG:\n\tdb \"Disk was successfully read \", 0\n\nDISK_ERR_MSG:\n\tdb \"Disk read error! \", 0\n\nSECTORS_ERR_MSG:\n\tdb \"Incorrect number of sectors read \", 0\n\ndisk_loop:\n\tjmp $\n\ndisk_exit:\n\tret\n"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex07/main.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex07 / main.asm\n; Title:\tПростая программа загрузочного сектора, которая демонстрирует \n;\t\t\tчтение с диска\n; ------------------------------------------------------------------------------\n; Description: Операционная система не уберется в 512 байтов, поэтому нам нужно\n;\t\t\t\tуметь прочитать что-то с диска.\n; ------------------------------------------------------------------------------\n\n\n[org 0x7c00]\n\nmov bp, 0x8000\t\t\t\t\t\t; Распологаем наш стек подальше в безопасное\nmov sp, bp\t\t\t\t\t\t\t; место\n\nmov bx, 0x9000\t\t\t\t\t\t; Данные из секторов будут загружаться в\n\t\t\t\t\t\t\t\t\t; адрес 0x0000(ES):0x9000(BX), т.е.\n\t\t\t\t\t\t\t\t\t; (ES * 16 + BS), равный 0x90000\n\nmov dh, 2\t\t\t\t\t\t\t; Загрузим 2 сектора\n\ncall disk_load\t\t\t\t\t\t; Загружаем диск\n\nmov dx, [0x9000]\t\t\t\t\t; Выводим на экран первое загрузившееся\ncall print_hex\t\t\t\t\t\t; \"слово\" (т.е. машинное слово = 2 байта)\n\t\t\t\t\t\t\t\t\t; предполагая, что оно будет равно 0xdada\n\t\t\t\t\t\t\t\t\t; (распологается по адресу 0x9000)\n\nmov dx, [0x9000 + 512]\t\t\t\t; Выводим на экран первое \"слово\" из 2-го\n\t\t\t\t\t\t\t\t\t; загруженного нами сектора. Должно быть\n\t\t\t\t\t\t\t\t\t; равно 0xface\ncall print_hex\n\njmp $\n\n%include \"../ex04/print_string.asm\"\t; Функция печати строки\n%include \"../ex05/print_hex.asm\"\t; Функция печати 16-ричного числа\n%include \"disk_load.asm\"\t\t\t; Функция чтения диска\n\nHEX_OUT:\n\tdb \"0x0000\", 0\n\ntimes 510-($-$$) db 0\ndw 0xaa55\n\n; БИОС загрузит только первые 512 байтов с диска, поэтому если мы специально\n; добавим пару секторов (тоже по 512 байт), мы сможем убедиться в том что у \n; нас получилось загрузить эти самые сектора. \n; TODO: explain better.\n\ntimes 256 dw 0xdada\t\t\t\t\t; второй сектор\ntimes 256 dw 0xface\t\t\t\t\t; третий сектор\n"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex08/gdt.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / gdt.asm\n; Title:\tОпределяем GDT (глобальная таблица дескрипторов)\n; ------------------------------------------------------------------------------\n; Description:\n; \tСпособ, которым процессор переводит логический адрес в физический, в\n; \t32-битном защищенном режиме отличается от 16-битного реального режима.\n; \tВместо того, чтобы умножить значение регистра сегмента на 16 и прибавить к\n; \tэтому \"смещение\" (offset), регистр сегмента становится индексом \n; \tопределенного дескриптора сегмента в GDT.\n; \tДескриптор сегмента - это 8-битная структура, которая определяет свойства\n; \tэтого сегмента:\n; \t\t- Base address (32 bits), определяющий откуда сегмент начинается в\n; \t\tфизической памяти.\n; \t\t- Segment Limit (20 bits), определяющий размер сегмента\n;\t\t- Различные флаги, которые устанавливают каким образом процессор будет\n; \t\t\"относиться\" к сегментам, например уровень привилегий и т.д.\n;\n; \tФлаги:\n;\t* 1-ые флаги:\n; \t\t- present flag (флаг присутствия). Если его значение \"1\", то это \n;\t\tуказывает, что сегмент присутствует в памяти (это нужно для виртуальной \n;\t\tпамяти)\n;\t\t- privilege flag (флаг привилегии). Значение \"0\" - самый высокий уровень\n;\t\tпривилегии\n;\t\t- descriptor type (тип дескриптора). \"1\" - для сегмента кода или \n;\t\tсегмента данных\n; \t* Флаги типа:\n;\t\t- code (флаг кода). \"1\" - для кода, \"0\" - для даннных\n;\t\t- conformig (флаг подчинения). \"0\" - чтобы код в другом сегменте с\n;\t\tболее низким уровнем привилегий не смог вызвать код из этого сегмента - \n;\t\tэто ключ к защите памяти (memory protection).\n;\t\t- readable (читаемость). \"1\" - если читаемый, \"0\" - только исполняемый.\n;\t\t- writable. Разрешает сегменту данных быть записываемым, в противном\n;\t\tслучае, он будет доступен только для чтения.\n;\t\t- accessed (флаг доступа). Этот флаг устанавливается, когда происходит\n; \t\tобращение к сегменту.\n;\t\t- expand down. Флаг (бит), позволяющий сегменту расширяться вниз.\n;\t* 2-ые флаги:\n;\t\t- granulariy (гранулярность). \"0\" - байтовая гранулярность, лимит\n; \t\tзадается в байтах, если \"1\" - страничная гранулярность, в 4кб блоках.\n;\t\tЕсли выбрать страничную гранулярность и установить значение лимита как\n;\t\t0xfffff, то лимит умножится на 16*16*16 (4кб), и лимит станет 0xfffff000\n; \t\tпозволяя нашему сегменту занять 4гб места в памяти.\n;\t\t- 32-bit default. \"1\" - т.к. наш сегмент будет содержать 32-битный код.\n;\t\t- 64-bit code segment. \"0\" - т.к. не используется на 32-битных \n;\t\tпроцессорах.\n;\t\t- AVL (available). Определяет доступность сегмента для использования \n;\t\tсистемным программным обеспечением (используются только ОС).\n; ------------------------------------------------------------------------------\n\ngdt_start:\t\t\t\t\t; Эта пустая метка нужно для того чтобы удобнее\n\t\t\t\t\t\t\t; посчитать размер GDT для ее дескриптора \n\t\t\t\t\t\t\t; (end - start)\n\ngdt_null:\t\t\t\t\t; Необходимый нулевой дескриптор для GDT\n\tdd 0x0\t\t\t\t\t; dd - define double (двойное \"слово\"\", т.е.\n\tdd 0x0\t\t\t\t\t; 4 байта).\n\ngdt_code:\t\t\t\t\t; Определяем дескриптор сегмента кода\n\tdw 0xffff\t\t\t\t; Limit (bits 0-15)\n\tdw 0x0\t\t\t\t\t; Base (bits 0-15)\n\tdb 0x0\t\t\t\t\t; Base (bits 16-23)\n\tdb 10011010b\t\t\t; Первые флаги + флаги типа (смотрим по битам)\n\t\t\t\t\t\t\t; present: 1, privilege: 00, descriptor type: 1\n\t\t\t\t\t\t\t; code: 1, conforming: 0, readable: 1, accessed: 0\n\tdb 11001111b\t\t\t; Вторые флаги + длина сегмента (bits 16-19):\n\t\t\t\t\t\t\t; granularity: 1, 32-bit default: 1,\n\t\t\t\t\t\t\t; 64-bit default: 0, AVL: 0\n\tdb 0x0\t\t\t\t\t; Base (bits 24-31)\n\ngdt_data:\t\t\t\t\t; Определяем дескриптор сегмента кода\n\tdw 0xffff\t\t\t\t; Limit (bits 0-15)\n\tdw 0x0\t\t\t\t\t; Base (bits 0-15)\n\tdb 0x0\t\t\t\t\t; Base (bits 16-23)\n\tdb 10010010b\t\t\t; Первые флаги + флаги типа (смотрим по битам)\n\tdb 11001111b\t\t\t; Вторые флаги + длина сегмента (bits 16-19)\n\tdb 0x0\t\t\t\t\t; Base (bits 24-31)\n\ngdt_end:\t\t\t\t\t; Пустая метка\n\n\ngdt_descriptor:\t\t\t\t\t\t; дескриптор GDT\n\tdw gdt_end - gdt_start - 1\t\t; Размер GDT\n\tdd gdt_start\t\t\t\t\t; Адрес начала GDT\n\n\nCODE_SEG equ gdt_code - gdt_start\t; Определяем некоторые константы. \nDATA_SEG equ gdt_data - gdt_start\t; Они понадобятся для регистров сегментов в\n\t\t\t\t\t\t\t\t\t; 32-битном защищенном режиме. Например,\n\t\t\t\t\t\t\t\t\t; когда мы установим регистр DS = 0x10 (т.е\n\t\t\t\t\t\t\t\t\t; 16 байтов) в этом режиме, процессор\n\t\t\t\t\t\t\t\t\t; поймет что мы хотим использовать сегмент,\n\t\t\t\t\t\t\t\t\t; находящийся в смещении 0x10 в GDT,\n\t\t\t\t\t\t\t\t\t; т.е. в нашем случае это сегмент данных\n\t\t\t\t\t\t\t\t\t; (0x0 -> NULL, 0x08 -> сегмент кода,\n\t\t\t\t\t\t\t\t\t; 0x10 -> сегмент данных)\n"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex08/main.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / main.asm\n; Title:\tПрограмма загрузочного сектора, которая входит в 32-битный \n; \t\t\tЗащищенный Режим\n; ------------------------------------------------------------------------------\n; Description: \n;\tБыло бы неплохо остаться в 16-битном реальном режиме, но чтобы использовать\n;\tвозможности процессора полностью, мы должны переключиться в 32-битный\n;\tзащищенный режим.\n;\tОсновные отличия 32-битного защищенного режима:\n;\t\t1. Регистры увеличены до 32 бит. Чтобы использовать увеличенные регистры\n;\t\tнужно добавить букву \"е\" перед ними. Пример: AX -> EAX, BX -> EBX\n;\t\t2. Добавлены 2 новых регистров сегмента: FS и GS.\n;\t\t3. Доступны 32-битные смещения (размер сегмента сможет достигать 4гб)\n;\t\t4. Процессор поддерживает более сложную сегментацию памяти, у которой\n;\t\tесть 2 достоинства:\n;\t\t\ta. Коду в одном сегменте запрещено исполнять код в другом, более\n;\t\t\tпривилигированном сегменте, поэтому вы можете защитить код ядра\n;\t\t\tот кода пользовательских приложений.\n;\t\t\tb. Процессор может предоставлять виртуальную память для процессов\n;\t\t\tпользователя.\n;\t\t5. Обработка прерываний также более сложна.\n;\tСамая сложная часть перехода в 32PM - мы должны подготовить сложную\n;\tструктуру данных, которая называется глобальная таблица дескрипторов (GDT).\n; ------------------------------------------------------------------------------\n\n\n[org 0x7c00]\n\n\tmov bp, 0x9000\t\t\t\t\t; Устанавливаем стек\n\tmov sp, bp\n\n\tmov bx, MSG_REAL_MODE\n\tcall print_string\t\t\t\t; Печатаем сообщение на экран\n\n\tcall switch_to_pm\t\t\t\t; Переключаемся на загрузочный режим\n\n\tjmp $\n\n%include \"../ex06/print_string.asm\"\t; Вывод строки\n%include \"gdt.asm\"\t\t\t\t\t; GDT\n%include \"print_string_pm.asm\"\t\t; Вывод строки в 32 PM\n%include \"switch.asm\"\t\t\t\t; Переключиться на 32 PM\n\n[bits 32]\n\nBEGIN_PM:\t\t\t\t\t\t\t; Сюда мы попадем после переключения в PM\n\tmov ebx, MSG_PROT_MODE\n\tcall print_string_pm\t\t\t; Печатаем сообщение на экран\n\n\tjmp $\n\nMSG_REAL_MODE:\n\tdb \"Started in 16-bit Real Mode\", 0\nMSG_PROT_MODE:\n\tdb \"Successfully landed in 32-bit Protected mode\", 0\n\ntimes 510-($-$$) db 0\ndw 0xaa55\n"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex08/print_string_pm.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / print_string_pm.asm\n; Title:\tФункция вывода строки на экран в 32-битном защищенном режиме\n; ------------------------------------------------------------------------------\n; Description:\n;\tПлюсы 32-битного режима: возможность использовать 32-битные регистры и\n;\tадрессацию памяти, защищенную память виртуальную память\n;\tМинусы: отсутствие БИОС прерываний, требование наличия GDT (об этом позже)\n;\tВ этой программе мы напишем новую функцию печати строки, но без прерываний\n;\tБИОСа, а напрямую манипулируя VGA видеопамятью, вместо вызова int 0x10.\n;\tVGA размещена начиная с адреса 0xb8000, и у VGA имеется специальный\n;\tтекстовый режим, поэтому нам не придется напрямую рисовать пиксели.\n;\tОсобенности:\n;\t1. Символ представляется в виде 2-х байтов. Первый байт = сам символ,\n;\tвторой байт = 4 бита на цвет текста и еще 4 на цвет фона. \n;\tНапример, чтобы распечатать символ 'A' белым текстом на черном фоне, мы\n;\tиспольузуем 0x410f: 0x41 == 'A', 0 == белый, f == черный.\n; ------------------------------------------------------------------------------\n\n\n[bits 32]\t\t\t\t\t; Используем 32-битный режим\n\n\t\t\t\t\t\t\t; Определяем некоторые константы\nVIDEO_MEMORY equ 0xb8000\t; = адрес начала памяти VGA\nWHITE_ON_BLACK equ 0x0f\t\t; = цвет символов (0x0f - белый на черном)\n\nprint_string_pm:\n\tpusha\n\tmov edx, VIDEO_MEMORY\t; Перемещаем в EDX адрес начала массива видеопамяти\n\nprint_string_pm_loop:\n\t\t\t\t\t\t\t; Помним, что AX (2б) = AH(1б) и AL(1б)\n\tmov al, [ebx]\t\t\t; Сохраняем символ из EBX в AL\n\tmov ah, WHITE_ON_BLACK\t; Устанавливаем цвет символов в AH\n\t\t\t\t\t\t\t; таким образом AX получается равен символу + цвету\n\n\tcmp al, 0\t\t\t\t; if (AL == 0), т.е. если конец массива, то\n\tje print_string_pm_done\t; заканчиваем выполнение функции\n\t\t\t\t\t\t\t; else:\n\tmov [edx], ax\t\t\t; video_memory[EDX] = AX\n\tadd ebx, 1\t\t\t\t; переходим к следующему символу (+1, просто массив)\n\tadd edx, 2\t\t\t\t; переходим к следующему адресу в VGA (+2 т.к.\n\t\t\t\t\t\t\t; два байта на символ)\n\n\tjmp print_string_pm_loop\n\nprint_string_pm_done:\n\tpopa\n\tret\n"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex08/switch.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / switch.asm\n; Title:\tПереключаемся в PM (Protected mode, защищенный режим)\n; ------------------------------------------------------------------------------\n; Description:\n;\tЧтобы сделать свитч, нам нужно:\n;\t\t1. Отключить прерывания (процессор просто будет их игнорировать), т.к.\n; \t\tв PM, прерывания обрабатываются совершенно по-другому в отличие от\n;\t\tReal Mode. Даже если процессор и смог бы распределить сигналы прерываний\n;\t\tпо конкретным BIOS обработчикам прерываний, БИОС обработчики будут\n;\t\tобрабатывать 16-битный код, что повлекло бы за собой ошибки.\n;\t\t2. Загрузить GDT дескриптор\n;\t\t3. Изменяем первый бит регистра управления cr0 на \"1\"\n;\t\thttps://en.wikipedia.org/wiki/Control_register#CR0\n;\t\t4. Т.к. процессор использует специальную технику, которая называется\n;\t\tназывается pipelining (гугли: вычислительный конвейер, полезная статья\n;\t\tна хабре: https://habr.com/ru/post/182002/), и поэтому сразу после того,\n;\t\tкак перевести процессор в PM (что мы и сделали в предыдущем пункте), \n;\t\tнам нужно заставить процессор завершить всю работу в конвейере, чтобы\n;\t\tбыть уверенным, что все будущие инструкции будут выполнены корректно.\n;\t\tКонвейер загружает в себя некоторые количество последующих после текущей\n;\t\tинструкций, но конвейеру не очень нравятся инструкции типа call и jmp,\n;\t\tт.к. процессор не знает полностью какие инструкции будут следовать за\n;\t\tними, в особенности если мы вызовем jmp или call \"прыгая\" в другой\n;\t\tсегмент. Поэтому нам нужно сделать \"дальний прыжок\", чтобы стереть \n;\t\tобрабатываемые в конвейере инструкции.\n;\t\tСам прыжок: \tjmp <сегмент>:<адрес смещения>\n; ------------------------------------------------------------------------------\n\n\n[bits 16]\n\nswitch_to_pm:\n\tcli\t\t\t\t\t\t; Отключаем прерывания (cli = clear interrupts)\n\t\n\tlgdt [gdt_descriptor]\t; Загружаем GDT дескриптор (lgdt = load GDT)\n\n\tmov eax, cr0\t\t\t; Чтобы перейти в PM, нужно чтобы первый бит\n\tor eax, 0x1\t\t\t\t; регистра управления cr0 был 1\n\tmov cr0, eax\n\n\tjmp CODE_SEG:init_pm\t; Делаем \"дальний прыжок\" в наш новый 32-битный\n\t\t\t\t\t\t\t; сегмент кода. Это так же заставляет процессор\n\t\t\t\t\t\t\t; завершить обрабатываемые в конвейере инструкции. \n\n[bits 32]\n\ninit_pm:\t\t\t\t\t; в PM, наши старые сегменты бесполезны, поэтому\n\tmov ax, DATA_SEG\t\t; мы делаем так, чтобы регистры всех сегментов\n\tmov ds, ax\t\t\t\t; указывали на сегмент данных, который мы определили\n\tmov ss, ax\t\t\t\t; в GDT (см. ./gdt.asm)\n\tmov es, ax\n\tmov fs, ax\n\tmov gs, ax\n\n\tmov ebp, 0x90000\t\t; Обновляем позицию стека, чтобы он был на самом\n\tmov esp, ebp\t\t\t; верху свободного места\n\n\tcall BEGIN_PM\t\t\t; Вызываем функцию из ./main.asm\n"
  },
  {
    "path": "guide/01-KERNEL/ex00/BUILD.md",
    "content": "# Процесс сборки\n\nДля сборки нам понадобится собрать кросс-компилятор gcc для i386 архитектуры процессора. Удобнее использовать готовый отсюда: https://wiki.osdev.org/GCC_Cross-Compiler#Prebuilt_Toolchains. Для компьютеров на Linux с x86_64 архитектурой:\n```\nwget http://newos.org/toolchains/i386-elf-4.9.1-Linux-x86_64.tar.xz\nmkdir /usr/local/i386elfgcc\ntar -xf i386-elf-4.9.1-Linux-x86_64.tar.xz -C /usr/local/i386elfgcc --strip-components=1\nexport PATH=$PATH:/usr/local/i386elfgcc/bin\n```\nДалее можно использовать установленный тулчейн с помощью команд `i386-elf-gcc` для gcc, `i386-elf-ld` для линкера, и т.д.\n\n1. Итак, сначала компилируем загрузочный сектор. Получится бинарный файл.\n```\nnasm bootsect.asm -f bin -o bootsect.bin\n```\nФлаги:\n- `-f bin` - формат бинарный\n- `-o bootsect.bin` - output file = bootsect.bin\n2. Далее компилируем ядро в объектный файл, чтобы потом собрать его вместе с kernel_entry.\n```\ni386-elf-gcc -ffreestanding -c kernel.c -o kernel.o\n```\nФлаги:\n- `-ffreestanding` - в режиме `freestanding`, единственные доступные заголовочные файлы стандартной библиотеки это <float.h>, <iso646.h>, <limits.h>, <stdarg.h>, <stdbool.h>, <stddef.h> и <stdint.h>. Также эта опция указывает компилятору не полагаться на то, что стандартные функции имеют их обычное определение. Это предотвратит компилятор от оптимизации, которую он делает на основе предположений о поведении функций из стандартных библиотек. Например в `hosted` режиме (противополжен `freestanding`), gcc знает о том, что имеющаяся библиотека соответствует спецификации стандарта языка Си. Он может преобразовать `printf(\"hi\\n\")` в `puts(\"hi\")`, т.к. имеет представление из определения стандартной IO библиотеки что эти две функции ведут себя одинаково в данном случае. А с флагом `-ffreestanding` gcc не проводит подобных оптимизаций. Подобнее: http://cs107e.github.io/guides/gcc/, https://stackoverflow.com/questions/18711719/freestanding-gcc-and-builtin-functions.\n- `-c kernel.c` - флаг указывает на то, что файл после компиляции не нужно линковать\n- `-o kernel.o` - output object file\n3. Компилируем `kernel_entry.asm`\n```\nnasm kernel_entry.asm -f elf -o kernel_entry.o\n```\nФлаги:\n- `-f elf` - формат: ELF (Executable and Linkable Format)\n\n4. Линкуем `kernel_entry.o` и `kernel.o` вместе в один бинарный файл `kernel.bin`\n```\ni386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o kernel.o --oformat binary\n```\nФлаги:\n- `-o kernel.bin` - output file = kernel.bin\n- `-Ttext 0x1000` - этот флаг распологает секцию `.text` по адресу `0x1000`.\n- `--oformat binary` - output format = binary\n\n5. Соединяем два файла `bootsect.bin` и `kernel.bin` в один `os-image.bin` с помощью утилиты cat\n```\ncat bootsect.bin kernel.bin > os-image.bin\n```\n6. Запускаем образ ОС с помощью эмулятора qemu\n```\nqemu-system-i386 -fda os-image.bin\n```\nФлаги:\n- `-fda` - использовать файл как образ флоппи диска (дискеты).\n\nВуаля!\n\n> Подробнее: [Makefile](build/Makefile)\n"
  },
  {
    "path": "guide/01-KERNEL/ex00/README.md",
    "content": "# `01-KERNEL / ex00`: Процесс сборки, структура проекта, дебаг\n> Новые штучки: `Makefile`, `gcc`, `gdb`\n\nНовые файлы упражнения, которые нужно посмотреть:\n- `boot/newline.asm`\n- `boot/bootsect.asm`\n- `boot/kernel_entry.asm`\n- `kernel/kernel.c`\n- `build/Makefile`\n\n## Процесс сборки\nОписан тут [BUILD.md](BUILD.md)\n\n## Структура проекта\n- `boot` - файлы загрузочного сектора `.asm`\n- `kernel` - файлы ядра `.c`\n- `build` - мейкфайл и скомпилированные файлы, включая образ ОС `.o` `.bin` `.elf`\n\n## Дебаг\n\n1. Запускаем программу make с таргетом debug\n```\n$ make debug\n```\n2. Запускаем GDB в отдельном терминале\n```\n$ gdb\n```\n3. Подключаемся к порту 1234, который по дефолту прослушивает QEMU\n```\n(gdb) target remote localhost:1234\n```\n4. Загружаем символьный файл `kernel.elf` который предоставляет GDB полезную информацию для дебага. Это нужно для перевода символов (названия функций и переменных) в адреса, номеров строк в адреса кода и так далее.\n```\n(gdb) symbol-file kernel.elf\n```\n4. Ставим брейкпоинт на функции `kmain`\n```\n(gdb) b kmain\n```\n5. Запускаем программу\n```\n(gdb) continue\n```\n6. Идем читать гайд по GDB\n```\nНапример тут https://habr.com/ru/post/491534/\n```\n"
  },
  {
    "path": "guide/01-KERNEL/ex00/boot/bootsect.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t01-KERNEL\n; File:\t\tex00 / bootsect.asm\n; Title:\tПрограмма загрузочного сектора, которая загружает ядро, написанное\n;\t\t\tна C в 32-битный защищенный режим.\n; ------------------------------------------------------------------------------\n; Description:\n; ------------------------------------------------------------------------------\n\n\n[org 0x7c00]\n\nKERNEL_OFFSET equ 0x1000\t; Смещение в памяти, из которого мы загрузим ядро\n\n\tmov [BOOT_DRIVE], dl\t; BIOS stores our boot drive in DL , so it ’s\n\t\t\t\t\t\t\t; best to remember this for later. (Remember that\n\t\t\t\t\t\t\t; the BIOS sets us the boot drive in 'dl' on boot)\n\tmov bp, 0x9000\t\t\t; Устанавливаем стек\n\tmov sp, bp\n\n\tmov bx, MSG_REAL_MODE\t; Печатаем сообщение\n\tcall print_string\n\n\tcall load_kernel\t\t; Загружаем ядро\n\tcall switch_to_pm\t\t; Переключаемся в Защищенный Режим\n\tjmp $\n\n%include \"print_string.asm\"\t\t; ф. печати строки\n%include \"print_hex.asm\"\t\t; ф. печати 16-ричного числа\n%include \"disk_load.asm\"\t\t; ф. чтения диска\n%include \"print_string_pm.asm\"\t; ф. печати строки (32PM)\n%include \"switch.asm\"\t\t\t; ф. переключения в 32PM\n%include \"gdt.asm\"\t\t\t\t; таблица GDT\n\n[bits 16]\n\nload_kernel:\n\tmov bx, MSG_LOAD_KERNEL\n\tcall print_string\t\t; Печатаем сообщение о том, то мы загружаем ядро\n\t\t\t\t\t\t\t; Устанавливаем параметры для функции disk_load:\n\tmov bx, KERNEL_OFFSET\t; Загрузим данные в место памяти по\t\tTODO: disk_load main lookup\n\t\t\t\t\t\t\t; смещению KERNEL_OFFSET\n\tmov dh, 16\t\t\t\t; Загрузим много секторов. *\n\tmov dl, [BOOT_DRIVE]\t; Загрузим данные из BOOT_DRIVE (Возвращаем BOOT_DRIVE)\n\tcall disk_load\t\t\t; Вызываем функцию disk_load\n\tret\n\n\n[bits 32]\t\t\t\t\t; Сюда мы попадем после переключения в 32PM\n\nBEGIN_PM:\n\tmov ebx, MSG_PROT_MODE\n\tcall print_string_pm\t; Печатаем сообщение об успешной загрузке в 32PM\n\tcall KERNEL_OFFSET\t\t; Переходим в адрес, по которому загрузился код ядра\n\tjmp $\n\n\nBOOT_DRIVE:\t\t\tdb 0\nMSG_REAL_MODE:\t\tdb \"Started in 16-bit Real Mode\", 0\nMSG_PROT_MODE:\t\tdb \"Successfully landed in 32-bit Protected mode\", 0\nMSG_LOAD_KERNEL:\tdb \"Loading kernel into VIDEO_MEMORY\", 0\n\ntimes 510-($-$$) db 0\ndw 0xaa55\n\n; ------\n; * - Забавный факт: если загрузить меньше секторов, то мы столкнемся со\n; странными ошибками когда будем писать ядро на Си. Например, аргументы\n; функции могут быть повреждены, а строки \"обрезаны\".\n"
  },
  {
    "path": "guide/01-KERNEL/ex00/boot/disk_load.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / disk_load.asm\n; Title:\tФункция чтения диска\n; ------------------------------------------------------------------------------\n; Description:\n;\tЧтобы лучше понять, что здесь происходит, разберитесь с тем, что такое CHS\n;\tпо ссылке https://ru.wikipedia.org/wiki/CHS\n; ------------------------------------------------------------------------------\n\n\ndisk_load:\n\tpush dx\n\tmov ah, 0x02\t\t\t; Указвыаем БИОСу что нам нужна рутина чтения диска\n\t\t\t\t\t\t\t; Указываем что нам нужно:\n\tmov al, dh\t\t\t\t; 1. Прочитать кол-во секторов, равное значению в dh\n\tmov ch, 0x00\t\t\t; 2. Выбрать нулевой цилиндр\n\tmov dh, 0x00\t\t\t; 3. Выбрать нулевую головку\n\tmov cl, 0x02\t\t\t; 4. Начинать считывать со второго сектора (т.е.\n\t\t\t\t\t\t\t; первый свободный сектор сразу после загруочного \n\t\t\t\t\t\t\t; сектора, т.к. загрузочный сектор находится по \n\t\t\t\t\t\t\t; адресу 0x01)\n\tint 0x13\t\t\t\t; Вызываем прерывание для чтения\n\n\t\t\t\t\t\t\t; У БИОСа может не получиться прочитать диск, и\n\t\t\t\t\t\t\t; чтобы дать нам знать что произошла ошибка, он,\n\t\t\t\t\t\t\t; во-первых, обновляет специальный флаг CF (carry \n\t\t\t\t\t\t\t; flag) специальным значением, которое означает \n\t\t\t\t\t\t\t; ошибку, а во-вторых, кладет в регистр AL кол-во \n\t\t\t\t\t\t\t; секторов, которые у него получилось прочитать.\n\t\n\tjc disk_error\t\t\t; jc - инструкция для прыжка на указанную метку,\n\t\t\t\t\t\t\t; которая выполняется только если CF (carry flag)\n\t\t\t\t\t\t\t; сигнализирует об ошибке\n\n\tpop dx\t\t\t\t\t; Восстанавливаем регистр DX из стека\n\tcmp dh, al\t\t\t\t; если AL (кол-во прочитанных секторов) != DH\n\t\t\t\t\t\t\t; (предполагаемое кол-во секторов),\n\tjne disk_sectors_error\t; то выводим на экран сообщение об ошибке и зависаем\n\t\t\t\t\t\t\t; (то есть запускаем бесконечный цикл)\n\tjmp disk_success\n\tjmp disk_exit\t\t\t; Заканчиваем выполнение функции\n\ndisk_success:\n\tmov bx, SUCCESS_MSG\n\tcall print_string\n\tjmp disk_exit\n\ndisk_error:\n\tmov bx, DISK_ERR_MSG\t; Перемещаем в BX сообщение об ошибке\n\tcall print_string\t\t; Выводим его на экран\n\tmov dh, al\n\tcall print_hex\n\tjmp disk_loop\t\t\t; бесконечный цикл\n\ndisk_sectors_error:\n\tmov bx, SECTORS_ERR_MSG\n\tcall print_string\n\nSUCCESS_MSG:\n\tdb \"Disk was successfully read \", 0\n\nDISK_ERR_MSG:\n\tdb \"Disk read error! \", 0\n\nSECTORS_ERR_MSG:\n\tdb \"Incorrect number of sectors read \", 0\n\ndisk_loop:\n\tjmp $\n\ndisk_exit:\n\tret"
  },
  {
    "path": "guide/01-KERNEL/ex00/boot/gdt.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / gdt.asm\n; Title:\tОпределяем GDT (глобальная таблица дескрипторов)\n; ------------------------------------------------------------------------------\n; Description:\n; \tСпособ, которым процессор переводит логический адрес в физический, в\n; \t32-битном защищенном режиме отличается от 16-битного реального режима.\n; \tВместо того, чтобы умножить значение регистра сегмента на 16 и прибавить к\n; \tэтому \"смещение\" (offset), регистр сегмента становится индексом \n; \tопределенного дескриптора сегмента в GDT.\n; \tДескриптор сегмента - это 8-битная структура, которая определяет свойства\n; \tэтого сегмента:\n; \t\t- Base address (32 bits), определяющий откуда сегмент начинается в\n; \t\tфизической памяти.\n; \t\t- Segment Limit (20 bits), определяющий размер сегмента\n;\t\t- Различные флаги, которые устанавливают каким образом процессор будет\n; \t\t\"относиться\" к сегментам, например уровень привилегий и т.д.\n;\n; \tФлаги:\n;\t* 1-ые флаги:\n; \t\t- present flag (флаг присутствия). Если его значение \"1\", то это \n;\t\tуказывает, что сегмент присутствует в памяти (это нужно для виртуальной \n;\t\tпамяти)\n;\t\t- privilege flag (флаг привилегии). Значение \"0\" - самый высокий уровень\n;\t\tпривилегии\n;\t\t- descriptor type (тип дескриптора). \"1\" - для сегмента кода или \n;\t\tсегмента данных\n; \t* Флаги типа:\n;\t\t- code (флаг кода). \"1\" - для кода, \"0\" - для даннных\n;\t\t- conformig (флаг подчинения). \"0\" - чтобы код в другом сегменте с\n;\t\tболее низким уровнем привилегий не смог вызвать код из этого сегмента - \n;\t\tэто ключ к защите памяти (memory protection).\n;\t\t- readable (читаемость). \"1\" - если читаемый, \"0\" - только исполняемый.\n;\t\t- writable. Разрешает сегменту данных быть записываемым, в противном\n;\t\tслучае, он будет доступен только для чтения.\n;\t\t- accessed (флаг доступа). Этот флаг устанавливается, когда происходит\n; \t\tобращение к сегменту.\n;\t\t- expand down. Флаг (бит), позволяющий сегменту расширяться вниз.\n;\t* 2-ые флаги:\n;\t\t- granulariy (гранулярность). \"0\" - байтовая гранулярность, лимит\n; \t\tзадается в байтах, если \"1\" - страничная гранулярность, в 4кб блоках.\n;\t\tЕсли выбрать страничную гранулярность и установить значение лимита как\n;\t\t0xfffff, то лимит умножится на 16*16*16 (4кб), и лимит станет 0xfffff000\n; \t\tпозволяя нашему сегменту занять 4гб места в памяти.\n;\t\t- 32-bit default. \"1\" - т.к. наш сегмент будет содержать 32-битный код.\n;\t\t- 64-bit code segment. \"0\" - т.к. не используется на 32-битных \n;\t\tпроцессорах.\n;\t\t- AVL (available). Определяет доступность сегмента для использования \n;\t\tсистемным программным обеспечением (используются только ОС).\n; ------------------------------------------------------------------------------\n\ngdt_start:\t\t\t\t\t; Эта пустая метка нужно для того чтобы удобнее\n\t\t\t\t\t\t\t; посчитать размер GDT для ее дескриптора \n\t\t\t\t\t\t\t; (end - start)\n\ngdt_null:\t\t\t\t\t; Необходимый нулевой дескриптор для GDT\n\tdd 0x0\t\t\t\t\t; dd - define double (двойное слово, т.е. 4 байта)\n\tdd 0x0\n\ngdt_code:\t\t\t\t\t; Определяем дескриптор сегмента кода\n\tdw 0xffff\t\t\t\t; Limit (bits 0-15)\n\tdw 0x0\t\t\t\t\t; Base (bits 0-15)\n\tdb 0x0\t\t\t\t\t; Base (bits 16-23)\n\tdb 10011010b\t\t\t; Первые флаги + флаги типа (смотрим по битам)\n\t\t\t\t\t\t\t; present: 1, privilege: 00, descriptor type: 1\n\t\t\t\t\t\t\t; code: 1, conforming: 0, readable: 1, accessed: 0\n\tdb 11001111b\t\t\t; Вторые флаги + длина сегмента (bits 16-19):\n\t\t\t\t\t\t\t; granularity: 1, 32-bit default: 1,\n\t\t\t\t\t\t\t; 64-bit default: 0, AVL: 0\n\tdb 0x0\t\t\t\t\t; Base (bits 24-31)\n\ngdt_data:\t\t\t\t\t; Определяем дескриптор сегмента кода\n\tdw 0xffff\t\t\t\t; Limit (bits 0-15)\n\tdw 0x0\t\t\t\t\t; Base (bits 0-15)\n\tdb 0x0\t\t\t\t\t; Base (bits 16-23)\n\tdb 10010010b\t\t\t; Первые флаги + флаги типа (смотрим по битам)\n\tdb 11001111b\t\t\t; Вторые флаги + длина сегмента (bits 16-19)\n\tdb 0x0\t\t\t\t\t; Base (bits 24-31)\n\ngdt_end:\t\t\t\t\t; Пустая метка\n\n\ngdt_descriptor:\t\t\t\t\t\t; дескриптор GDT\n\tdw gdt_end - gdt_start - 1\t\t; Размер GDT\n\tdd gdt_start\t\t\t\t\t; Адрес начала GDT\n\n\nCODE_SEG equ gdt_code - gdt_start\t; Определяем некоторые константы. \nDATA_SEG equ gdt_data - gdt_start\t; Они понадобятся для регистров сегментов в\n\t\t\t\t\t\t\t\t\t; 32-битном защищенном режиме. Например,\n\t\t\t\t\t\t\t\t\t; когда мы установим регистр DS = 0x10 (т.е\n\t\t\t\t\t\t\t\t\t; 16 байтов) в этом режиме, процессор\n\t\t\t\t\t\t\t\t\t; поймет что мы хотим использовать сегмент,\n\t\t\t\t\t\t\t\t\t; находящийся в смещении 0x10 в нашем GDT,\n\t\t\t\t\t\t\t\t\t; т.е. в нашем случае это сегмент данных\n\t\t\t\t\t\t\t\t\t; (0x0 -> NULL, 0x08 -> сегмент кода,\n\t\t\t\t\t\t\t\t\t; 0x10 -> сегмент данных)"
  },
  {
    "path": "guide/01-KERNEL/ex00/boot/kernel_entry.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t01-KERNEL\n; File:\t\tex00 / kernel_entry.asm\n; Title:\tКод, служащий входной точкой для функции kmain из kernel.c\n; ------------------------------------------------------------------------------\n; Description:\n; ------------------------------------------------------------------------------\n\n\n[bits 32]\n[extern kmain]\t; Определяем 'внешнюю' штуку с названием kmain - она понадобится\n\t\t\t\t; линкеру чтобы собрать все вместе\ncall kmain\t\t; Вызываем определенную выше функцию, которая будет доступна\n\t\t\t\t; после линковки. Это функция kmain из kernel.c\njmp $\n"
  },
  {
    "path": "guide/01-KERNEL/ex00/boot/print_hex.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex05 / print_hex.asm\n; Title:\tФункция вывода шестнадцатеричного числа на экран\n; ------------------------------------------------------------------------------\n; Description: \n;\tКак выглядит регистр EAX (32 b = 32 бита):\n;\n;\t\t\t\t\t\tEAX (32 b)\n;\t\t---------------------------------------------\n;\t\t|\t\t\t\t\t|\t\t\t|\t\t\t|\n;\t\t|\t\t\t\t\t|  AH (8 b)\t|  AL (8 b) |\n;\t\t|\t\t\t\t\t|\t\t\t|\t\t\t|\n;\t\t---------------------------------------------\n;\t\t\t\t\t\t\t\t\tAX (16 b)\n;\n;\tКак видим, EAX содержит в себе регистр AX, который в свою очередь разделен \n;\tна AH (a high, верхний) и AL (a low, нижний).\n; ------------------------------------------------------------------------------\n\n\n; Помним, что в main.asm:\n; DX  =  0x1fb6\n; BX  =  \"0x0000\" (а точнее, BX=\"0\", т.к. указывает на первый элемент)\n\nprint_hex:\n\tpusha\t\t\t\t\t\t; Сохраняем значения регистров в стеке\n\tmov cx, 0\t\t\t\t\t; Регистр CX будет служить счетчиком\n\nloop1:\n\tcmp cx, 4\t\t\t\t\t; if (CX < 4)\n\tjl print\t\t\t\t\t; Переходим к print\n\tjmp end\t\t\t\t\t\t; else переходим к end\n\nprint:\n\tmov ax, dx\t\t\t\t\t; В AX теперь 0x1fb6\n\tand ax, 0x000f\t\t\t\t; В AX теперь 6 (последняя цифра от 0x1fb6).\n\tcmp ax, 9\t\t\t\t\t; if (AX > 9) (проверяем обозначается ли число\n\t\t\t\t\t\t\t\t; буквой т.к. мы помним что цифра больше 9 в \n\t\t\t\t\t\t\t\t; 16-ричной системе исчисления обозначается\n\t\t\t\t\t\t\t\t; буквой латинского алфавита)\n\tjg num_to_abc\t\t\t\t; Переходим к num_to_abc\n\tjmp next\n\nnum_to_abc:\t\t\t\t\t\t; Перевод числа в букву (так, как она бы\n\t\t\t\t\t\t\t\t; выглядела в 16-ричной СИ), например число 15\n\t\t\t\t\t\t\t\t; в десятичной будет равно f в 16-ричной\n\tadd ax, 39\t\t\t\t\t; Добавляем к этому числу 39, чтобы затем еще\n\t\t\t\t\t\t\t\t; добавить 48 ('0'), получая код \n\t\t\t\t\t\t\t\t; соответсвующего символа в ASCII (например,\n\t\t\t\t\t\t\t\t; f (как число, то есть 15) + 39 + 48 (код '0')\n\t\t\t\t\t\t\t\t; = 102 (то есть 'f' в ASCII, как нам и нужно)\n\tjmp next\n\nnext:\n\tadd ax, '0'\t\t\t\t\t; Добавляем 48 в ax\n\tmov bx, HEX_OUT + 5\t\t\t; Теперь bx указывает на последний символ строки\n\t\t\t\t\t\t\t\t; HEX_OUT\n\tsub bx, cx\t\t\t\t\t; BX = BX - counter (для итерации)\n\tmov [bx], al\t\t\t\t; Так как мы разыменовываем bx (вот так: [bx]),\n\t\t\t\t\t\t\t\t; то [bx] это не регистр, а ссылка на память,\n\t\t\t\t\t\t\t\t; и так как ax = 16 бит (2 байта), чтобы нам не\n\t\t\t\t\t\t\t\t; перезаписать лишнюю память, в [bx] мы помещаем\n\t\t\t\t\t\t\t\t; не ax, а al, размер которого равен 1-му байту.\n\tror dx, 4\t\t\t\t\t; было: 0x1fb6, стало: 0x61fb (переносим\n\t\t\t\t\t\t\t\t; последнюю цифру в начало)\n\tinc cx\t\t\t\t\t\t; counter++\n\tjmp loop1\t\t\t\t\t; переходим обратно к loop1\n\nend:\n\tmov bx, HEX_OUT\t\t\t\t; Делаем так, чтобы bx снова указывал на первый\n\t\t\t\t\t\t\t\t; символ строки HEX_OUT\n\tcall print_string\t\t\t; выводи на экран строку из регистра bx\n\tpopa\t\t\t\t\t\t; Возвращаем регистрам их изначальное значение\n\tret\t\t\t\t\t\t\t; Заканчиваем выполнение функции\n\n\nHEX_OUT:\tdb \"0x0000\", 0"
  },
  {
    "path": "guide/01-KERNEL/ex00/boot/print_string.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex04 / print_string.asm\n; Title:\tФункция вывода строки на экран\n; ------------------------------------------------------------------------------\n; Description: null\n; ------------------------------------------------------------------------------\n\n\nprint_string:\t\t\t; Функция вывода строки на экран.\n\tpusha\t\t\t\t; Когда мы используем функции, мы можем модифицировать\n\t\t\t\t\t\t; регистры прямо в них, что нарушает чистоту функции,\n\t\t\t\t\t\t; т.е. мы можем перезаписывать какие-то внешние\n\t\t\t\t\t\t; данные. Для этого мы добавляем значение всех регистров\n\t\t\t\t\t\t; в стек с помощью команды pusha, а в конце функции\n\t\t\t\t\t\t; мы возвращаем регистрам их изначальные значения,\n\t\t\t\t\t\t; которые возьмем из стека (команда popa).\n\t\t\t\t\t\t\n\tmov ah, 0x0e\t\t; tele-type mode\n\tloop:\t\t\t\t; Метка loop (= цикл)\n\t\tmov al, [bx]\t; Перемещаем значение BX в AL, т.к. мы помним что в BX\n\t\t\t\t\t\t; лежит первый символ строки (см. ./main.asm)\n\t\tcmp al, 0\t\t; Команда cmp для сравнения AL и 0.\n\t\tje newline\t\t; (if) je = \"jump if equal\",\n\t\t\t\t\t\t; т.е. перемещаемся к коду с меткой return если AL == 0\n\t\tjmp put_char\t; (else) в противном случае перемещаемся к put_char.\n\nput_char:\t\t\t\t; Метка put_char - вывод символа на экран.\n\tint 0x10\t\t\t; Вызываем прерывание, которое позволяет вывести \n\t\t\t\t\t\t; на экран значение регистра AL, в котором лежит [bx].\n\tinc bx\t\t\t\t; inc <регистр> - увеличить на 1.\n\tjmp loop\t\t\t; Возвращаемся обратно к циклу.\n\nnewline:\n\tmov ah, 0x0e\n\tmov al, 0x0a\t; (0x0a) \\n - new line\n\tint 0x10\n\tmov al, 0x0d\t; (0x0d) \\r - carriage return\n\tint 0x10\n\tjmp return\n\nreturn:\t\t\t\t\t; Метка return - завершаем функцию\n\tpopa\t\t\t\t; Возвращаем регистрам их изначальные значения.\n\tret\t\t\t\t\t; Заканчиваем выполнение функции.\n"
  },
  {
    "path": "guide/01-KERNEL/ex00/boot/print_string_pm.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / print_string_pm.asm\n; Title:\tФункция вывода строки на экран в 32-битном защищенном режиме\n; ------------------------------------------------------------------------------\n; Description:\n;\tПлюсы 32-битного режима: возможность использовать 32-битные регистры и\n;\tадрессацию памяти, защищенную память виртуальную память\n;\tМинусы: отсутствие БИОС прерываний, требование наличия GDT (об этом позже)\n;\tВ этой программе мы напишем новую функцию печати строки, но без прерываний\n;\tБИОСа, а напрямую манипулируя VGA видеопамятью, вместо вызова int 0x10.\n;\tVGA память размещена начиная с адреса 0xb8000, и у VGA имеется специальный\n;\tтекстовый режим, поэтому нам не придется напрямую рисовать пиксели.\n;\tОсобенности:\n;\t1. Символ представляется в виде 2-х байтов. Первый байт - сам символ,\n;\tвторой байт - 4 бита на цвет текста и еще 4 на цвет фона. \n;\tНапример, чтобы распечатать символ 'A' белым текстом на черном фоне, мы\n;\tиспольузуем 0x410f: 0x41 == 'A', 0 == белый, f == черный.\n; ------------------------------------------------------------------------------\n\n\n[bits 32]\t\t\t\t\t; Используем 32-битный режим\n\n\t\t\t\t\t\t\t; Определяем некоторые константы\nVIDEO_MEMORY equ 0xb8000\t; = адрес начала памяти VGA\nWHITE_ON_BLACK equ 0x0f\t\t; = цвет символов (0x0f - белый на черном)\n\nprint_string_pm:\n\tpusha\n\tmov edx, VIDEO_MEMORY\t; Перемещаем в EDX адрес начала массива видеопамяти\n\nprint_string_pm_loop:\n\t\t\t\t\t\t\t; Помним, что AX (2б) = AH(1б) и AL(1б)\n\tmov al, [ebx]\t\t\t; Сохраняем символ из EBX в AL\n\tmov ah, WHITE_ON_BLACK\t; Устанавливаем цвет символов в AH\n\t\t\t\t\t\t\t; таким образом AX получается равен символу + цвету\n\n\tcmp al, 0\t\t\t\t; if (AL == 0), т.е. если конец массива, то\n\tje print_string_pm_done\t; заканчиваем выполнение функции\n\t\t\t\t\t\t\t; else:\n\tmov [edx], ax\t\t\t; video_memory[EDX] = AX\n\tadd ebx, 1\t\t\t\t; переходим к следующему символу (+1, просто массив)\n\tadd edx, 2\t\t\t\t; переходим к следующему адресу в VGA (+2 т.к.\n\t\t\t\t\t\t\t; два байта на символ)\n\n\tjmp print_string_pm_loop\n\nprint_string_pm_done:\n\tpopa\n\tret\n"
  },
  {
    "path": "guide/01-KERNEL/ex00/boot/switch.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / switch.asm\n; Title:\tПереключаемся в PM (Protected mode, защищенный режим)\n; ------------------------------------------------------------------------------\n; Description:\n;\tЧтобы сделать свитч, нам нужно:\n;\t\t1. Отключить прерывания (процессор просто будет их игнорировать), т.к.\n; \t\tв PM, прерывания обрабатываются совершенно по-другому в отличие от\n;\t\tReal Mode. Даже если процессор и смог бы распределить сигналы прерываний\n;\t\tпо конкретным BIOS обработчикам прерываний, БИОС обработчики будут\n;\t\tобрабатывать 16-битный код, что повлекло бы за собой ошибки.\n;\t\t2. Загрузить GDT дескриптор\n;\t\t3. Изменяем первый бит регистра управления cr0 на \"1\"\n;\t\thttps://en.wikipedia.org/wiki/Control_register#CR0\n;\t\t4. Т.к. процессор использует специальную технику, которая называется\n;\t\tназывается pipelining (гугли: вычислительный конвейер, полезная статья\n;\t\tна хабре: https://habr.com/ru/post/182002/), и поэтому сразу после того,\n;\t\tкак перевести процессор в PM (что мы и сделали в предыдущем пункте), \n;\t\tнам нужно заставить процессор завершить всю работу в конвейере, чтобы\n;\t\tбыть уверенным, что все будущие инструкции будут выполнены корректно.\n;\t\tКонвейер загружает в себя некоторые количество последующих после текущей\n;\t\tинструкций, но конвейеру не очень нравятся инструкции типа call и jmp,\n;\t\tт.к. процессор не знает полностью какие инструкции будут следовать за\n;\t\tними, в особенности если мы вызовем jmp или call \"прыгая\" в другой\n;\t\tсегмент. Поэтому нам нужно сделать \"дальний прыжок\", чтобы завершить\n;\t\tобрабатываемые в конвейере инструкции.\n;\t\tСам прыжок: \tjmp <сегмент>:<адрес смещения>\n; ------------------------------------------------------------------------------\n\n\n[bits 16]\n\nswitch_to_pm:\n\tcli\t\t\t\t\t\t; Отключаем прерывания (cli = clear interrupts)\n\t\n\tlgdt [gdt_descriptor]\t; Загружаем GDT дескриптор (lgdt = load GDT)\n\n\tmov eax, cr0\t\t\t; Чтобы перейти в PM, нужно чтобы первый бит\n\tor eax, 0x1\t\t\t\t; регистра управления cr0 был 1\n\tmov cr0, eax\n\n\tjmp CODE_SEG:init_pm\t; Делаем \"дальний прыжок\" в наш новый 32-битный\n\t\t\t\t\t\t\t; сегмент кода. Это так же заставляет процессор\n\t\t\t\t\t\t\t; завершить обрабатываемые в конвейере инструкции. \n\n[bits 32]\n\ninit_pm:\t\t\t\t\t; в PM, наши старые сегменты бесполезны, поэтому\n\tmov ax, DATA_SEG\t\t; мы делаем так, чтобы регистры всех сегментов\n\tmov ds, ax\t\t\t\t; указывали на сегмент данных, который мы определили\n\tmov ss, ax\t\t\t\t; в GDT (см. ./gdt.asm)\n\tmov es, ax\n\tmov fs, ax\n\tmov gs, ax\n\n\tmov ebp, 0x90000\t\t; Обновляем позицию стека, чтобы он был на самом\n\tmov esp, ebp\t\t\t; верху свободного места\n\n\tcall BEGIN_PM\t\t\t; Вызываем функцию из ./main.asm"
  },
  {
    "path": "guide/01-KERNEL/ex00/build/Makefile",
    "content": "# флаг для дебага для gcc\nCFLAGS = -g\n\nrun: os-image.bin\n\tqemu-system-i386 -fda os-image.bin\n\tmake clean\n\ndebug: os-image.bin kernel.elf\n\t# kernel.elf нужен для gdb как symbol-file\n\t# флаг -s указывает qemu открыть и прослушивать 1234 порт\n\t# чтобы gdb смог соединиться с ним для дебага\n\t# флаг -S указыает QEMU не запускать образ и подождать подключений\n\tqemu-system-i386 -s -S -fda os-image.bin\n\tmake clean\n\nos-image.bin: bootsect.bin kernel.bin\n\tcat bootsect.bin kernel.bin > os-image.bin\n\nbootsect.bin:\n\tcd ../boot/ && nasm bootsect.asm -f bin -o ../build/bootsect.bin && cd -\n\nkernel.bin: kernel_entry.o kernel.o\n\ti386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o kernel.o --oformat binary\n\nkernel_entry.o:\n\tnasm ../boot/kernel_entry.asm -f elf -o kernel_entry.o\n\nkernel.o:\n\ti386-elf-gcc ${CFLAGS} -ffreestanding -c ../kernel/kernel.c\n\nkernel.elf: kernel_entry.o kernel.o \n\t# как kernel.bin только без --oformat binary\n\ti386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o kernel.o -o kernel.elf\n\nclean:\n\trm *.bin *.o *.elf\n"
  },
  {
    "path": "guide/01-KERNEL/ex00/kernel/kernel.c",
    "content": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex00 / kernel / kernel.c\n*\tTitle:\tПрограмма на Си, в которую мы загрузимся после boot'а.\n* ------------------------------------------------------------------------------\n*\tDescription:\n* ----------------------------------------------------------------------------*/\n\n\nvoid \tfunc() {}\t\t/* Эта функция нужна чтобы показать, что ядро будет\n\t\t\t\t\t\tзагружаться не с начала этого файла (0x00), а с\n\t\t\t\t\t\tфункции kmain. Если бы это было не так, то вместо kmain\n\t\t\t\t\t\tкод бы начал выполняться с этой функции, а т.к. она\n\t\t\t\t\t\tничего не делает, то мы бы не получили результата */\n\nvoid\tkmain()\n{\n\tchar *video_memory = (char *) 0xb8000;\t/* Распологаем символ 'x' по */\n\t*video_memory = 'x';\t\t\t\t\t/* адресу 0xb8000 */\n}\n"
  },
  {
    "path": "guide/01-KERNEL/ex01/boot/bootsect.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t01-KERNEL\n; File:\t\tex00 / bootsect.asm\n; Title:\tПрограмма загрузочного сектора, которая загружает ядро, написанное\n;\t\t\tна C в 32-битный защищенный режим.\n; ------------------------------------------------------------------------------\n; Description:\n; ------------------------------------------------------------------------------\n\n\n[org 0x7c00]\n\nKERNEL_OFFSET equ 0x1000\t; Смещение в памяти, из которого мы загрузим ядро\n\n\tmov [BOOT_DRIVE], dl\t; BIOS stores our boot drive in DL , so it ’s\n\t\t\t\t\t\t\t; best to remember this for later. (Remember that\n\t\t\t\t\t\t\t; the BIOS sets us the boot drive in 'dl' on boot)\n\tmov bp, 0x9000\t\t\t; Устанавливаем стек\n\tmov sp, bp\n\n\tmov bx, MSG_REAL_MODE\t; Печатаем сообщение\n\tcall print_string\n\n\tcall load_kernel\t\t; Загружаем ядро\n\tcall switch_to_pm\t\t; Переключаемся в Защищенный Режим\n\tjmp $\n\n%include \"print_string.asm\"\t\t; ф. печати строки\n%include \"print_hex.asm\"\t\t; ф. печати 16-ричного числа\n%include \"disk_load.asm\"\t\t; ф. чтения диска\n%include \"print_string_pm.asm\"\t; ф. печати строки (32PM)\n%include \"switch.asm\"\t\t\t; ф. переключения в 32PM\n%include \"gdt.asm\"\t\t\t\t; таблица GDT\n\n[bits 16]\n\nload_kernel:\n\tmov bx, MSG_LOAD_KERNEL\n\tcall print_string\t\t; Печатаем сообщение о том, то мы загружаем ядро\n\t\t\t\t\t\t\t; Устанавливаем параметры для функции disk_load:\n\tmov bx, KERNEL_OFFSET\t; Загрузим данные в место памяти по\t\tTODO: disk_load main lookup\n\t\t\t\t\t\t\t; смещению KERNEL_OFFSET\n\tmov dh, 16\t\t\t\t; Загрузим много секторов. *\n\tmov dl, [BOOT_DRIVE]\t; Загрузим данные из BOOT_DRIVE (Возвращаем BOOT_DRIVE)\n\tcall disk_load\t\t\t; Вызываем функцию disk_load\n\tret\n\n\n[bits 32]\t\t\t\t\t; Сюда мы попадем после переключения в 32PM\n\nBEGIN_PM:\n\tmov ebx, MSG_PROT_MODE\n\tcall print_string_pm\t; Печатаем сообщение об успешной загрузке в 32PM\n\tcall KERNEL_OFFSET\t\t; Переходим в адрес, по которому загрузился код ядра\n\tjmp $\n\n\nBOOT_DRIVE:\t\t\tdb 0\nMSG_REAL_MODE:\t\tdb \"Started in 16-bit Real Mode\", 0\nMSG_PROT_MODE:\t\tdb \"Successfully landed in 32-bit Protected mode\", 0\nMSG_LOAD_KERNEL:\tdb \"Loading kernel into VIDEO_MEMORY\", 0\n\ntimes 510-($-$$) db 0\ndw 0xaa55\n\n; ------\n; * - Забавный факт: если загрузить меньше секторов, то мы столкнемся со\n; странными ошибками когда будем писать ядро на Си. Например, аргументы\n; функции могут быть повреждены, а строки \"обрезаны\".\n"
  },
  {
    "path": "guide/01-KERNEL/ex01/boot/disk_load.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / disk_load.asm\n; Title:\tФункция чтения диска\n; ------------------------------------------------------------------------------\n; Description:\n;\tЧтобы лучше понять, что здесь происходит, разберитесь с тем, что такое CHS\n;\tпо ссылке https://ru.wikipedia.org/wiki/CHS\n; ------------------------------------------------------------------------------\n\n\ndisk_load:\n\tpush dx\n\tmov ah, 0x02\t\t\t; Указвыаем БИОСу что нам нужна рутина чтения диска\n\t\t\t\t\t\t\t; Указываем что нам нужно:\n\tmov al, dh\t\t\t\t; 1. Прочитать кол-во секторов, равное значению в dh\n\tmov ch, 0x00\t\t\t; 2. Выбрать нулевой цилиндр\n\tmov dh, 0x00\t\t\t; 3. Выбрать нулевую головку\n\tmov cl, 0x02\t\t\t; 4. Начинать считывать со второго сектора (т.е.\n\t\t\t\t\t\t\t; первый свободный сектор сразу после загруочного \n\t\t\t\t\t\t\t; сектора, т.к. загрузочный сектор находится по \n\t\t\t\t\t\t\t; адресу 0x01)\n\tint 0x13\t\t\t\t; Вызываем прерывание для чтения\n\n\t\t\t\t\t\t\t; У БИОСа может не получиться прочитать диск, и\n\t\t\t\t\t\t\t; чтобы дать нам знать что произошла ошибка, он,\n\t\t\t\t\t\t\t; во-первых, обновляет специальный флаг CF (carry \n\t\t\t\t\t\t\t; flag) специальным значением, которое означает \n\t\t\t\t\t\t\t; ошибку, а во-вторых, кладет в регистр AL кол-во \n\t\t\t\t\t\t\t; секторов, которые у него получилось прочитать.\n\t\n\tjc disk_error\t\t\t; jc - инструкция для прыжка на указанную метку,\n\t\t\t\t\t\t\t; которая выполняется только если CF (carry flag)\n\t\t\t\t\t\t\t; сигнализирует об ошибке\n\n\tpop dx\t\t\t\t\t; Восстанавливаем регистр DX из стека\n\tcmp dh, al\t\t\t\t; если AL (кол-во прочитанных секторов) != DH\n\t\t\t\t\t\t\t; (предполагаемое кол-во секторов),\n\tjne disk_sectors_error\t; то выводим на экран сообщение об ошибке и зависаем\n\t\t\t\t\t\t\t; (то есть запускаем бесконечный цикл)\n\tjmp disk_success\n\tjmp disk_exit\t\t\t; Заканчиваем выполнение функции\n\ndisk_success:\n\tmov bx, SUCCESS_MSG\n\tcall print_string\n\tjmp disk_exit\n\ndisk_error:\n\tmov bx, DISK_ERR_MSG\t; Перемещаем в BX сообщение об ошибке\n\tcall print_string\t\t; Выводим его на экран\n\tmov dh, al\n\tcall print_hex\n\tjmp disk_loop\t\t\t; бесконечный цикл\n\ndisk_sectors_error:\n\tmov bx, SECTORS_ERR_MSG\n\tcall print_string\n\nSUCCESS_MSG:\n\tdb \"Disk was successfully read \", 0\n\nDISK_ERR_MSG:\n\tdb \"Disk read error! \", 0\n\nSECTORS_ERR_MSG:\n\tdb \"Incorrect number of sectors read \", 0\n\ndisk_loop:\n\tjmp $\n\ndisk_exit:\n\tret"
  },
  {
    "path": "guide/01-KERNEL/ex01/boot/gdt.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / gdt.asm\n; Title:\tОпределяем GDT (глобальная таблица дескрипторов)\n; ------------------------------------------------------------------------------\n; Description:\n; \tСпособ, которым процессор переводит логический адрес в физический, в\n; \t32-битном защищенном режиме отличается от 16-битного реального режима.\n; \tВместо того, чтобы умножить значение регистра сегмента на 16 и прибавить к\n; \tэтому \"смещение\" (offset), регистр сегмента становится индексом \n; \tопределенного дескриптора сегмента в GDT.\n; \tДескриптор сегмента - это 8-битная структура, которая определяет свойства\n; \tэтого сегмента:\n; \t\t- Base address (32 bits), определяющий откуда сегмент начинается в\n; \t\tфизической памяти.\n; \t\t- Segment Limit (20 bits), определяющий размер сегмента\n;\t\t- Различные флаги, которые устанавливают каким образом процессор будет\n; \t\t\"относиться\" к сегментам, например уровень привилегий и т.д.\n;\n; \tФлаги:\n;\t* 1-ые флаги:\n; \t\t- present flag (флаг присутствия). Если его значение \"1\", то это \n;\t\tуказывает, что сегмент присутствует в памяти (это нужно для виртуальной \n;\t\tпамяти)\n;\t\t- privilege flag (флаг привилегии). Значение \"0\" - самый высокий уровень\n;\t\tпривилегии\n;\t\t- descriptor type (тип дескриптора). \"1\" - для сегмента кода или \n;\t\tсегмента данных\n; \t* Флаги типа:\n;\t\t- code (флаг кода). \"1\" - для кода, \"0\" - для даннных\n;\t\t- conformig (флаг подчинения). \"0\" - чтобы код в другом сегменте с\n;\t\tболее низким уровнем привилегий не смог вызвать код из этого сегмента - \n;\t\tэто ключ к защите памяти (memory protection).\n;\t\t- readable (читаемость). \"1\" - если читаемый, \"0\" - только исполняемый.\n;\t\t- writable. Разрешает сегменту данных быть записываемым, в противном\n;\t\tслучае, он будет доступен только для чтения.\n;\t\t- accessed (флаг доступа). Этот флаг устанавливается, когда происходит\n; \t\tобращение к сегменту.\n;\t\t- expand down. Флаг (бит), позволяющий сегменту расширяться вниз.\n;\t* 2-ые флаги:\n;\t\t- granulariy (гранулярность). \"0\" - байтовая гранулярность, лимит\n; \t\tзадается в байтах, если \"1\" - страничная гранулярность, в 4кб блоках.\n;\t\tЕсли выбрать страничную гранулярность и установить значение лимита как\n;\t\t0xfffff, то лимит умножится на 16*16*16 (4кб), и лимит станет 0xfffff000\n; \t\tпозволяя нашему сегменту занять 4гб места в памяти.\n;\t\t- 32-bit default. \"1\" - т.к. наш сегмент будет содержать 32-битный код.\n;\t\t- 64-bit code segment. \"0\" - т.к. не используется на 32-битных \n;\t\tпроцессорах.\n;\t\t- AVL (available). Определяет доступность сегмента для использования \n;\t\tсистемным программным обеспечением (используются только ОС).\n; ------------------------------------------------------------------------------\n\ngdt_start:\t\t\t\t\t; Эта пустая метка нужно для того чтобы удобнее\n\t\t\t\t\t\t\t; посчитать размер GDT для ее дескриптора \n\t\t\t\t\t\t\t; (end - start)\n\ngdt_null:\t\t\t\t\t; Необходимый нулевой дескриптор для GDT\n\tdd 0x0\t\t\t\t\t; dd - define double (двойное слово, т.е. 4 байта)\n\tdd 0x0\n\ngdt_code:\t\t\t\t\t; Определяем дескриптор сегмента кода\n\tdw 0xffff\t\t\t\t; Limit (bits 0-15)\n\tdw 0x0\t\t\t\t\t; Base (bits 0-15)\n\tdb 0x0\t\t\t\t\t; Base (bits 16-23)\n\tdb 10011010b\t\t\t; Первые флаги + флаги типа (смотрим по битам)\n\t\t\t\t\t\t\t; present: 1, privilege: 00, descriptor type: 1\n\t\t\t\t\t\t\t; code: 1, conforming: 0, readable: 1, accessed: 0\n\tdb 11001111b\t\t\t; Вторые флаги + длина сегмента (bits 16-19):\n\t\t\t\t\t\t\t; granularity: 1, 32-bit default: 1,\n\t\t\t\t\t\t\t; 64-bit default: 0, AVL: 0\n\tdb 0x0\t\t\t\t\t; Base (bits 24-31)\n\ngdt_data:\t\t\t\t\t; Определяем дескриптор сегмента кода\n\tdw 0xffff\t\t\t\t; Limit (bits 0-15)\n\tdw 0x0\t\t\t\t\t; Base (bits 0-15)\n\tdb 0x0\t\t\t\t\t; Base (bits 16-23)\n\tdb 10010010b\t\t\t; Первые флаги + флаги типа (смотрим по битам)\n\tdb 11001111b\t\t\t; Вторые флаги + длина сегмента (bits 16-19)\n\tdb 0x0\t\t\t\t\t; Base (bits 24-31)\n\ngdt_end:\t\t\t\t\t; Пустая метка\n\n\ngdt_descriptor:\t\t\t\t\t\t; дескриптор GDT\n\tdw gdt_end - gdt_start - 1\t\t; Размер GDT\n\tdd gdt_start\t\t\t\t\t; Адрес начала GDT\n\n\nCODE_SEG equ gdt_code - gdt_start\t; Определяем некоторые константы. \nDATA_SEG equ gdt_data - gdt_start\t; Они понадобятся для регистров сегментов в\n\t\t\t\t\t\t\t\t\t; 32-битном защищенном режиме. Например,\n\t\t\t\t\t\t\t\t\t; когда мы установим регистр DS = 0x10 (т.е\n\t\t\t\t\t\t\t\t\t; 16 байтов) в этом режиме, процессор\n\t\t\t\t\t\t\t\t\t; поймет что мы хотим использовать сегмент,\n\t\t\t\t\t\t\t\t\t; находящийся в смещении 0x10 в нашем GDT,\n\t\t\t\t\t\t\t\t\t; т.е. в нашем случае это сегмент данных\n\t\t\t\t\t\t\t\t\t; (0x0 -> NULL, 0x08 -> сегмент кода,\n\t\t\t\t\t\t\t\t\t; 0x10 -> сегмент данных)"
  },
  {
    "path": "guide/01-KERNEL/ex01/boot/kernel_entry.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t01-KERNEL\n; File:\t\tex00 / kernel_entry.asm\n; Title:\tКод, служащий входной точкой для функции kmain из kernel.c\n; ------------------------------------------------------------------------------\n; Description:\n; ------------------------------------------------------------------------------\n\n\n[bits 32]\n[extern kmain]\t; Определяем 'внешнюю' штуку с названием kmain - она понадобится\n\t\t\t\t; линкеру чтобы собрать все вместе\ncall kmain\t\t; Вызываем определенную выше функцию, которая будет доступна\n\t\t\t\t; после линковки. Это функция kmain из kernel.c\njmp $\n"
  },
  {
    "path": "guide/01-KERNEL/ex01/boot/print_hex.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex05 / print_hex.asm\n; Title:\tФункция вывода шестнадцатеричного числа на экран\n; ------------------------------------------------------------------------------\n; Description: \n;\tКак выглядит регистр EAX (32 b = 32 бита):\n;\n;\t\t\t\t\t\tEAX (32 b)\n;\t\t---------------------------------------------\n;\t\t|\t\t\t\t\t|\t\t\t|\t\t\t|\n;\t\t|\t\t\t\t\t|  AH (8 b)\t|  AL (8 b) |\n;\t\t|\t\t\t\t\t|\t\t\t|\t\t\t|\n;\t\t---------------------------------------------\n;\t\t\t\t\t\t\t\t\tAX (16 b)\n;\n;\tКак видим, EAX содержит в себе регистр AX, который в свою очередь разделен \n;\tна AH (a high, верхний) и AL (a low, нижний).\n; ------------------------------------------------------------------------------\n\n\n; Помним, что в main.asm:\n; DX  =  0x1fb6\n; BX  =  \"0x0000\" (а точнее, BX=\"0\", т.к. указывает на первый элемент)\n\nprint_hex:\n\tpusha\t\t\t\t\t\t; Сохраняем значения регистров в стеке\n\tmov cx, 0\t\t\t\t\t; Регистр CX будет служить счетчиком\n\nloop1:\n\tcmp cx, 4\t\t\t\t\t; if (CX < 4)\n\tjl print\t\t\t\t\t; Переходим к print\n\tjmp end\t\t\t\t\t\t; else переходим к end\n\nprint:\n\tmov ax, dx\t\t\t\t\t; В AX теперь 0x1fb6\n\tand ax, 0x000f\t\t\t\t; В AX теперь 6 (последняя цифра от 0x1fb6).\n\tcmp ax, 9\t\t\t\t\t; if (AX > 9) (проверяем обозначается ли число\n\t\t\t\t\t\t\t\t; буквой т.к. мы помним что цифра больше 9 в \n\t\t\t\t\t\t\t\t; 16-ричной системе исчисления обозначается\n\t\t\t\t\t\t\t\t; буквой латинского алфавита)\n\tjg num_to_abc\t\t\t\t; Переходим к num_to_abc\n\tjmp next\n\nnum_to_abc:\t\t\t\t\t\t; Перевод числа в букву (так, как она бы\n\t\t\t\t\t\t\t\t; выглядела в 16-ричной СИ), например число 15\n\t\t\t\t\t\t\t\t; в десятичной будет равно f в 16-ричной\n\tadd ax, 39\t\t\t\t\t; Добавляем к этому числу 39, чтобы затем еще\n\t\t\t\t\t\t\t\t; добавить 48 ('0'), получая код \n\t\t\t\t\t\t\t\t; соответсвующего символа в ASCII (например,\n\t\t\t\t\t\t\t\t; f (как число, то есть 15) + 39 + 48 (код '0')\n\t\t\t\t\t\t\t\t; = 102 (то есть 'f' в ASCII, как нам и нужно)\n\tjmp next\n\nnext:\n\tadd ax, '0'\t\t\t\t\t; Добавляем 48 в ax\n\tmov bx, HEX_OUT + 5\t\t\t; Теперь bx указывает на последний символ строки\n\t\t\t\t\t\t\t\t; HEX_OUT\n\tsub bx, cx\t\t\t\t\t; BX = BX - counter (для итерации)\n\tmov [bx], al\t\t\t\t; Так как мы разыменовываем bx (вот так: [bx]),\n\t\t\t\t\t\t\t\t; то [bx] это не регистр, а ссылка на память,\n\t\t\t\t\t\t\t\t; и так как ax = 16 бит (2 байта), чтобы нам не\n\t\t\t\t\t\t\t\t; перезаписать лишнюю память, в [bx] мы помещаем\n\t\t\t\t\t\t\t\t; не ax, а al, размер которого равен 1-му байту.\n\tror dx, 4\t\t\t\t\t; было: 0x1fb6, стало: 0x61fb (переносим\n\t\t\t\t\t\t\t\t; последнюю цифру в начало)\n\tinc cx\t\t\t\t\t\t; counter++\n\tjmp loop1\t\t\t\t\t; переходим обратно к loop1\n\nend:\n\tmov bx, HEX_OUT\t\t\t\t; Делаем так, чтобы bx снова указывал на первый\n\t\t\t\t\t\t\t\t; символ строки HEX_OUT\n\tcall print_string\t\t\t; выводи на экран строку из регистра bx\n\tpopa\t\t\t\t\t\t; Возвращаем регистрам их изначальное значение\n\tret\t\t\t\t\t\t\t; Заканчиваем выполнение функции\n\n\nHEX_OUT:\tdb \"0x0000\", 0"
  },
  {
    "path": "guide/01-KERNEL/ex01/boot/print_string.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex04 / print_string.asm\n; Title:\tФункция вывода строки на экран\n; ------------------------------------------------------------------------------\n; Description: null\n; ------------------------------------------------------------------------------\n\n\nprint_string:\t\t\t; Функция вывода строки на экран.\n\tpusha\t\t\t\t; Когда мы используем функции, мы можем модифицировать\n\t\t\t\t\t\t; регистры прямо в них, что нарушает чистоту функции,\n\t\t\t\t\t\t; т.е. мы можем перезаписывать какие-то внешние\n\t\t\t\t\t\t; данные. Для этого мы добавляем значение всех регистров\n\t\t\t\t\t\t; в стек с помощью команды pusha, а в конце функции\n\t\t\t\t\t\t; мы возвращаем регистрам их изначальные значения,\n\t\t\t\t\t\t; которые возьмем из стека (команда popa).\n\t\t\t\t\t\t\n\tmov ah, 0x0e\t\t; tele-type mode\n\tloop:\t\t\t\t; Метка loop (= цикл)\n\t\tmov al, [bx]\t; Перемещаем значение BX в AL, т.к. мы помним что в BX\n\t\t\t\t\t\t; лежит первый символ строки (см. ./main.asm)\n\t\tcmp al, 0\t\t; Команда cmp для сравнения AL и 0.\n\t\tje newline\t\t; (if) je = \"jump if equal\",\n\t\t\t\t\t\t; т.е. перемещаемся к коду с меткой return если AL == 0\n\t\tjmp put_char\t; (else) в противном случае перемещаемся к put_char.\n\nput_char:\t\t\t\t; Метка put_char - вывод символа на экран.\n\tint 0x10\t\t\t; Вызываем прерывание, которое позволяет вывести \n\t\t\t\t\t\t; на экран значение регистра AL, в котором лежит [bx].\n\tinc bx\t\t\t\t; inc <регистр> - увеличить на 1.\n\tjmp loop\t\t\t; Возвращаемся обратно к циклу.\n\nnewline:\n\tmov ah, 0x0e\n\tmov al, 0x0a\t; (0x0a) \\n - new line\n\tint 0x10\n\tmov al, 0x0d\t; (0x0d) \\r - carriage return\n\tint 0x10\n\tjmp return\n\nreturn:\t\t\t\t\t; Метка return - завершаем функцию\n\tpopa\t\t\t\t; Возвращаем регистрам их изначальные значения.\n\tret\t\t\t\t\t; Заканчиваем выполнение функции.\n"
  },
  {
    "path": "guide/01-KERNEL/ex01/boot/print_string_pm.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / print_string_pm.asm\n; Title:\tФункция вывода строки на экран в 32-битном защищенном режиме\n; ------------------------------------------------------------------------------\n; Description:\n;\tПлюсы 32-битного режима: возможность использовать 32-битные регистры и\n;\tадрессацию памяти, защищенную память виртуальную память\n;\tМинусы: отсутствие БИОС прерываний, требование наличия GDT (об этом позже)\n;\tВ этой программе мы напишем новую функцию печати строки, но без прерываний\n;\tБИОСа, а напрямую манипулируя VGA видеопамятью, вместо вызова int 0x10.\n;\tVGA память размещена начиная с адреса 0xb8000, и у VGA имеется специальный\n;\tтекстовый режим, поэтому нам не придется напрямую рисовать пиксели.\n;\tОсобенности:\n;\t1. Символ представляется в виде 2-х байтов. Первый байт - сам символ,\n;\tвторой байт - 4 бита на цвет текста и еще 4 на цвет фона. \n;\tНапример, чтобы распечатать символ 'A' белым текстом на черном фоне, мы\n;\tиспольузуем 0x410f: 0x41 == 'A', 0 == белый, f == черный.\n;\n;\t\tBit:     | 15 14 13 12 11 10 9 8 | 7 6 5 4 | 3 2 1 0 |\n;\t\tContent: | ASCII                 | FG      | BG      |\n;\n; ------------------------------------------------------------------------------\n\n\n[bits 32]\t\t\t\t\t; Используем 32-битный режим\n\n\t\t\t\t\t\t\t; Определяем некоторые константы\nVIDEO_MEMORY equ 0xb8000\t; = адрес начала памяти VGA\nWHITE_ON_BLACK equ 0x0f\t\t; = цвет символов (0x0f - белый на черном)\n\nprint_string_pm:\n\tpusha\n\tmov edx, VIDEO_MEMORY\t; Перемещаем в EDX адрес начала массива видеопамяти\n\nprint_string_pm_loop:\n\t\t\t\t\t\t\t; Помним, что AX (2б) = AH(1б) и AL(1б)\n\tmov al, [ebx]\t\t\t; Сохраняем символ из EBX в AL\n\tmov ah, WHITE_ON_BLACK\t; Устанавливаем цвет символов в AH\n\t\t\t\t\t\t\t; таким образом AX получается равен символу + цвету\n\n\tcmp al, 0\t\t\t\t; if (AL == 0), т.е. если конец массива, то\n\tje print_string_pm_done\t; заканчиваем выполнение функции\n\t\t\t\t\t\t\t; else:\n\tmov [edx], ax\t\t\t; video_memory[EDX] = AX\n\tadd ebx, 1\t\t\t\t; переходим к следующему символу (+1, просто массив)\n\tadd edx, 2\t\t\t\t; переходим к следующему адресу в VGA (+2 т.к.\n\t\t\t\t\t\t\t; два байта на символ)\n\n\tjmp print_string_pm_loop\n\nprint_string_pm_done:\n\tpopa\n\tret\n"
  },
  {
    "path": "guide/01-KERNEL/ex01/boot/switch.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / switch.asm\n; Title:\tПереключаемся в PM (Protected mode, защищенный режим)\n; ------------------------------------------------------------------------------\n; Description:\n;\tЧтобы сделать свитч, нам нужно:\n;\t\t1. Отключить прерывания (процессор просто будет их игнорировать), т.к.\n; \t\tв PM, прерывания обрабатываются совершенно по-другому в отличие от\n;\t\tReal Mode. Даже если процессор и смог бы распределить сигналы прерываний\n;\t\tпо конкретным BIOS обработчикам прерываний, БИОС обработчики будут\n;\t\tобрабатывать 16-битный код, что повлекло бы за собой ошибки.\n;\t\t2. Загрузить GDT дескриптор\n;\t\t3. Изменяем первый бит регистра управления cr0 на \"1\"\n;\t\thttps://en.wikipedia.org/wiki/Control_register#CR0\n;\t\t4. Т.к. процессор использует специальную технику, которая называется\n;\t\tназывается pipelining (гугли: вычислительный конвейер, полезная статья\n;\t\tна хабре: https://habr.com/ru/post/182002/), и поэтому сразу после того,\n;\t\tкак перевести процессор в PM (что мы и сделали в предыдущем пункте), \n;\t\tнам нужно заставить процессор завершить всю работу в конвейере, чтобы\n;\t\tбыть уверенным, что все будущие инструкции будут выполнены корректно.\n;\t\tКонвейер загружает в себя некоторые количество последующих после текущей\n;\t\tинструкций, но конвейеру не очень нравятся инструкции типа call и jmp,\n;\t\tт.к. процессор не знает полностью какие инструкции будут следовать за\n;\t\tними, в особенности если мы вызовем jmp или call \"прыгая\" в другой\n;\t\tсегмент. Поэтому нам нужно сделать \"дальний прыжок\", чтобы завершить\n;\t\tобрабатываемые в конвейере инструкции.\n;\t\tСам прыжок: \tjmp <сегмент>:<адрес смещения>\n; ------------------------------------------------------------------------------\n\n\n[bits 16]\n\nswitch_to_pm:\n\tcli\t\t\t\t\t\t; Отключаем прерывания (cli = clear interrupts)\n\t\n\tlgdt [gdt_descriptor]\t; Загружаем GDT дескриптор (lgdt = load GDT)\n\n\tmov eax, cr0\t\t\t; Чтобы перейти в PM, нужно чтобы первый бит\n\tor eax, 0x1\t\t\t\t; регистра управления cr0 был 1\n\tmov cr0, eax\n\n\tjmp CODE_SEG:init_pm\t; Делаем \"дальний прыжок\" в наш новый 32-битный\n\t\t\t\t\t\t\t; сегмент кода. Это так же заставляет процессор\n\t\t\t\t\t\t\t; завершить обрабатываемые в конвейере инструкции. \n\n[bits 32]\n\ninit_pm:\t\t\t\t\t; в PM, наши старые сегменты бесполезны, поэтому\n\tmov ax, DATA_SEG\t\t; мы делаем так, чтобы регистры всех сегментов\n\tmov ds, ax\t\t\t\t; указывали на сегмент данных, который мы определили\n\tmov ss, ax\t\t\t\t; в GDT (см. ./gdt.asm)\n\tmov es, ax\n\tmov fs, ax\n\tmov gs, ax\n\n\tmov ebp, 0x90000\t\t; Обновляем позицию стека, чтобы он был на самом\n\tmov esp, ebp\t\t\t; верху свободного места\n\n\tcall BEGIN_PM\t\t\t; Вызываем функцию из ./main.asm"
  },
  {
    "path": "guide/01-KERNEL/ex01/build/Makefile",
    "content": "C_FILES = $(shell find ../drivers/*.c ../*.c)\ntemp = $(notdir $(C_FILES))\nO_FILES = ${temp:.c=.o}\n\n# флаг для дебага для gcc\nCFLAGS = -g\n\nrun: os-image.bin\n\tqemu-system-i386 -fda os-image.bin\n\tmake clean\n\ndebug: os-image.bin kernel.elf\n\t# kernel.elf нужен для gdb как symbol-file\n\t# флаг -s указывает qemu открыть и прослушивать 1234 порт\n\t# чтобы gdb смог соединиться с ним для дебага\n\t# флаг -S указыает QEMU не запускать образ и подождать подключений\n\tqemu-system-i386 -s -S -fda os-image.bin\n\tmake clean\n\nos-image.bin: bootsect.bin kernel.bin\n\tcat bootsect.bin kernel.bin > os-image.bin\n\nbootsect.bin:\n\tcd ../boot/ && nasm bootsect.asm -f bin -o ../build/bootsect.bin && cd -\n\nkernel.bin: kernel_entry.o kernel.o\n\ti386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o kernel.o $(O_FILES) --oformat binary\n\nkernel_entry.o:\n\tnasm ../boot/kernel_entry.asm -f elf -o kernel_entry.o\n\nkernel.o:\n\ti386-elf-gcc ${CFLAGS} -ffreestanding -c ../kernel/kernel.c $(C_FILES)\n\nkernel.elf: kernel_entry.o kernel.o \n\t# как kernel.bin только без --oformat binary\n\ti386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o kernel.o $(O_FILES) -o kernel.elf\n\nclean:\n\trm *.bin *.o *.elf"
  },
  {
    "path": "guide/01-KERNEL/ex01/common.c",
    "content": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / common.c\n*\tTitle:\tВсякие удобные константы, типы и функции\n* ------------------------------------------------------------------------------\n*\tDescription:\n* ----------------------------------------------------------------------------*/\n\n\n#include \"common.h\"\n\nvoid\tmemcpy(u8 *src, u8 *dest, u32 bytes)\n{\n\tu32 i;\n\n\ti = 0;\n\twhile (i < bytes)\n\t{\n\t\tdest[i] = src[i];\n\t\ti++;\n\t}\n}"
  },
  {
    "path": "guide/01-KERNEL/ex01/common.h",
    "content": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / common.h\n*\tTitle:\tВсякие удобные константы, типы и функции\n* ------------------------------------------------------------------------------\n*\tDescription:\n* ----------------------------------------------------------------------------*/\n\n\n#ifndef COMMON_H\n#define COMMON_H\n\n// Указанная размерность характерна только для архитектуры x86\n// Подробнее про типы данных: https://metanit.com/cpp/c/2.3.php\n\ntypedef unsigned int\tu32;\t// беззнаковое целое число размером 32 бита\ntypedef \t\t int\ts32; \t// целое число 32 бита со знаком\ntypedef unsigned short\tu16;\t// и т.д.\ntypedef \t\t short\ts16;\ntypedef unsigned char\tu8;\ntypedef \t\t char\ts8;\n\nvoid\tmemcpy(u8 *src, u8 *dest, u32 bytes);\n\n#endif\n"
  },
  {
    "path": "guide/01-KERNEL/ex01/drivers/lowlevel_io.c",
    "content": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / drivers / lowlevel_io.c\n*\tTitle:\tНизкоуровневые I/O функции для работы с девайсами\n* ------------------------------------------------------------------------------\n*\tDescription:\n*\t\tОбычно есть два способа взаимодействовать с hardware - memory-mapped I/O\n*\t\tи I/O ports. \n*\t\tЕсли девайс использует memory-mapped I/O, то чтобы передать ему \n*\t\tкакую-либо информацию, нужно записать ее в специальный адрес. Например, \n*\t\tчтобы вывести символ на экран, мы записывали его в адрес 0xb8000.\n*\t\tЕсли девайс использует I/O ports, то для взаимодействия с ним\n*\t\tиспользуются инструкции ассемблера in и out.\n*\n*       Чтобы работать с дейвасом, нам достаточно знать что в нем есть некий\n*       контролирующий чип, который делает всю грязную работу за нас и позволяет\n*       процессору взаимодействовать с напрямую с железом дейваса. У такого чипа\n*       есть регистры в которые можно что-то записывать или что-то из них\n*       читать, и значение этих регистров указывет чипу что делать.\n*\n*       Как мы будем читать/писать в регистры контролирующих чипов девайса? Вот\n*       что нужно знать:\n*       1. В архитектуре процессоров Интел, регистры контроля девайсами\n*       расположены в специальном I/O адресном пространстве.\n*       2. Инструкции ввода-ввывода используют такой синтаксис (intel syntax):\n*       in <записать результат сюда (регистр процессора)>, <прочитать\n*                                       содержимое отсюда (регистр девайса)>\n*       out <записать сюда (регистр девайса)>, <вот это>\n*       3. К сожалению, в языке Си нет подобных конструкций, так что нам\n*       придется использовать inline assembly. Ща посмотрим как.\n* ----------------------------------------------------------------------------*/\n\n\nunsigned char   port_byte_in(unsigned short port)\n{\n    /* Функция-обертка над assembly, читающая 1 байт из параметра port */\n    /* unsigned short port: адрес регистра какого-либо девайса, из которого */\n    /* мы что-то прочтем. */\n\n    /* Используется другой синтаксис ассембли (GAS). Обратите внимание, что */\n    /* выражение \"mov dest, src\" в GAS мы запишем как \"mov src, dest\", т.е. */\n    /* \"in dx, al\" означает прочитать содержимое порта (адрес которого */\n    /* находится в DX) и положить в AL. */\n    /* Символ % означает регистр, а т.к. % - escape symbol, то мы */\n    /* пишем еще один %. */\n    /* Перемещаем результат в регистр AL т.к. размер AL == 1 байт */\n    unsigned char result;\n\t__asm__(\"in %%dx, %%al\" : \"=a\" (result) : \"d\" (port));\n\t/* разберем только что вызванную функцию: */\n\t/* \"in %%dx, %%al\"\t\t- Прочитать содержимое порта и положить это в AL */\n\t/* : \"=a\" (result)\t\t- Положить значение AL в переменную result */\n\t/* : \"d\" (port)\t\t\t- Загрузить port в регистр EDX (extended DX: 32b) */\n    return (result);\t\t/* Возвращаем прочитанное содержимое из port */\n}\n\n\nvoid    port_byte_out(unsigned short port, unsigned char data)\n{\n    /* Функция-обертка над assembly, пишущая data (1 байт) в port */\n    /* unsigned short port: адрес регистра девайса, в который что-то запишем */\n    /* unsigned char data: 1 байт какой-то информации (например, символ) */\n\t__asm__(\"out %%al, %%dx\" : : \"a\" (data), \"d\" (port));\n\t/* разберем только что вызванную функцию: */\n\t/* \"out %%al, %%dx\"\t\t- Записать data в port */\n\t/* : : \"a\" (data)\t\t- Загрузить data в регистр EAX */\n\t/* : \"d\" (port)\t\t\t- Загрузить port в регистр EDX */\n}\n\n\nunsigned char   port_word_in(unsigned short port)\n{\n    /* Функция-обертка над assembly, читающая 2 байта из параметра port */\n    /* Перемещаем результат в регистр AX т.к. размер AX == 2 байта */\n    unsigned short result;\n    __asm__(\"in %%dx, %%ax\" : \"=a\" (result) : \"d\" (port));\n    return (result);\n}\n\n\nvoid port_word_out(unsigned short port, unsigned short data)\n{\n    /* Функция-обертка над assembly, пишущая data (2 байта, т.е. word) в port */\n    __asm__(\"out %%ax, %%dx\" : : \"a\" (data), \"d\" (port));\n}\n"
  },
  {
    "path": "guide/01-KERNEL/ex01/drivers/lowlevel_io.h",
    "content": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / drivers / lowlevel_io.h\n*\tTitle:\tЗаголовочный файл для lowlevel_io.c\n* ------------------------------------------------------------------------------\n*\tDescription:\n* ----------------------------------------------------------------------------*/\n\n\nunsigned char   port_byte_in(unsigned short port);\nvoid    port_byte_out(unsigned short port, unsigned char data);\nunsigned char   port_word_in(unsigned short port);\nvoid port_word_out(unsigned short port, unsigned short data);\n"
  },
  {
    "path": "guide/01-KERNEL/ex01/drivers/screen.c",
    "content": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / drivers / screen.c\n*\tTitle:\tФункции работы с экраном\n* ------------------------------------------------------------------------------\n*\tDescription:\n* ----------------------------------------------------------------------------*/\n\n\n#include \"screen.h\"\n#include \"lowlevel_io.h\"\n#include \"../common.h\"\n\n\nvoid\tkprint(u8 *str)\n{\n\t/* Функция печати строки */\n\t\n\t// u8 *str: указатель на строку (на первый символ строки). Строка должна\n\t// быть null-terminated.\n\n\twhile (*str)\n\t{\n\t\tputchar(*str, WHITE_ON_BLACK);\n\t\tstr++;\n\t}\n}\n\nvoid\tputchar(u8 character, u8 attribute_byte)\n{\n\t/* Более высокоуровневая функция печати символа */\n\n\t// u8 character: байт, соответствующий символу\n\t// u8 attribute_byte: байт, соответствующий цвету текста/фона символа\n\n\tu16 offset;\n\n\toffset = get_cursor();\n\tif (character == '\\n')\n\t{\n\t\t// Переводим строку.\n\t\tif ((offset / 2 / MAX_COLS) == (MAX_ROWS - 1)) \n\t\t\tscroll_line();\n\t\telse\n\t\t\tset_cursor((offset - offset % MAX_COLS) + MAX_COLS*2);\n\t}\n\telse \n\t{\n\t\tif (offset == (MAX_COLS * MAX_ROWS * 2)) scroll_line();\n\t\twrite(character, attribute_byte, offset);\n\t\tset_cursor(offset+2);\n\t}\n}\n\nvoid\tscroll_line()\n{\n\t/* Функция скроллинга */\n\n\tu8 i = 1;\t\t// Начинаем со второй строки.\n\tu16 last_line;\t// Начало последней строки.\n\n\twhile (i < MAX_ROWS)\n\t{\n\t\tmemcpy(\n\t\t\t(u8 *)(VIDEO_ADDRESS + (MAX_COLS * i * 2)),\n\t\t\t(u8 *)(VIDEO_ADDRESS + (MAX_COLS * (i-1) * 2)),\n\t\t\t(MAX_COLS*2)\n\t\t);\n\t\ti++;\n\t}\n\n\tlast_line = (MAX_COLS*MAX_ROWS*2) - MAX_COLS*2;\n\ti = 0;\n\twhile (i < MAX_COLS)\n\t{\n\t\twrite('\\0', WHITE_ON_BLACK, (last_line + i * 2));\n\t\ti++;\n\t}\n\tset_cursor(last_line);\n}\n\nvoid\tclear_screen()\n{\n\t/* Функция очистки экрана */\n\n\tu16\toffset = 0;\n\twhile (offset < (MAX_ROWS * MAX_COLS * 2))\n\t{\n\t\twrite('\\0', WHITE_ON_BLACK, offset);\n\t\toffset += 2;\n\t}\n\tset_cursor(0);\n}\n\nvoid\twrite(u8 character, u8 attribute_byte, u16 offset)\n{\n\t/* Функция печати символа на экран с помощью VGA по адресу 0xb8000 */\n\n\t// u8 character: байт, соответствующий символу\n\t// u8 attribute_byte: байт, соответствующий цвету текста/фона символа\n\t// u16 offset: смещение (позиция), по которому нужно распечатать символ\n\t\n\tu8 *vga = (u8 *) VIDEO_ADDRESS;\n\tvga[offset] = character;\n\tvga[offset + 1] = attribute_byte;\n}\n\nu16\t\tget_cursor()\n{\n\t/* Функция, возвращающая позицию курсора (char offset). */\n\n\tport_byte_out(REG_SCREEN_CTRL, 14);\t\t\t\t// Запрашиваем верхний байт\n\tu8 high_byte = port_byte_in(REG_SCREEN_DATA);\t// Принимаем его\n\tport_byte_out(REG_SCREEN_CTRL, 15);\t\t\t\t// Запрашиваем нижний байт\n\tu8 low_byte = port_byte_in(REG_SCREEN_DATA);\t// Принимаем и его\n\t// Возвращаем смещение умножая его на 2, т.к. порты возвращают смещение в\n\t// клетках экрана (cell offset), а нам нужно в символах (char offset), т.к.\n\t// на каждый символ у нас 2 байта\n\treturn (((high_byte << 8) + low_byte) * 2);\n}\n\nvoid\tset_cursor(u16 pos)\n{\n\t/* Функция, устаналивающая курсор по смещнию (позиции) pos */\n\t/* Поиграться с битами можно тут http://bitwisecmd.com/ */\n\n\tpos /= 2;\t// конвертируем в cell offset (в позицию по клеткам, а не\n\t\t\t\t// символам)\n\t// Устанавливаем позицию курсора\n\tport_byte_out(REG_SCREEN_CTRL, 14);\t\t\t// Указываем, что будем\n\t\t\t\t\t\t\t\t\t\t\t\t// передавать верхний байт\n\tport_byte_out(REG_SCREEN_DATA, (pos >> 8));\t// Передаем верхний байт\n\tport_byte_out(REG_SCREEN_CTRL, 15);\t\t\t// Указываем, что будем\n\t\t\t\t\t\t\t\t\t\t\t\t// передавать нижний байт\n\tport_byte_out(REG_SCREEN_DATA, (pos & 0xff));\t// передаем нижний байт\n}\n"
  },
  {
    "path": "guide/01-KERNEL/ex01/drivers/screen.h",
    "content": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / drivers / screen.h\n*\tTitle:\tЗаголовочный файл для screen.c\n* ------------------------------------------------------------------------------\n*\tDescription:\n* ----------------------------------------------------------------------------*/\n\n\n#include \"../common.h\"\n\n#define VIDEO_ADDRESS 0xb8000\t// Адрес начала VGA для печати символов\n#define MAX_ROWS 25\t\t\t\t// макс. строк\n#define MAX_COLS 80\t\t\t\t// макс. столбцов\n\n#define WHITE_ON_BLACK 0x0f\t\t// 0x0 == white fg, 0xf == black bg\n\n// Адреса I/O портов для взаимодействия с экраном.\n#define REG_SCREEN_CTRL 0x3d4\t// этот порт для описания данных\n#define REG_SCREEN_DATA 0x3d5\t// а этот порт для самих данных\n\nvoid\tkprint(u8 *str);\nvoid\tputchar(u8 character, u8 attribute_byte);\nvoid\tclear_screen();\nvoid\twrite(u8 character, u8 attribute_byte, u16 offset);\nvoid\tscroll_line();\nu16\t\tget_cursor();\nvoid\tset_cursor(u16 pos);\n"
  },
  {
    "path": "guide/01-KERNEL/ex01/kernel/kernel.c",
    "content": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / kernel / kernel.c\n*\tTitle:\tЯдро\n* ------------------------------------------------------------------------------\n*\tDescription:\n* ----------------------------------------------------------------------------*/\n\n\n#include \"../common.h\"\n#include \"../drivers/screen.h\"\n\n\ns32\t\tkmain()\n{\t\n\t/* Простая программа чтобы продемонстрировать чего мы добились. */\n\t/* Поиграйтесь с кодом и добавьте больше логики, попробуйте заполнить все */\n\t/* 25 строк чтобы посмотреть работу функции scroll_line. */\n\n\tu8 i;\n\t\n\tclear_screen();\n\n\ti = 0;\n\twhile (i < 3)\n\t{\n\t\tkprint(\"Hello, world!\\n\");\n\t\ti++;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/boot/bootsect.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t01-KERNEL\n; File:\t\tex00 / bootsect.asm\n; Title:\tПрограмма загрузочного сектора, которая загружает ядро, написанное\n;\t\t\tна C в 32-битный защищенный режим.\n; ------------------------------------------------------------------------------\n; Description:\n; ------------------------------------------------------------------------------\n\n\n[org 0x7c00]\n\nKERNEL_OFFSET equ 0x1000\t; Смещение в памяти, из которого мы загрузим ядро\n\n\tmov [BOOT_DRIVE], dl\t; BIOS stores our boot drive in DL , so it ’s\n\t\t\t\t\t\t\t; best to remember this for later. (Remember that\n\t\t\t\t\t\t\t; the BIOS sets us the boot drive in 'dl' on boot)\n\tmov bp, 0x9000\t\t\t; Устанавливаем стек\n\tmov sp, bp\n\n\tmov bx, MSG_REAL_MODE\t; Печатаем сообщение\n\tcall print_string\n\n\tcall load_kernel\t\t; Загружаем ядро\n\tcall switch_to_pm\t\t; Переключаемся в Защищенный Режим\n\tjmp $\n\n%include \"print_string.asm\"\t\t; ф. печати строки\n%include \"print_hex.asm\"\t\t; ф. печати 16-ричного числа\n%include \"disk_load.asm\"\t\t; ф. чтения диска\n%include \"print_string_pm.asm\"\t; ф. печати строки (32PM)\n%include \"switch.asm\"\t\t\t; ф. переключения в 32PM\n%include \"gdt.asm\"\t\t\t\t; таблица GDT\n\n[bits 16]\n\nload_kernel:\n\tmov bx, MSG_LOAD_KERNEL\n\tcall print_string\t\t; Печатаем сообщение о том, то мы загружаем ядро\n\t\t\t\t\t\t\t; Устанавливаем параметры для функции disk_load:\n\tmov bx, KERNEL_OFFSET\t; Загрузим данные в место памяти по\t\tTODO: disk_load main lookup\n\t\t\t\t\t\t\t; смещению KERNEL_OFFSET\n\tmov dh, 16\t\t\t\t; Загрузим много секторов. *\n\tmov dl, [BOOT_DRIVE]\t; Загрузим данные из BOOT_DRIVE (Возвращаем BOOT_DRIVE)\n\tcall disk_load\t\t\t; Вызываем функцию disk_load\n\tret\n\n\n[bits 32]\t\t\t\t\t; Сюда мы попадем после переключения в 32PM\n\nBEGIN_PM:\n\tmov ebx, MSG_PROT_MODE\n\tcall print_string_pm\t; Печатаем сообщение об успешной загрузке в 32PM\n\tcall KERNEL_OFFSET\t\t; Переходим в адрес, по которому загрузился код ядра\n\tjmp $\n\n\nBOOT_DRIVE:\t\t\tdb 0\nMSG_REAL_MODE:\t\tdb \"Started in 16-bit Real Mode\", 0\nMSG_PROT_MODE:\t\tdb \"Successfully landed in 32-bit Protected mode\", 0\nMSG_LOAD_KERNEL:\tdb \"Loading kernel into VIDEO_MEMORY\", 0\n\ntimes 510-($-$$) db 0\ndw 0xaa55\n\n; ------\n; * - Забавный факт: если загрузить меньше секторов, то мы столкнемся со\n; странными ошибками когда будем писать ядро на Си. Например, аргументы\n; функции могут быть повреждены, а строки \"обрезаны\".\n"
  },
  {
    "path": "src/boot/disk_load.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / disk_load.asm\n; Title:\tФункция чтения диска\n; ------------------------------------------------------------------------------\n; Description:\n;\tЧтобы лучше понять, что здесь происходит, разберитесь с тем, что такое CHS\n;\tпо ссылке https://ru.wikipedia.org/wiki/CHS\n; ------------------------------------------------------------------------------\n\n\ndisk_load:\n\tpush dx\n\tmov ah, 0x02\t\t\t; Указвыаем БИОСу что нам нужна рутина чтения диска\n\t\t\t\t\t\t\t; Указываем что нам нужно:\n\tmov al, dh\t\t\t\t; 1. Прочитать кол-во секторов, равное значению в dh\n\tmov ch, 0x00\t\t\t; 2. Выбрать нулевой цилиндр\n\tmov dh, 0x00\t\t\t; 3. Выбрать нулевую головку\n\tmov cl, 0x02\t\t\t; 4. Начинать считывать со второго сектора (т.е.\n\t\t\t\t\t\t\t; первый свободный сектор сразу после загруочного \n\t\t\t\t\t\t\t; сектора, т.к. загрузочный сектор находится по \n\t\t\t\t\t\t\t; адресу 0x01)\n\tint 0x13\t\t\t\t; Вызываем прерывание для чтения\n\n\t\t\t\t\t\t\t; У БИОСа может не получиться прочитать диск, и\n\t\t\t\t\t\t\t; чтобы дать нам знать что произошла ошибка, он,\n\t\t\t\t\t\t\t; во-первых, обновляет специальный флаг CF (carry \n\t\t\t\t\t\t\t; flag) специальным значением, которое означает \n\t\t\t\t\t\t\t; ошибку, а во-вторых, кладет в регистр AL кол-во \n\t\t\t\t\t\t\t; секторов, которые у него получилось прочитать.\n\t\n\tjc disk_error\t\t\t; jc - инструкция для прыжка на указанную метку,\n\t\t\t\t\t\t\t; которая выполняется только если CF (carry flag)\n\t\t\t\t\t\t\t; сигнализирует об ошибке\n\n\tpop dx\t\t\t\t\t; Восстанавливаем регистр DX из стека\n\tcmp dh, al\t\t\t\t; если AL (кол-во прочитанных секторов) != DH\n\t\t\t\t\t\t\t; (предполагаемое кол-во секторов),\n\tjne disk_sectors_error\t; то выводим на экран сообщение об ошибке и зависаем\n\t\t\t\t\t\t\t; (то есть запускаем бесконечный цикл)\n\tjmp disk_success\n\tjmp disk_exit\t\t\t; Заканчиваем выполнение функции\n\ndisk_success:\n\tmov bx, SUCCESS_MSG\n\tcall print_string\n\tjmp disk_exit\n\ndisk_error:\n\tmov bx, DISK_ERR_MSG\t; Перемещаем в BX сообщение об ошибке\n\tcall print_string\t\t; Выводим его на экран\n\tmov dh, al\n\tcall print_hex\n\tjmp disk_loop\t\t\t; бесконечный цикл\n\ndisk_sectors_error:\n\tmov bx, SECTORS_ERR_MSG\n\tcall print_string\n\nSUCCESS_MSG:\n\tdb \"Disk was successfully read \", 0\n\nDISK_ERR_MSG:\n\tdb \"Disk read error! \", 0\n\nSECTORS_ERR_MSG:\n\tdb \"Incorrect number of sectors read \", 0\n\ndisk_loop:\n\tjmp $\n\ndisk_exit:\n\tret"
  },
  {
    "path": "src/boot/gdt.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / gdt.asm\n; Title:\tОпределяем GDT (глобальная таблица дескрипторов)\n; ------------------------------------------------------------------------------\n; Description:\n; \tСпособ, которым процессор переводит логический адрес в физический, в\n; \t32-битном защищенном режиме отличается от 16-битного реального режима.\n; \tВместо того, чтобы умножить значение регистра сегмента на 16 и прибавить к\n; \tэтому \"смещение\" (offset), регистр сегмента становится индексом \n; \tопределенного дескриптора сегмента в GDT.\n; \tДескриптор сегмента - это 8-битная структура, которая определяет свойства\n; \tэтого сегмента:\n; \t\t- Base address (32 bits), определяющий откуда сегмент начинается в\n; \t\tфизической памяти.\n; \t\t- Segment Limit (20 bits), определяющий размер сегмента\n;\t\t- Различные флаги, которые устанавливают каким образом процессор будет\n; \t\t\"относиться\" к сегментам, например уровень привилегий и т.д.\n;\n; \tФлаги:\n;\t* 1-ые флаги:\n; \t\t- present flag (флаг присутствия). Если его значение \"1\", то это \n;\t\tуказывает, что сегмент присутствует в памяти (это нужно для виртуальной \n;\t\tпамяти)\n;\t\t- privilege flag (флаг привилегии). Значение \"0\" - самый высокий уровень\n;\t\tпривилегии\n;\t\t- descriptor type (тип дескриптора). \"1\" - для сегмента кода или \n;\t\tсегмента данных\n; \t* Флаги типа:\n;\t\t- code (флаг кода). \"1\" - для кода, \"0\" - для даннных\n;\t\t- conformig (флаг подчинения). \"0\" - чтобы код в другом сегменте с\n;\t\tболее низким уровнем привилегий не смог вызвать код из этого сегмента - \n;\t\tэто ключ к защите памяти (memory protection).\n;\t\t- readable (читаемость). \"1\" - если читаемый, \"0\" - только исполняемый.\n;\t\t- writable. Разрешает сегменту данных быть записываемым, в противном\n;\t\tслучае, он будет доступен только для чтения.\n;\t\t- accessed (флаг доступа). Этот флаг устанавливается, когда происходит\n; \t\tобращение к сегменту.\n;\t\t- expand down. Флаг (бит), позволяющий сегменту расширяться вниз.\n;\t* 2-ые флаги:\n;\t\t- granulariy (гранулярность). \"0\" - байтовая гранулярность, лимит\n; \t\tзадается в байтах, если \"1\" - страничная гранулярность, в 4кб блоках.\n;\t\tЕсли выбрать страничную гранулярность и установить значение лимита как\n;\t\t0xfffff, то лимит умножится на 16*16*16 (4кб), и лимит станет 0xfffff000\n; \t\tпозволяя нашему сегменту занять 4гб места в памяти.\n;\t\t- 32-bit default. \"1\" - т.к. наш сегмент будет содержать 32-битный код.\n;\t\t- 64-bit code segment. \"0\" - т.к. не используется на 32-битных \n;\t\tпроцессорах.\n;\t\t- AVL (available). Определяет доступность сегмента для использования \n;\t\tсистемным программным обеспечением (используются только ОС).\n; ------------------------------------------------------------------------------\n\ngdt_start:\t\t\t\t\t; Эта пустая метка нужно для того чтобы удобнее\n\t\t\t\t\t\t\t; посчитать размер GDT для ее дескриптора \n\t\t\t\t\t\t\t; (end - start)\n\ngdt_null:\t\t\t\t\t; Необходимый нулевой дескриптор для GDT\n\tdd 0x0\t\t\t\t\t; dd - define double (двойное слово, т.е. 4 байта)\n\tdd 0x0\n\ngdt_code:\t\t\t\t\t; Определяем дескриптор сегмента кода\n\tdw 0xffff\t\t\t\t; Limit (bits 0-15)\n\tdw 0x0\t\t\t\t\t; Base (bits 0-15)\n\tdb 0x0\t\t\t\t\t; Base (bits 16-23)\n\tdb 10011010b\t\t\t; Первые флаги + флаги типа (смотрим по битам)\n\t\t\t\t\t\t\t; present: 1, privilege: 00, descriptor type: 1\n\t\t\t\t\t\t\t; code: 1, conforming: 0, readable: 1, accessed: 0\n\tdb 11001111b\t\t\t; Вторые флаги + длина сегмента (bits 16-19):\n\t\t\t\t\t\t\t; granularity: 1, 32-bit default: 1,\n\t\t\t\t\t\t\t; 64-bit default: 0, AVL: 0\n\tdb 0x0\t\t\t\t\t; Base (bits 24-31)\n\ngdt_data:\t\t\t\t\t; Определяем дескриптор сегмента кода\n\tdw 0xffff\t\t\t\t; Limit (bits 0-15)\n\tdw 0x0\t\t\t\t\t; Base (bits 0-15)\n\tdb 0x0\t\t\t\t\t; Base (bits 16-23)\n\tdb 10010010b\t\t\t; Первые флаги + флаги типа (смотрим по битам)\n\tdb 11001111b\t\t\t; Вторые флаги + длина сегмента (bits 16-19)\n\tdb 0x0\t\t\t\t\t; Base (bits 24-31)\n\ngdt_end:\t\t\t\t\t; Пустая метка\n\n\ngdt_descriptor:\t\t\t\t\t\t; дескриптор GDT\n\tdw gdt_end - gdt_start - 1\t\t; Размер GDT\n\tdd gdt_start\t\t\t\t\t; Адрес начала GDT\n\n\nCODE_SEG equ gdt_code - gdt_start\t; Определяем некоторые константы. \nDATA_SEG equ gdt_data - gdt_start\t; Они понадобятся для регистров сегментов в\n\t\t\t\t\t\t\t\t\t; 32-битном защищенном режиме. Например,\n\t\t\t\t\t\t\t\t\t; когда мы установим регистр DS = 0x10 (т.е\n\t\t\t\t\t\t\t\t\t; 16 байтов) в этом режиме, процессор\n\t\t\t\t\t\t\t\t\t; поймет что мы хотим использовать сегмент,\n\t\t\t\t\t\t\t\t\t; находящийся в смещении 0x10 в нашем GDT,\n\t\t\t\t\t\t\t\t\t; т.е. в нашем случае это сегмент данных\n\t\t\t\t\t\t\t\t\t; (0x0 -> NULL, 0x08 -> сегмент кода,\n\t\t\t\t\t\t\t\t\t; 0x10 -> сегмент данных)"
  },
  {
    "path": "src/boot/kernel_entry.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t01-KERNEL\n; File:\t\tex00 / kernel_entry.asm\n; Title:\tКод, служащий входной точкой для функции kmain из kernel.c\n; ------------------------------------------------------------------------------\n; Description:\n; ------------------------------------------------------------------------------\n\n\n[bits 32]\n[extern kmain]\t; Определяем 'внешнюю' штуку с названием kmain - она понадобится\n\t\t\t\t; линкеру чтобы собрать все вместе\ncall kmain\t\t; Вызываем определенную выше функцию, которая будет доступна\n\t\t\t\t; после линковки. Это функция kmain из kernel.c\njmp $\n"
  },
  {
    "path": "src/boot/print_hex.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex05 / print_hex.asm\n; Title:\tФункция вывода шестнадцатеричного числа на экран\n; ------------------------------------------------------------------------------\n; Description: \n;\tКак выглядит регистр EAX (32 b = 32 бита):\n;\n;\t\t\t\t\t\tEAX (32 b)\n;\t\t---------------------------------------------\n;\t\t|\t\t\t\t\t|\t\t\t|\t\t\t|\n;\t\t|\t\t\t\t\t|  AH (8 b)\t|  AL (8 b) |\n;\t\t|\t\t\t\t\t|\t\t\t|\t\t\t|\n;\t\t---------------------------------------------\n;\t\t\t\t\t\t\t\t\tAX (16 b)\n;\n;\tКак видим, EAX содержит в себе регистр AX, который в свою очередь разделен \n;\tна AH (a high, верхний) и AL (a low, нижний).\n; ------------------------------------------------------------------------------\n\n\n; Помним, что в main.asm:\n; DX  =  0x1fb6\n; BX  =  \"0x0000\" (а точнее, BX=\"0\", т.к. указывает на первый элемент)\n\nprint_hex:\n\tpusha\t\t\t\t\t\t; Сохраняем значения регистров в стеке\n\tmov cx, 0\t\t\t\t\t; Регистр CX будет служить счетчиком\n\nloop1:\n\tcmp cx, 4\t\t\t\t\t; if (CX < 4)\n\tjl print\t\t\t\t\t; Переходим к print\n\tjmp end\t\t\t\t\t\t; else переходим к end\n\nprint:\n\tmov ax, dx\t\t\t\t\t; В AX теперь 0x1fb6\n\tand ax, 0x000f\t\t\t\t; В AX теперь 6 (последняя цифра от 0x1fb6).\n\tcmp ax, 9\t\t\t\t\t; if (AX > 9) (проверяем обозначается ли число\n\t\t\t\t\t\t\t\t; буквой т.к. мы помним что цифра больше 9 в \n\t\t\t\t\t\t\t\t; 16-ричной системе исчисления обозначается\n\t\t\t\t\t\t\t\t; буквой латинского алфавита)\n\tjg num_to_abc\t\t\t\t; Переходим к num_to_abc\n\tjmp next\n\nnum_to_abc:\t\t\t\t\t\t; Перевод числа в букву (так, как она бы\n\t\t\t\t\t\t\t\t; выглядела в 16-ричной СИ), например число 15\n\t\t\t\t\t\t\t\t; в десятичной будет равно f в 16-ричной\n\tadd ax, 39\t\t\t\t\t; Добавляем к этому числу 39, чтобы затем еще\n\t\t\t\t\t\t\t\t; добавить 48 ('0'), получая код \n\t\t\t\t\t\t\t\t; соответсвующего символа в ASCII (например,\n\t\t\t\t\t\t\t\t; f (как число, то есть 15) + 39 + 48 (код '0')\n\t\t\t\t\t\t\t\t; = 102 (то есть 'f' в ASCII, как нам и нужно)\n\tjmp next\n\nnext:\n\tadd ax, '0'\t\t\t\t\t; Добавляем 48 в ax\n\tmov bx, HEX_OUT + 5\t\t\t; Теперь bx указывает на последний символ строки\n\t\t\t\t\t\t\t\t; HEX_OUT\n\tsub bx, cx\t\t\t\t\t; BX = BX - counter (для итерации)\n\tmov [bx], al\t\t\t\t; Так как мы разыменовываем bx (вот так: [bx]),\n\t\t\t\t\t\t\t\t; то [bx] это не регистр, а ссылка на память,\n\t\t\t\t\t\t\t\t; и так как ax = 16 бит (2 байта), чтобы нам не\n\t\t\t\t\t\t\t\t; перезаписать лишнюю память, в [bx] мы помещаем\n\t\t\t\t\t\t\t\t; не ax, а al, размер которого равен 1-му байту.\n\tror dx, 4\t\t\t\t\t; было: 0x1fb6, стало: 0x61fb (переносим\n\t\t\t\t\t\t\t\t; последнюю цифру в начало)\n\tinc cx\t\t\t\t\t\t; counter++\n\tjmp loop1\t\t\t\t\t; переходим обратно к loop1\n\nend:\n\tmov bx, HEX_OUT\t\t\t\t; Делаем так, чтобы bx снова указывал на первый\n\t\t\t\t\t\t\t\t; символ строки HEX_OUT\n\tcall print_string\t\t\t; выводи на экран строку из регистра bx\n\tpopa\t\t\t\t\t\t; Возвращаем регистрам их изначальное значение\n\tret\t\t\t\t\t\t\t; Заканчиваем выполнение функции\n\n\nHEX_OUT:\tdb \"0x0000\", 0"
  },
  {
    "path": "src/boot/print_string.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex04 / print_string.asm\n; Title:\tФункция вывода строки на экран\n; ------------------------------------------------------------------------------\n; Description: null\n; ------------------------------------------------------------------------------\n\n\nprint_string:\t\t\t; Функция вывода строки на экран.\n\tpusha\t\t\t\t; Когда мы используем функции, мы можем модифицировать\n\t\t\t\t\t\t; регистры прямо в них, что нарушает чистоту функции,\n\t\t\t\t\t\t; т.е. мы можем перезаписывать какие-то внешние\n\t\t\t\t\t\t; данные. Для этого мы добавляем значение всех регистров\n\t\t\t\t\t\t; в стек с помощью команды pusha, а в конце функции\n\t\t\t\t\t\t; мы возвращаем регистрам их изначальные значения,\n\t\t\t\t\t\t; которые возьмем из стека (команда popa).\n\t\t\t\t\t\t\n\tmov ah, 0x0e\t\t; tele-type mode\n\tloop:\t\t\t\t; Метка loop (= цикл)\n\t\tmov al, [bx]\t; Перемещаем значение BX в AL, т.к. мы помним что в BX\n\t\t\t\t\t\t; лежит первый символ строки (см. ./main.asm)\n\t\tcmp al, 0\t\t; Команда cmp для сравнения AL и 0.\n\t\tje newline\t\t; (if) je = \"jump if equal\",\n\t\t\t\t\t\t; т.е. перемещаемся к коду с меткой return если AL == 0\n\t\tjmp put_char\t; (else) в противном случае перемещаемся к put_char.\n\nput_char:\t\t\t\t; Метка put_char - вывод символа на экран.\n\tint 0x10\t\t\t; Вызываем прерывание, которое позволяет вывести \n\t\t\t\t\t\t; на экран значение регистра AL, в котором лежит [bx].\n\tinc bx\t\t\t\t; inc <регистр> - увеличить на 1.\n\tjmp loop\t\t\t; Возвращаемся обратно к циклу.\n\nnewline:\n\tmov ah, 0x0e\n\tmov al, 0x0a\t; (0x0a) \\n - new line\n\tint 0x10\n\tmov al, 0x0d\t; (0x0d) \\r - carriage return\n\tint 0x10\n\tjmp return\n\nreturn:\t\t\t\t\t; Метка return - завершаем функцию\n\tpopa\t\t\t\t; Возвращаем регистрам их изначальные значения.\n\tret\t\t\t\t\t; Заканчиваем выполнение функции.\n"
  },
  {
    "path": "src/boot/print_string_pm.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / print_string_pm.asm\n; Title:\tФункция вывода строки на экран в 32-битном защищенном режиме\n; ------------------------------------------------------------------------------\n; Description:\n;\tПлюсы 32-битного режима: возможность использовать 32-битные регистры и\n;\tадрессацию памяти, защищенную память виртуальную память\n;\tМинусы: отсутствие БИОС прерываний, требование наличия GDT (об этом позже)\n;\tВ этой программе мы напишем новую функцию печати строки, но без прерываний\n;\tБИОСа, а напрямую манипулируя VGA видеопамятью, вместо вызова int 0x10.\n;\tVGA память размещена начиная с адреса 0xb8000, и у VGA имеется специальный\n;\tтекстовый режим, поэтому нам не придется напрямую рисовать пиксели.\n;\tОсобенности:\n;\t1. Символ представляется в виде 2-х байтов. Первый байт - сам символ,\n;\tвторой байт - 4 бита на цвет текста и еще 4 на цвет фона. \n;\tНапример, чтобы распечатать символ 'A' белым текстом на черном фоне, мы\n;\tиспольузуем 0x410f: 0x41 == 'A', 0 == белый, f == черный.\n;\n;\t\tBit:     | 15 14 13 12 11 10 9 8 | 7 6 5 4 | 3 2 1 0 |\n;\t\tContent: | ASCII                 | FG      | BG      |\n;\n; ------------------------------------------------------------------------------\n\n\n[bits 32]\t\t\t\t\t; Используем 32-битный режим\n\n\t\t\t\t\t\t\t; Определяем некоторые константы\nVIDEO_MEMORY equ 0xb8000\t; = адрес начала памяти VGA\nWHITE_ON_BLACK equ 0x0f\t\t; = цвет символов (0x0f - белый на черном)\n\nprint_string_pm:\n\tpusha\n\tmov edx, VIDEO_MEMORY\t; Перемещаем в EDX адрес начала массива видеопамяти\n\nprint_string_pm_loop:\n\t\t\t\t\t\t\t; Помним, что AX (2б) = AH(1б) и AL(1б)\n\tmov al, [ebx]\t\t\t; Сохраняем символ из EBX в AL\n\tmov ah, WHITE_ON_BLACK\t; Устанавливаем цвет символов в AH\n\t\t\t\t\t\t\t; таким образом AX получается равен символу + цвету\n\n\tcmp al, 0\t\t\t\t; if (AL == 0), т.е. если конец массива, то\n\tje print_string_pm_done\t; заканчиваем выполнение функции\n\t\t\t\t\t\t\t; else:\n\tmov [edx], ax\t\t\t; video_memory[EDX] = AX\n\tadd ebx, 1\t\t\t\t; переходим к следующему символу (+1, просто массив)\n\tadd edx, 2\t\t\t\t; переходим к следующему адресу в VGA (+2 т.к.\n\t\t\t\t\t\t\t; два байта на символ)\n\n\tjmp print_string_pm_loop\n\nprint_string_pm_done:\n\tpopa\n\tret\n"
  },
  {
    "path": "src/boot/switch.asm",
    "content": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 / switch.asm\n; Title:\tПереключаемся в PM (Protected mode, защищенный режим)\n; ------------------------------------------------------------------------------\n; Description:\n;\tЧтобы сделать свитч, нам нужно:\n;\t\t1. Отключить прерывания (процессор просто будет их игнорировать), т.к.\n; \t\tв PM, прерывания обрабатываются совершенно по-другому в отличие от\n;\t\tReal Mode. Даже если процессор и смог бы распределить сигналы прерываний\n;\t\tпо конкретным BIOS обработчикам прерываний, БИОС обработчики будут\n;\t\tобрабатывать 16-битный код, что повлекло бы за собой ошибки.\n;\t\t2. Загрузить GDT дескриптор\n;\t\t3. Изменяем первый бит регистра управления cr0 на \"1\"\n;\t\thttps://en.wikipedia.org/wiki/Control_register#CR0\n;\t\t4. Т.к. процессор использует специальную технику, которая называется\n;\t\tназывается pipelining (гугли: вычислительный конвейер, полезная статья\n;\t\tна хабре: https://habr.com/ru/post/182002/), и поэтому сразу после того,\n;\t\tкак перевести процессор в PM (что мы и сделали в предыдущем пункте), \n;\t\tнам нужно заставить процессор завершить всю работу в конвейере, чтобы\n;\t\tбыть уверенным, что все будущие инструкции будут выполнены корректно.\n;\t\tКонвейер загружает в себя некоторые количество последующих после текущей\n;\t\tинструкций, но конвейеру не очень нравятся инструкции типа call и jmp,\n;\t\tт.к. процессор не знает полностью какие инструкции будут следовать за\n;\t\tними, в особенности если мы вызовем jmp или call \"прыгая\" в другой\n;\t\tсегмент. Поэтому нам нужно сделать \"дальний прыжок\", чтобы завершить\n;\t\tобрабатываемые в конвейере инструкции.\n;\t\tСам прыжок: \tjmp <сегмент>:<адрес смещения>\n; ------------------------------------------------------------------------------\n\n\n[bits 16]\n\nswitch_to_pm:\n\tcli\t\t\t\t\t\t; Отключаем прерывания (cli = clear interrupts)\n\t\n\tlgdt [gdt_descriptor]\t; Загружаем GDT дескриптор (lgdt = load GDT)\n\n\tmov eax, cr0\t\t\t; Чтобы перейти в PM, нужно чтобы первый бит\n\tor eax, 0x1\t\t\t\t; регистра управления cr0 был 1\n\tmov cr0, eax\n\n\tjmp CODE_SEG:init_pm\t; Делаем \"дальний прыжок\" в наш новый 32-битный\n\t\t\t\t\t\t\t; сегмент кода. Это так же заставляет процессор\n\t\t\t\t\t\t\t; завершить обрабатываемые в конвейере инструкции. \n\n[bits 32]\n\ninit_pm:\t\t\t\t\t; в PM, наши старые сегменты бесполезны, поэтому\n\tmov ax, DATA_SEG\t\t; мы делаем так, чтобы регистры всех сегментов\n\tmov ds, ax\t\t\t\t; указывали на сегмент данных, который мы определили\n\tmov ss, ax\t\t\t\t; в GDT (см. ./gdt.asm)\n\tmov es, ax\n\tmov fs, ax\n\tmov gs, ax\n\n\tmov ebp, 0x90000\t\t; Обновляем позицию стека, чтобы он был на самом\n\tmov esp, ebp\t\t\t; верху свободного места\n\n\tcall BEGIN_PM\t\t\t; Вызываем функцию из ./main.asm"
  },
  {
    "path": "src/build/Makefile",
    "content": "C_FILES = $(shell find ../kernel/*.c ../drivers/*.c ../*.c)\ntemp = $(notdir $(C_FILES))\nO_FILES = ${temp:.c=.o}\n\n# флаг для дебага для gcc\nCFLAGS = -g\n\nrun: os-image.bin\n\tqemu-system-i386 -fda os-image.bin\n\tmake clean\n\ndebug: os-image.bin kernel.elf\n\t# kernel.elf нужен для gdb как symbol-file\n\t# флаг -s указывает qemu открыть и прослушивать 1234 порт\n\t# чтобы gdb смог соединиться с ним для дебага\n\t# флаг -S указыает QEMU не запускать образ и подождать подключений\n\tqemu-system-i386 -s -S -fda os-image.bin\n\tmake clean\n\nos-image.bin: bootsect.bin kernel.bin\n\tcat bootsect.bin kernel.bin > os-image.bin\n\nbootsect.bin:\n\tcd ../boot/ && nasm bootsect.asm -f bin -o ../build/bootsect.bin && cd -\n\nkernel.bin: kernel_entry.o kernel.o\n\ti386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o $(O_FILES) --oformat binary\n\nkernel_entry.o:\n\tnasm ../boot/kernel_entry.asm -f elf -o kernel_entry.o\n\nkernel.o:\n\ti386-elf-gcc ${CFLAGS} -ffreestanding -c $(C_FILES)\n\nkernel.elf: kernel_entry.o kernel.o \n\t# как kernel.bin только без --oformat binary\n\ti386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o $(O_FILES) -o kernel.elf\n\nclean:\n\trm *.bin *.o *.elf"
  },
  {
    "path": "src/common.c",
    "content": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / common.c\n*\tTitle:\tВсякие удобные константы, типы и функции\n* ------------------------------------------------------------------------------\n*\tDescription:\n* ----------------------------------------------------------------------------*/\n\n\n#include \"common.h\"\n\nvoid\tmemcpy(u8 *src, u8 *dest, u32 bytes)\n{\n\tu32 i;\n\n\ti = 0;\n\twhile (i < bytes)\n\t{\n\t\tdest[i] = src[i];\n\t\ti++;\n\t}\n}"
  },
  {
    "path": "src/common.h",
    "content": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / common.h\n*\tTitle:\tВсякие удобные константы, типы и функции\n* ------------------------------------------------------------------------------\n*\tDescription:\n* ----------------------------------------------------------------------------*/\n\n\n#ifndef COMMON_H\n#define COMMON_H\n\n// Указанная размерность характерна только для архитектуры x86\n// Подробнее про типы данных: https://metanit.com/cpp/c/2.3.php\n\ntypedef unsigned int\tu32;\t// беззнаковое целое число размером 32 бита\ntypedef \t\t int\ts32; \t// целое число 32 бита со знаком\ntypedef unsigned short\tu16;\t// и т.д.\ntypedef \t\t short\ts16;\ntypedef unsigned char\tu8;\ntypedef \t\t char\ts8;\n\nvoid\tmemcpy(u8 *src, u8 *dest, u32 bytes);\n\n#endif\n"
  },
  {
    "path": "src/drivers/lowlevel_io.c",
    "content": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / drivers / lowlevel_io.c\n*\tTitle:\tНизкоуровневые I/O функции для работы с девайсами\n* ------------------------------------------------------------------------------\n*\tDescription:\n*\t\tОбычно есть два способа взаимодействовать с hardware - memory-mapped I/O\n*\t\tи I/O ports. \n*\t\tЕсли девайс использует memory-mapped I/O, то чтобы передать ему \n*\t\tкакую-либо информацию, нужно записать ее в специальный адрес. Например, \n*\t\tчтобы вывести символ на экран, мы записывали его в адрес 0xb8000.\n*\t\tЕсли девайс использует I/O ports, то для взаимодействия с ним\n*\t\tиспользуются инструкции ассемблера in и out.\n*\n*       Чтобы работать с дейвасом, нам достаточно знать что в нем есть некий\n*       контролирующий чип, который делает всю грязную работу за нас и позволяет\n*       процессору взаимодействовать с напрямую с железом дейваса. У такого чипа\n*       есть регистры в которые можно что-то записывать или что-то из них\n*       читать, и значение этих регистров указывет чипу что делать.\n*\n*       Как мы будем читать/писать в регистры контролирующих чипов девайса? Вот\n*       что нужно знать:\n*       1. В архитектуре процессоров Интел, регистры контроля девайсами\n*       расположены в специальном I/O адресном пространстве.\n*       2. Инструкции ввода-ввывода используют такой синтаксис (intel syntax):\n*       in <записать результат сюда (регистр процессора)>, <прочитать\n*                                       содержимое отсюда (регистр девайса)>\n*       out <записать сюда (регистр девайса)>, <вот это>\n*       3. К сожалению, в языке Си нет подобных конструкций, так что нам\n*       придется использовать inline assembly. Ща посмотрим как.\n* ----------------------------------------------------------------------------*/\n\n\nunsigned char   port_byte_in(unsigned short port)\n{\n    /* Функция-обертка над assembly, читающая 1 байт из параметра port */\n    /* unsigned short port: адрес регистра какого-либо девайса, из которого */\n    /* мы что-то прочтем. */\n\n    /* Используется другой синтаксис ассембли (GAS). Обратите внимание, что */\n    /* выражение \"mov dest, src\" в GAS мы запишем как \"mov src, dest\", т.е. */\n    /* \"in dx, al\" означает прочитать содержимое порта (адрес которого */\n    /* находится в DX) и положить в AL. */\n    /* Символ % означает регистр, а т.к. % - escape symbol, то мы */\n    /* пишем еще один %. */\n    /* Перемещаем результат в регистр AL т.к. размер AL == 1 байт */\n    unsigned char result;\n\t__asm__(\"in %%dx, %%al\" : \"=a\" (result) : \"d\" (port));\n\t/* разберем только что вызванную функцию: */\n\t/* \"in %%dx, %%al\"\t\t- Прочитать содержимое порта и положить это в AL */\n\t/* : \"=a\" (result)\t\t- Положить значение AL в переменную result */\n\t/* : \"d\" (port)\t\t\t- Загрузить port в регистр EDX (extended DX: 32b) */\n    return (result);\t\t/* Возвращаем прочитанное содержимое из port */\n}\n\n\nvoid    port_byte_out(unsigned short port, unsigned char data)\n{\n    /* Функция-обертка над assembly, пишущая data (1 байт) в port */\n    /* unsigned short port: адрес регистра девайса, в который что-то запишем */\n    /* unsigned char data: 1 байт какой-то информации (например, символ) */\n\t__asm__(\"out %%al, %%dx\" : : \"a\" (data), \"d\" (port));\n\t/* разберем только что вызванную функцию: */\n\t/* \"out %%al, %%dx\"\t\t- Записать data в port */\n\t/* : : \"a\" (data)\t\t- Загрузить data в регистр EAX */\n\t/* : \"d\" (port)\t\t\t- Загрузить port в регистр EDX */\n}\n\n\nunsigned char   port_word_in(unsigned short port)\n{\n    /* Функция-обертка над assembly, читающая 2 байта из параметра port */\n    /* Перемещаем результат в регистр AX т.к. размер AX == 2 байта */\n    unsigned short result;\n    __asm__(\"in %%dx, %%ax\" : \"=a\" (result) : \"d\" (port));\n    return (result);\n}\n\n\nvoid port_word_out(unsigned short port, unsigned short data)\n{\n    /* Функция-обертка над assembly, пишущая data (2 байта, т.е. word) в port */\n    __asm__(\"out %%ax, %%dx\" : : \"a\" (data), \"d\" (port));\n}\n"
  },
  {
    "path": "src/drivers/lowlevel_io.h",
    "content": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / drivers / lowlevel_io.h\n*\tTitle:\tЗаголовочный файл для lowlevel_io.c\n* ------------------------------------------------------------------------------\n*\tDescription:\n* ----------------------------------------------------------------------------*/\n\n\nunsigned char   port_byte_in(unsigned short port);\nvoid    port_byte_out(unsigned short port, unsigned char data);\nunsigned char   port_word_in(unsigned short port);\nvoid port_word_out(unsigned short port, unsigned short data);\n"
  },
  {
    "path": "src/drivers/screen.c",
    "content": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / drivers / screen.c\n*\tTitle:\tФункции работы с экраном\n* ------------------------------------------------------------------------------\n*\tDescription:\n* ----------------------------------------------------------------------------*/\n\n\n#include \"screen.h\"\n#include \"lowlevel_io.h\"\n#include \"../common.h\"\n\n\nvoid\tkprint(u8 *str)\n{\n\t/* Функция печати строки */\n\t\n\t// u8 *str: указатель на строку (на первый символ строки). Строка должна\n\t// быть null-terminated.\n\n\twhile (*str)\n\t{\n\t\tputchar(*str, WHITE_ON_BLACK);\n\t\tstr++;\n\t}\n}\n\nvoid\tputchar(u8 character, u8 attribute_byte)\n{\n\t/* Более высокоуровневая функция печати символа */\n\n\t// u8 character: байт, соответствующий символу\n\t// u8 attribute_byte: байт, соответствующий цвету текста/фона символа\n\n\tu16 offset;\n\n\toffset = get_cursor();\n\tif (character == '\\n')\n\t{\n\t\t// Переводим строку.\n\t\tif ((offset / 2 / MAX_COLS) == (MAX_ROWS - 1)) \n\t\t\tscroll_line();\n\t\telse\n\t\t\tset_cursor((offset - offset % (MAX_COLS*2)) + MAX_COLS*2);\n\t}\n\telse \n\t{\n\t\tif (offset == (MAX_COLS * MAX_ROWS * 2)) scroll_line();\n\t\twrite(character, attribute_byte, offset);\n\t\tset_cursor(offset+2);\n\t}\n}\n\nvoid\tscroll_line()\n{\n\t/* Функция скроллинга */\n\n\tu8 i = 1;\t\t// Начинаем со второй строки.\n\tu16 last_line;\t// Начало последней строки.\n\n\twhile (i < MAX_ROWS)\n\t{\n\t\tmemcpy(\n\t\t\t(u8 *)(VIDEO_ADDRESS + (MAX_COLS * i * 2)),\n\t\t\t(u8 *)(VIDEO_ADDRESS + (MAX_COLS * (i-1) * 2)),\n\t\t\t(MAX_COLS*2)\n\t\t);\n\t\ti++;\n\t}\n\n\tlast_line = (MAX_COLS*MAX_ROWS*2) - MAX_COLS*2;\n\ti = 0;\n\twhile (i < MAX_COLS)\n\t{\n\t\twrite('\\0', WHITE_ON_BLACK, (last_line + i * 2));\n\t\ti++;\n\t}\n\tset_cursor(last_line);\n}\n\nvoid\tclear_screen()\n{\n\t/* Функция очистки экрана */\n\n\tu16\toffset = 0;\n\twhile (offset < (MAX_ROWS * MAX_COLS * 2))\n\t{\n\t\twrite('\\0', WHITE_ON_BLACK, offset);\n\t\toffset += 2;\n\t}\n\tset_cursor(0);\n}\n\nvoid\twrite(u8 character, u8 attribute_byte, u16 offset)\n{\n\t/* Функция печати символа на экран с помощью VGA по адресу 0xb8000 */\n\n\t// u8 character: байт, соответствующий символу\n\t// u8 attribute_byte: байт, соответствующий цвету текста/фона символа\n\t// u16 offset: смещение (позиция), по которому нужно распечатать символ\n\t\n\tu8 *vga = (u8 *) VIDEO_ADDRESS;\n\tvga[offset] = character;\n\tvga[offset + 1] = attribute_byte;\n}\n\nu16\t\tget_cursor()\n{\n\t/* Функция, возвращающая позицию курсора (char offset). */\n\n\tport_byte_out(REG_SCREEN_CTRL, 14);\t\t\t\t// Запрашиваем верхний байт\n\tu8 high_byte = port_byte_in(REG_SCREEN_DATA);\t// Принимаем его\n\tport_byte_out(REG_SCREEN_CTRL, 15);\t\t\t\t// Запрашиваем нижний байт\n\tu8 low_byte = port_byte_in(REG_SCREEN_DATA);\t// Принимаем и его\n\t// Возвращаем смещение умножая его на 2, т.к. порты возвращают смещение в\n\t// клетках экрана (cell offset), а нам нужно в символах (char offset), т.к.\n\t// на каждый символ у нас 2 байта\n\treturn (((high_byte << 8) + low_byte) * 2);\n}\n\nvoid\tset_cursor(u16 pos)\n{\n\t/* Функция, устаналивающая курсор по смещнию (позиции) pos */\n\t/* Поиграться с битами можно тут http://bitwisecmd.com/ */\n\n\t// конвертируем в cell offset (в позицию по клеткам, а не символам)\n\tpos /= 2;\n\n\t// Указываем, что будем передавать верхний байт\n\tport_byte_out(REG_SCREEN_CTRL, 14);\n\t// Передаем верхний байт\n\tport_byte_out(REG_SCREEN_DATA, (u8)(pos >> 8));\n\t// Указываем, что будем передавать нижний байт\n\tport_byte_out(REG_SCREEN_CTRL, 15);\n\t// Передаем нижний байт\n\tport_byte_out(REG_SCREEN_DATA, (u8)(pos & 0xff));\n}\n"
  },
  {
    "path": "src/drivers/screen.h",
    "content": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / drivers / screen.h\n*\tTitle:\tЗаголовочный файл для screen.c\n* ------------------------------------------------------------------------------\n*\tDescription:\n* ----------------------------------------------------------------------------*/\n\n\n#include \"../common.h\"\n\n#define VIDEO_ADDRESS 0xb8000\t// Адрес начала VGA для печати символов\n#define MAX_ROWS 25\t\t\t\t// макс. строк\n#define MAX_COLS 80\t\t\t\t// макс. столбцов\n\n#define WHITE_ON_BLACK 0x0f\t\t// 0x0 == white fg, 0xf == black bg\n\n// Адреса I/O портов для взаимодействия с экраном.\n#define REG_SCREEN_CTRL 0x3d4\t// этот порт для описания данных\n#define REG_SCREEN_DATA 0x3d5\t// а этот порт для самих данных\n\nvoid\tkprint(u8 *str);\nvoid\tputchar(u8 character, u8 attribute_byte);\nvoid\tclear_screen();\nvoid\twrite(u8 character, u8 attribute_byte, u16 offset);\nvoid\tscroll_line();\nu16\t\tget_cursor();\nvoid\tset_cursor(u16 pos);\n"
  },
  {
    "path": "src/kernel/kernel.c",
    "content": "#include \"../common.h\"\n#include \"../drivers/screen.h\"\n#include \"./utils.h\"\n\n\ns32\t\tkmain()\n{\t\n\tclear_screen();\n\t\n\tkprint_rick_and_morty();\n\t\n\tkprint(\n\t\t\"- Look Rick, we are in an OS!\\n\"\n\t\t\"- Damn Morty, that's fantastic!\\n\\n\"\n\t);\n\tkprint(\"So... Welcome to my OS!\\n\\n\");\n\tkprint(\n\t\t\"Actually, the functionality of this OS is limited only to displaying that creepy \"\n\t\t\"ASCII art. No commands, no more features. The only purpose of this OS is \"\n\t\t\"educational - you can follow easy steps in guide/ folder and create your own OS, \"\n\t\t\"just like this. The guide is well-documented and all written in Russian.\\n\\n\"\n\t);\n\tkprint(\"Thank you for your attention!\\n\");\n\tkprint(\"Find me on GitHub: \");\n\tkprint_colored(\"https://github.com/thedenisnikulin\", 0xf0);\n\treturn 0;\n}\n"
  },
  {
    "path": "src/kernel/utils.c",
    "content": "#include \"./utils.h\"\n#include \"../drivers/screen.h\"\n#include \"../common.h\"\n\nvoid\tkprint_rick_and_morty()\n{\n\tu8 *char_map[] = {\n\t\t\"__#_#_#\\n\",\n\t\t\"__#####____\", \"#####\\n\",\n\t\t\"__#\", \"###\", \"#____\", \"#\", \"###\", \"#\\n\",\n\t\t\"__#\", \"#\", \"#\", \"#\", \"#____\", \"#\", \"#\", \"#\", \"#\", \"#\\n\",\n\t\t\"__#\", \"#\", \"#\", \"#\", \"#____\", \"#\", \"#\", \"#\", \"#\", \"#\\n\",\n\t\t\"__#####____\", \"#####\\n\",\n\t\t\"___###_____\", \"_###_\\n\",\n\t\t\"__##\", \"#\", \"##____\", \"#####\\n\",\n\t\t\"__##\", \"#\", \"##____\", \"#\", \"###\", \"#\\n\",\n\t\t\"__##\", \"#\", \"##____\", \"#\", \"###\", \"#\\n\",\n\t\t\"__#\", \"#\", \"#\", \"#\", \"#____\", \"#\", \"###\", \"#\\n\",\n\t\t\"___##\", \"#______\", \"###\\n\",\n\t\t\"___#_#______#_#\\n\"\n\t};\n\tu8 color_map[] = { \n\t\t0xbb,\n\t\t0xbb, 0x66,\n\t\t0xbb, 0xff, 0xbb, 0x66, 0xff, 0x66,\n\t\t0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff,\n\t\t0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff,\n\t\t0xff, 0xff,\n\t\t0xff, 0xff,\n\t\t0x77, 0xbb, 0x77, 0xee,\n\t\t0x77, 0xbb, 0x77, 0xff, 0xee, 0xff,\t\n\t\t0x77, 0xbb, 0x77, 0xff, 0xee, 0xff,\n\t\t0xff, 0x77, 0xbb, 0x77, 0xff, 0xff, 0xee, 0xff,\n\t\t0x99, 0x77, 0x99,\n\t\t0x99\n\t};\n\tu8 i = 0;\n\twhile (i < 61)\n\t{\n\t\tkprint_colored(char_map[i], color_map[i]);\n\t\ti++;\n\t}\n}\n\nvoid\tkprint_colored(u8 *str, u8 attr)\n{\n\twhile (*str)\n\t{\n\t\tif (*str == '_')\n\t\t\tputchar(*str, 0x00);\t\n\t\telse\n\t\t\tputchar(*str, attr);\n\t\tstr++;\n\t}\n}\n"
  },
  {
    "path": "src/kernel/utils.h",
    "content": "#include \"../common.h\"\n\n\nvoid\tkprint_rick_and_morty();\nvoid\tkprint_colored(u8 *str, u8 attr);\n"
  }
]