Школа асемблера: розробка операційної системи. Політ «Колібрі». На що здатна ОС, повністю написана на асемблері Ассемблювання та компілювання

Нещодавно я вирішив вивчити асемблер, але марно ганяти рядки коду мені було не цікаво. Я подумав, що в міру вивчення асемблера, я освоюватиму якусь предметну область. Так мій вибір припав на написання завантажувача. Результат моїх знахідок тут, у цьому блозі.

Хочеться одразу сказати, що я люблю теорію у поєднанні з практикою, тож почнемо.

Спочатку я покажу як створити найпростіший MBRщоб ми могли в найкоротші терміни порадіти результату. У міру того, як ми ускладнюватимемо практичні приклади, я даватиму теоретичну інформацію.

Спочатку зробимо завантажувач для USB-флешки!

Увага!!! Перша наша асемблерна програма буде працювати як для флешки так і для інших пристроїв таких як Floppy - диск або Жорсткий диск. Згодом, щоб усі приклади працювали коректно, я наводитиму ряд уточнень щодо роботи коду на різних пристроях.

Писати ми будемо на Fasm, тому що саме він вважається найкращим компілятором для написання завантажувачів яким є MBR.Друга причина вибору Fasm полягає в тому, що він спрощує процес компіляції файлів. Жодних директив командного рядкаі т.п. дурниці, яка може геть-чисто відбити полювання вивчати асемблер і досягати поставлених цілей. Отже, на початковому етапі нам знадобляться дві програми і яка-небудь непотрібнафлешка мінімального розміру. Я нарив у себе 1Gb (швидко форматується, та й не шкода, якщо що). Після роботи нашого завантажувача флешка перестане працювати нормально. моя Windows 7 відмовляється форматувати флешку. Повертати флешку до життя раджу утилітою HP USB Disk Storage Format Tool ( HPUSBFW.EXE) або іншими утилітами для форматування флешок.

Встановлюємо їх та викидаємо відповідні ярлики на робочий стіл або куди вам подобається.

Підготовка закінчена переходимо до дій

Відкриваємо Fasmw.exe та пишемо туди наступне. Ми накидаємо мінімум коду для того щоб побачити результат. Потім ми розберемо те, що тут накалякано. Коротко даю коментарі.

Код FASM: ============= boot.asm ==============

org 7C00h; адреси нашої програми розраховуються з урахуванням цієї директиви

use16; генерується шістнадцятковий код

cli ;забороняємо переривання для зміни адрес у сегментних регістрах

mov ax, 0

mov sp, 7C00h

sti ;дозволяємо переривання (після зміни адрес)

mov ax, 0003h ;установка відео режиму для виведення рядка на екран

int 10h

mov ax, 1301h ;власне виведення рядка функція 13h int 10h (пізніше буде докладніше)

mov bp, stroka ;адреса рядка, що виводиться

mov dx, 0000h ;рядок та колонка в якій виводиться текст

mov cx, 15 ;кількість символів рядка, що виводиться

mov bx, 000eh ;00-номер відео сторінки (краще не чіпати) 0e-атрибути символу(колір,фон)

int 10h

jmp $ ;топтатися на місці (зациклює програму на цій точці)

stroka db "OK, MBR loaded!"

times 510 - ($ - $$) db 0 ;заповнення нулями проміжку між попереднім байтом і пос-

db 0x55, 0xAA; льодовими двома байтами

Скомпілюйте цей код (Ctrl+F9) у fasm'e і збережіть бінарний файл, що вийшов, як boot.bin в якесь зручне місце. Перш ніж записати наш бінарник на флешку трохи теорії.

Коли ви встромили флешку в комп'ютер, для системи BIOS абсолютно не очевидно, що ви хочете завантажитися з флешки тому в налаштуваннях BIOS "а потрібно вибрати пристрій, з якого ви хочете завантажуватися. Отже ми вибрали завантаження з USB (як це зробити вам доведеться розібратися самим" оскільки інтерфейс біосу має різні варіації... можете загуглити налаштування BIOSдля вашої материнської плати. Там нічого складного немає, як правило).

Тепер коли BIOS знає, що ви хочете завантажуватися саме з флешки, він повинен переконатися, що нульовий сектор на флешці є завантажувальним. Для цього BIOS переглядає останні два байти нульового сектораі, якщо вони дорівнюють 0x55 0xAA, то тільки тоді він буде завантажений у оперативну пам'ять. Інакше, BIOS просто пройде повз вашу флешку. Знайшовши ці два чарівні байти, він вантажить нульовий сектор в оперативну пам'ять за адресою 0000:7С00h, а потім забуває про флешку і передає управління на цю адресу. Тепер вся влада над комп'ютером належить вашому завантажувачу і, діючи вже з оперативної пам'яті, може завантажувати з флешки додатковий код. Зараз ми подивимося як цей сектор виглядає в програмі DMDE.

1. Вставте флешку в комп'ютер і переконайтеся, що на ній немає потрібної вам інформації.

2. Відкрийте програму DMDE. Усі подальші дії читайте на малюнках:

Подивившись цей комікс, у вас з'явиться навичка завантаження вашого MBR на флешку. А так виглядає довгоочікуваний результат роботи нашого завантажувача:


До речі якщо говорити про мінімальний код завантажувача, то він може виглядати наприклад так:

Org 7C00h
jmp $
db 508 dup(0)
db 0x55,0xAA

Такий завантажувач, отримавши управління, просто вішає комп'ютер, здійснюючи в циклі одну безглузду команду jmp$. Я називаю її тупцювати на місці.

На YouTube я виклав відео, яке можливо допоможе вам:

Насамкінець кілька коротких фактів про роботу завантажувача:

1. Завантажувач, він же bootloader, він MBR має розмір 512 байт. Історично склалось,
що ця умова повинна виконуватися для підтримки старих носіїв та пристроїв.
2. Завантажувач, завжди розташовується в нульовому секторі флешки, Floppy-дискети, жорсткого диска, з погляду програми DMDE чи інших hex-редакторів, дозволяють працювати з пристроями. Для завантаження бінарника (наш boot.bin) на один із перерахованих пристроїв, нам не потрібно замислюватися про їх внутрішню фізичну структуру. Програма DMDE просто знає, як потрібно читати сектори на цих пристроях і відображає їх в режимі LBA (просто нумерує їх від 0 до останнього сектора). Про LBA можна почитати
3. Завантажувач завжди повинен закінчуватись двома байтами 0x55 0xAA.
4. Завантажувач завжди вантажиться на згадку за адресою 0000:7С00h.
5. З завантажувача розпочинається операційна система.


Оригінал: AsmSchool: Make an operating system
Автор: Mike Saunders
Дата публікації: 15 квітня 2016 р.
Переклад: А. Панін
Дата перекладу: 16 квітня 2016 р.

Частина 4: Маючи навички, отримані під час читання попередніх статей серії, ви можете розпочати розробку своєї власної операційної системи!

Для чого це потрібно?

  • Для розуміння принципів роботи компіляторів.
  • Для розуміння вказівок центрального процесора.
  • Для оптимізації вашого коду щодо продуктивності.

Протягом кількох місяців ми пройшли складний шлях, який розпочався з розробки простих программовою асемблера для Linux і закінчився у минулій статті серії розробкою самодостатнього коду, що виконується на персональному комп'ютері без операційної системи. Ну а зараз ми спробуємо зібрати всю інформацію воєдино і створити справжнісіньку операційну систему. Так, ми підемо шляхом Лінуса Торвальдса, але для початку варто відповісти на такі питання: "Що ж являє собою операційна система? Які з її функцій нам доведеться відтворити?".

У цій статті ми сфокусуємось лише на основних функціях операційної системи: завантаженні та виконанні програм. Складні операційні системи виконують набагато більше функцій, таких, як управління віртуальною пам'яттю та обробка мережевих пакетів, але їх коректної реалізації потрібні роки безперервної роботи, у цій статті ми розглянемо лише основні функції, що у будь-якій операційній системі. Минулого місяця ми розробили невелику програму, яка вміщалася в 512-байтовому секторі флоппі-диска (його першому секторі), а зараз ми трохи доопрацюємо її з метою додавання функції завантаження додаткових даних із диска.

Розробка системного завантажувача

Ми могли б спробувати максимально скоротити обсяг бінарного коду нашої операційної системи з метою його розміщення в першому 512-байтовому секторі флоппі-диска, тому самому, який завантажується засобами BIOS, але в такому разі ми не матимемо можливості реалізувати будь-які цікаві функції. Тому ми будемо використовувати ці 512 байт для розміщення бінарного коду простого системного завантажувача, який завантажуватиме бінарний код ядра ОС в оперативну пам'ять та виконуватиме його. (Після цього ми розробимо саме ядро ​​ОС, яке завантажуватиме бінарний код інших програм з диска і також виконуватиме його, але про це буде сказано трохи пізніше.)

Ви можете завантажити вихідний код розглянутих у статті прикладів за посиланням www.linuxvoice.com/code/lv015/asmschool.zip. А це код нашого системного завантажувача із файлу з ім'ям boot.asm:

BITS 16 jmp short start; Перехід до мітки з пропуском опису диска nop; Додаток перед описом диска %include "bpb.asm" start: mov ax, 07C0h; Адреса завантаження mov ds, ax; Сегмент даних mov ax, 9000h; Підготовка стека mov ss, ax mov sp, 0FFFFh; Стек росте вниз! cld; Установка прапора напрямку mov si, kern_filename call load_file jmp 2000h:0000h; Перехід до завантаженого з файлу бінарного коду ядра ОС kern_filename db "MYKERNELBIN" %include "disk.asm" times 510-($-$$) db 0 ; Доповнення бінарного коду нулями до 510 байт dw 0AA55h; Мітка закінчення бінарного коду системного завантажувача buffer: ; Початок буфера для вмісту диска

У цьому коді першою інструкцією центрального процесора є інструкція jmp , яка розташована після директиви BITS , що повідомляє асемблеру NASM про те, що використовується 16-бітовий режим. Як ви напевно пам'ятаєте з попередньої статті серії, виконання завантажуваного засобами BIOS з 512-байтного диска бінарного коду починається з самого початку, але нам доводиться здійснювати перехід до мітки для пропуску спеціального набору даних. Очевидно, що минулого місяця ми просто записували код на початок диска (за допомогою утиліти dd), а решту простору диска залишали порожнім.

Зараз нам доведеться використовувати флоппі-диск з відповідною файловою системою MS-DOS (FAT12), а для того, щоб коректно працювати з даною файловою системою, потрібно додати набір спеціальних даних поряд з початком сектора. Цей набір називається блоком параметрів BIOS (BIOS Parameter Block - BPB) і містить такі дані, як мітка диска, кількість секторів і так далі. Він не повинен цікавити нас на даному етапі, тому що подібним темам можна присвятити не одну серію статей, тому ми розмістили всі пов'язані з ним інструкції та дані в окремому файлі вихідного коду з ім'ям bpb.asm .

Виходячи з вищесказаного, ця директива з нашого коду вкрай важлива:

%include "bpb.asm"

Це директива NASM, яка дозволяє включити вміст вказаного файлу вихідного коду до поточного файлу вихідного коду в процесі асемблювання. Таким чином, ми зможемо зробити код нашого системного завантажувача максимально коротким і зрозумілим, винісши всі подробиці реалізації блоку параметрів BIOS в окремий файл. Блок параметрів BIOS повинен розташовуватись через три байти після початку сектора, а так як інструкція jmp займає лише два байти, нам доводиться використовувати інструкцію nop (її назва розшифровується як "no operation" - це інструкція, яка не робить нічого, крім витрати циклів центрального процесора ) з метою заповнення байта, що залишився.

Робота зі стеком

Далі нам доведеться використовувати інструкції, аналогічні розглянутим у минулій статті, для підготовки регістрів та стека, а також інструкцію cld (розшифровується як "clear direction"), що дозволяє встановити прапор напряму для певних інструкцій, таких як інструкція lodsb, яка після її виконання буде збільшувати значення регістрі SI , а чи не зменшувати його.

Після цього ми поміщаємо адресу рядка в регістр SI і викликаємо нашу функцію load_file. Але замисліться на хвилину - адже ми ще не розробили цю функцію! Так, це правда, але її реалізацію можна знайти в іншому файлі вихідного коду, що підключається, з ім'ям disk.asm .

Файлова система FAT12, що використовується на флоппі-дисках, які форматуються в MS-DOS, є однією з найпростіших існуючих файлових систем, але для роботи з її вмістом також потрібен чималий обсяг коду. Підпрограма load_file має довжину близько 200 рядків і не буде наведена в цій статті, оскільки ми розглядаємо процес розробки операційної системи, а не драйвера для певної файлової системи, отже, не дуже розумно витрачати таким чином місце на сторінках журналу. Загалом ми підключили файл вихідного коду disk.asm практично перед закінченням поточного файлу вихідного коду і можемо забути про нього. (Якщо вас все-таки зацікавила структура файлової системи FAT12, ви можете ознайомитися з чудовим оглядом за адресою http://tinyurl.com/fat12spec , після чого заглянути у файл вихідного коду disk.asm - код, що міститься в ньому, добре прокоментований .)

У будь-якому випадку, підпрограма load_file завантажує бінарний код з файлу з ім'ям, заданим у регістрі SI , сегмент 2000 зі зсувом 0, після чого ми здійснюємо перехід до його початку для виконання. І це все – ядро ​​операційної системи завантажено і системний завантажувач виконав своє завдання!

Ви напевно помітили, що як ім'я файлу ядра операційної системи в нашому коді використовується MYKERNELBIN замість MYKERNEL.BIN, яке цілком вписується в схему імен 8+3, використовувану на флоппі-дисках в DOS. Насправді, у файловій системі FAT12 використовується внутрішнє уявлення імен файлів, а ми заощаджуємо місце, використовуючи ім'я файлу, яке гарантовано не вимагатиме реалізації в рамках нашої підпрограми load_file механізму пошуку символу точки та перетворення імені файлу у внутрішнє уявлення файлової системи.

Після рядка з директивою підключення файлу вихідного коду disk.asm розташовані два рядки, призначені для доповнення бінарного коду системного завантажувача нулями до 512 байт та включення мітки закінчення його бінарного коду (про це йшлося у цій статті). Нарешті, наприкінці коду розташована мітка " buffer " , яка використовується підпрограмою load_file . Загалом, підпрограмі load_file потрібен вільний простір в оперативній пам'яті для виконання деяких проміжних дій у процесі пошуку файлу на диску, а ми маємо достатньо вільного простору після завантаження системного завантажувача, тому ми розміщуємо буфер саме тут.

Для асемблювання системного завантажувача слід використовувати таку команду:

Nasm -f bin -o boot.bin boot.asm

Тепер нам потрібно створити образ віртуального флоппі-диска у форматі MS-DOS і додати бінарний код нашого системного завантажувача до його перших 512 байт за допомогою наступних команд:

Mkdosfs -C floppy.img 1440 dd conv=notrunc if=boot.bin of=floppy.img

У цьому процес розробки системного завантажувача вважатимуться закінченим! Тепер ми маємо образ завантажувального флоппі-диска, який дозволяє завантажити бінарний код ядра операційної системи з файлу з ім'ям mykernel.bin і виконати його. Далі на нас чекає цікавіша частина роботи - розробка самого ядра операційної системи

Ядро операційної системи

Ми хочемо, щоб наше ядро ​​операційної системи виконувало безліч важливих завдань: виводило вітання, приймало введення від користувача, встановлювало, чи є введення командою, що підтримується, а також виконувало програми з диска після вказівки користувачем їх імен. Це код ядра операційної системи із файлу mykernel.asm:

Mov ax, 2000h mov ds, ax mov es, ax loop: mov si, prompt call lib_print_string mov si, user_input call lib_input_string cmp byte , 0 je loop cmp word , "ls" je list_files mov ax, si mov cx, 3 jc load_fail call 32768 jmp loop load_fail: mov si, load_fail_msg call lib_print_string jmp loop list_files: mov si, file_list call lib_get_file_list call lib_print_string jmp loop prompt db 13, m3 10, "Not found!" ", 0 user_input times 256 db 0 file_list times 1024 db 0 %include "lib.asm"

Перед розглядом коду слід звернути увагу на останній рядок із директивою підключення файлу вихідного коду lib.asm, який також знаходиться в архіві asmschool.zip із нашого веб-сайту. Це бібліотека корисних підпрограм для роботи з екраном, клавіатурою, рядками та дисками, які ви також можете використовувати - у цьому випадку ми підключаємо цей файл вихідного коду в самому кінці основного файлу вихідного коду ядра операційної системи для того, щоб зробити останній максимально компактним та красивим . Зверніться до розділу "Підпрограми бібліотеки lib.asm" для отримання додаткової інформаціїпро всі доступні підпрограми.

У перших трьох рядках коду ядра операційної системи ми здійснюємо заповнення регістрів сегментів даними для вказівки на сегмент 2000, який було здійснено завантаження бінарного коду. Це важливо для гарантованої коректної роботитаких інструкцій, як lodsb , які мають читати дані з поточного сегмента, а чи не з будь-якого іншого. Після цього ми не будемо виконувати жодних додаткових операцій із сегментами; наша операційна система працюватиме з 64 Кб оперативної пам'яті!

Далі в коді розташована мітка, що відповідає початку циклу. Насамперед ми використовуємо одну з підпрограм з бібліотеки lib.asm , а саме lib_print_string для виведення вітання. Байти 13 та 10 перед рядком вітання є символами переходу на новий рядокзавдяки яким вітання буде виводитися не відразу ж після виведення будь-якої програми, а завжди на новому рядку.

Після цього ми використовуємо іншу підпрограму з бібліотеки lib.asm під назвою lib_input_string , яка приймає введені користувачем за допомогою клавіатури символи та зберігає їх у буфері, вказівник на який знаходиться у регістрі SI. У нашому випадку буфер оголошується ближче до кінця коду ядра операційної системи наступним чином:

User_input times 256 db 0

Дане оголошення дозволяє створити буфер завдовжки 256 символів, заповнений нулями - його довжини має бути достатньо для зберігання команд такої простої операційної системи, як наша!

Далі ми виконуємо перевірку введення користувача. Якщо перший байт буфера user_input є нульовим, користувач просто натиснув клавішу Enter, не вводячи якої-небудь команди; не забувайте, що всі рядки закінчуються нульовими символами. Таким чином, у даному випадку ми повинні просто перейти до початку циклу та знову вивести вітання. Однак, якщо користувач вводить якусь команду, нам доведеться спочатку перевірити, чи не ввів він команду ls . До поточного моменту ви могли спостерігати в наших програмах мовою асемблера лише порівняння окремих байт, але не варто забувати про те, що також є можливість порівняння двобайтових значень або машинних слів. У цьому коді ми порівнюємо перше машинне слово з буфера user_input з машинним словом, що відповідає рядку ls і в тому випадку, якщо вони ідентичні, переміщуємося до блоку коду, що знаходиться нижче. В рамках цього блоку коду ми використовуємо іншу підпрограму з бібліотеки lib.asm для отримання розділеного комами списку розміщених на диску файлів (для зберігання якого повинен використовуватися буфер file_list), виводимо цей список на екран і переміщуємося назад в цикл для обробки введення користувача.

Виконання сторонніх програм

Якщо користувач не вводить команду ls , ми припускаємо, що він ввів ім'я програми з диска, тому є сенс спробувати завантажити її. Наша бібліотека lib.asm містить реалізацію корисної підпрограми lib_load_file, яка здійснює розбір таблиць файлової системи FAT12 диска: вона приймає покажчик на початок рядка з ім'ям файлу за допомогою регістра AX, а також значення зсуву для завантаження бінарного коду з файлу програми за допомогою регістра CX. Ми вже використовуємо регістр SI для зберігання покажчика на рядок з введенням користувача, тому ми копіюємо цей покажчик в регістр AX , після чого поміщаємо значення 32768, що використовується як зсув для завантаження бінарного коду з файлу програми, в регістр CX .

Але чому ми використовуємо саме це значення як зсув для завантаження бінарного коду з файлу програми? Ну, це просто один із варіантів карти розподілу пам'яті для нашої операційної системи. Через те, що ми працюємо в одному сегменті розміром 64 Кб, а бінарний код нашого ядра завантажений зі зміщенням 0, нам доводиться використовувати перші 32 Кб пам'яті для даних ядра, а решта 32 Кб - для даних програм, що завантажуються. Таким чином, зсув 32768 є серединою нашого сегмента і дозволяє надати достатній обсяг оперативної пам'яті як ядру операційної системи, так і програмам, що завантажуються.

Після цього підпрограма lib_load_file виконує вкрай важливу операцію: якщо вона не може знайти файл із заданим ім'ям на диску або з якоїсь причини не може рахувати його з диска, вона просто завершує роботу та встановлює спеціальний прапор перенесення (carry flag). Це прапор стану центрального процесора, який встановлюється в процесі виконання деяких математичних операцій і в даний момент не має нас цікавити, але при цьому ми можемо визначати наявність цього прапора для прийняття швидких рішень. Якщо підпрограма lib_load_asm встановлює прапор переносу, ми задіємо інструкцію jc (перехід за наявності прапора переносу - jump if carry) для переходу до блоку коду, в рамках якого здійснюється виведення повідомлення про помилку та повернення на початок циклу обробки введення користувача.

У тому ж випадку, якщо прапор перенесення не встановлений, можна зробити висновок, що підпрограма lib_load_asm успішно завантажила бінарний код із файлу програми в оперативну пам'ять за адресою 32768. , тобто почати виконання зазначеної користувачем програми! А після того, як в цій програмі буде використана інструкція ret (для повернення в код, що викликає), ми повинні будемо просто повернутися в цикл обробки введення користувача. Таким чином, ми створили операційну систему: вона складається з найпростіших механізмів розбору команд і завантаження програм, реалізованих в рамках приблизно 40 рядків асемблерного коду, хоча і з великою допомогою з боку підпрограм з бібліотеки lib.asm.

Для асемблювання коду ядра операційної системи слід використовувати таку команду:

Nasm -f bin -o mykernel.bin mykernel.asm

Після цього нам доведеться якось додати файл mykernel.bin у файл образу флоппі-диска. Якщо ви знайомі з прийомом монтування образів дисків за допомогою loopback-пристроїв, ви можете отримати доступ до вмісту образу диска floppy.img , скориставшись ним, але існує простий спосіб, що полягає у використанні інструментарію GNU Mtools (www.gnu.org/software /mtools). Це набір програм для роботи з флоппі-дисками, на яких використовуються файлові системи MS-DOS/FAT12, доступні з репозиторіїв пакетів програмного забезпеченнявсіх популярних дистрибутивів Linux, тому вам доведеться лише скористатися утилітою apt-get, yum, pacman або будь-якою іншою утилітою, яка використовується для встановлення пакетів програмного забезпечення у вашому дистрибутиві.

Після інсталяції відповідного пакета програмного забезпечення для додавання файлу mykernel.bin у файл образу диска floppy.img вам доведеться виконати таку команду:

Mcopy -i floppy.img mykernel.bin::/

Зверніть увагу на забавні символи в кінці команди: двокрапка, двокрапка та слеш. Тепер ми майже готові до запуску нашої операційної системи, але який у цьому сенс, поки для неї не існує додатків? Давайте виправимо це непорозуміння, розробивши вкрай простий додаток. Так, зараз ви розроблятимете додаток для своєї власної операційної системи - просто уявіть, наскільки підніметься ваш авторитет у рядах гіків. Збережіть наступний код у файлі з ім'ям test.asm:

Org 32768 mov ah, 0Eh mov al, "X" int 10h ret

Даний код просто використовує функцію BIOS для виведення символу "X" на екран, після чого повертає управління коду, що викликав його - в нашому випадку цим кодом є код операційної системи. Рядок org , з якого починається вихідний код програми, є не інструкцією центрального процесора, а директивою асемблера NASM, повідомляє йому про те, що бінарний код буде завантажений в оперативну пам'ять зі зміщенням 32768, отже, необхідно перерахувати всі зміщення з урахуванням даної обставини.

Цей код також потребує асемблювання, а бінарний файл, що вийшов у результаті, - додавання до файлу образу флоппі-диска:

Nasm -f bin -o test.bin test.asm mcopy -i floppy.img test.bin::/

Тепер глибоко зітхніть, приготуйтеся до споглядання неперевершених результатів власної роботи та завантажте образ флоппі-диска за допомогою емулятора ПК, такого як Qemu або VirtualBox. Наприклад, для цієї мети може використовуватися така команда:

Qemu-system-i386-fda floppy.img

Вуаля: системний завантажувач boot.img, який ми інтегрували в перший сектор образу диска, завантажує ядро ​​операційної системи mykernel.bin, яке виводить вітання. Введіть ls для отримання імен двох файлів, розміщених на диску (mykernel.bin і test.bin), після чого введіть ім'я останнього файлу для його виконання та виведення символу X на екран.

Це круто, чи не так? Тепер ви можете почати доопрацьовувати командну оболонкувашої операційної системи, додавати до реалізації нових команд, а також додавати файли додаткових програм на диск. Якщо ви бажаєте запустити цю операційну систему на реальному ПК, вам варто звернутися до розділу "Запуск системного завантажувача на реальній апаратній платформі" з попередньої статті серії - вам знадобляться такі самі команди. У наступному місяці ми зробимо нашу операційну систему більш потужною, дозволивши програмам, що завантажуються, використовувати системні функції і реалізувавши таким чином концепцію поділу коду, спрямовану на скорочення його дублювання. Більшість роботи все ще попереду.

Підпрограми бібліотеки lib.asm

Як говорилося раніше, бібліотека lib.asm надає великий набір корисних підпрограм для використання у рамках ваших ядер. операційних системта окремих програм. Деякі з них використовують інструкції та концепції, які поки не торкалися статей даної серії, інші (такі, як підпрограми для роботи з дисками) тісно пов'язані з особливостями пристрою файлових систем, але якщо ви вважаєте себе компетентним у даних питаннях, ви можете самостійно ознайомитися з їх реалізаціями та розібратися в принципі роботи. При цьому важливіше розібратися з тим, як викликати їх із власного коду:

  • lib_print_string - приймає покажчик на рядок, що завершується нульовим символом, за допомогою регістру SI і виводить цей рядок на екран.
  • lib_input_string - приймає покажчик на буфер за допомогою регістра SI та заповнює цей буфер символами, введеними користувачем за допомогою клавіатури. Після того, як користувач натискає клавішу Enter, рядок у буфері завершується нульовим символом і керування повертається коду програми, що викликає.
  • lib_move_cursor - переміщує курсор на екрані в позицію з координатами, що передаються за допомогою регістрів DH (номер рядка) та DL (номер стовпця).
  • lib_get_cursor_pos - слід викликати цю підпрограму для отримання номерів поточного рядка та стовпця за допомогою регістрів DH та DL відповідно.
  • lib_string_uppercase - приймає покажчик на початок рядка, що завершується нульовим символом, за допомогою регістру AX і переводить символи рядка у верхній регістр.
  • lib_string_length - приймає покажчик на початок рядка, що завершується нульовим символом, за допомогою регістра AX і повертає її довжину за допомогою регістра AX.
  • lib_string_compare - приймає покажчики початку двох завершуються нульовими символами рядків у вигляді регістрів SI і DI і порівнює ці рядки. Встановлює прапор переносу в тому випадку, якщо рядки ідентичні (для використання інструкції переходу залежно від прапора переносу jc) або видаляє цей прапор, якщо рядки різняться (для використання інструкції jnc).
  • lib_get_file_list - приймає покажчик на початок буфера за допомогою регістра SI і поміщає в цей буфер рядок, що завершується нульовим символом, що містить розділений комами список імен файлів з диска.
  • lib_load_file - приймає покажчик початку рядка, що містить ім'я файлу, за допомогою регістра AX і завантажує вміст файлу зі зміщення, переданому за допомогою регістра CX . Повертає кількість скопійованих у пам'ять байт (тобто розмір файлу) за допомогою регістра BX або встановлює прапор переносу, якщо файл із заданим ім'ям не знайдено.

Відразу говорю, не закривайте статтю з думками «Млинець, ще один Попов». У нього всього-то злизана Ubuntu, а у мене все з нуля, включаючи ядро ​​та програми. Отже, продовження під катом.

Група ОС: ось.
Спершу я кину вам один скріншот.

Більше їх немає, а тепер докладніше про те, навіщо її пишу.

Був теплий квітневий вечір, четвер. Я ще з дитинства мріяв написати ОС, як раптом подумав: «Я тепер знаю плюси і асм, чого б не втілити мою мрію?». Загугло сайти з цієї тематики і знайшов статтю з Хабра: "Як почати і не кинути писати ОС". Дякуємо її автору за посилання на OSDev Wiki внизу. Я зайшов туди та почав роботу. Там були в одній статті всі дані щодо мінімальної ОС. Я почав збирати крос-gcc та binutils, а потім все переписав звідти. Бачили б ви мою радість, коли я побачив напис «Hello, kernel World!» Я просто зі стільця підстрибнув і зрозумів – я не здамся. Я написав «консоль» (у лапках, я не мав доступу до клавіатури), але потім вирішив написати віконну систему. У результаті вона запрацювала, але доступу до клавіатури я не мав. А потім я вирішив вигадати назву, спираючись на X Window System. Загуглив Y Window System – вона є. У результаті назвав Z Window System 0.1, що входить до OS365 pre-alpha 0.1. І так, її не бачив ніхто, окрім мене самого. Потім зрозумів, як реалізувати підтримку клавіатури. Скрин найпершої версії, коли ще не було нічого, навіть віконної системи:

У ній навіть не рухався курсор тексту, як ви бачите. Потім я написав парочку простих додатківна основі Z. І ось реліз 1.0.0 alpha. Там було багато речей, навіть меню системи. А файловий менеджерта калькулятор просто не працювали.

Мене прямо тероризував друг, якому важливі одні вроди (Мітрофан, соррі). Казав «Запили VBE-режим 1024*768*32, запили, запили! Ну, запили!». Ну, я вже втомився його вислуховувати і все-таки запилив його. Про реалізацію нижче.

Я зробив усе моїм завантажувачем, а саме GRUB"ом. З його допомогою можна задати графічний режим без ускладнень шляхом додавання кількох магічних рядків у заголовок Multiboot.

Set ALIGN, 1<<0 .set MEMINFO, 1<<1 .set GRAPH, 1<<2 .set FLAGS, ALIGN | MEMINFO | GRAPH .set MAGIC, 0x1BADB002 .set CHECKSUM, -(MAGIC + FLAGS) .align 4 .long MAGIC .long FLAGS .long CHECKSUM .long 0, 0, 0, 0, 0 .long 0 # 0 = set graphics mode .long 1024, 768, 32 # Width, height, depth
А потім зі структури інформації Multiboot я беру адресу фреймбуфера та роздільну здатність екрану та пишу туди пікселі. VESA все зробили дуже заморочено – кольори RGB треба вводити у зворотному порядку (не R G B, а B G R). Я кілька днів не розумів - чому пікселі не виводяться? В результаті я зрозумів, що забув змінити значення 16 колірних констант з 0 ... 15 на їх RGB-еквіваленти. У результаті реліз, заодно запилив градієнтне тло. Потім я зробив консоль, 2 додатки та релізнув 1.2. Ах так, мало не забув - завантажити ОС можна на

Assembler

Асемблер(Від англ. assemble - збирати) - компілятор з мови асемблера до команди машинної мови.
Під кожну архітектуру процесора і під кожну ОС чи сімейство ОС існує свій Асемблер. Існують також так звані «крос-ассемблери», що дозволяють на машинах з однією архітектурою (або в середовищі однієї ОС) асемблювати програми для іншої цільової архітектури або іншої ОС, і отримувати код у форматі, придатному до виконання на цільовій архітектурі або в цільовій середовищі ОС.

Архітектура x86

Ассемблери для DOS

Найбільш відомими асемблерами для операційної системи DOS були Borland Turbo Assembler (TASM) та Microsoft Macro Assembler (MASM). Також свого часу був популярний простий асемблер A86.
Спочатку вони підтримували лише 16-бітові команди (до появи процесора Intel 80386). Пізніші версії TASM і MASM підтримують і 32-бітові команди, а також всі команди, введені в сучасніших процесорах, і системи команд, специфічних для конкретної архітектури (такі як, наприклад, MMX, SSE, 3DNow! і т.д.) .

Microsoft Windows

З появою операційної системи Microsoft Windows з'явилося розширення TASM, що називається TASM32, що дозволило створювати програми для виконання в середовищі Windows. Остання відома версія Tasm – 5.3, що підтримує інструкції MMX, на даний момент включена до Turbo C++ Explorer. Але офіційно розвиток програми повністю зупинено.
Microsoft підтримує свій продукт під назвою Microsoft Macro Assembler. Вона продовжує розвиватися і до сьогодні, останні версії включені до наборів DDK. Але версія програми, спрямовану створення програм для DOS, не розвивається. Крім того, Стівен Хатчесон створив пакет для програмування на MASM під назвою «MASM32».

GNU та GNU/Linux

До складу операційної системи GNU входить компілятор gcc, що включає асемблер gas (GNU Assembler), що використовує AT&T синтаксис, на відміну від більшості інших популярних асемблерів, які використовують Intel-синтаксис.

Ассемблери, що переносяться

Також існує відкритий проект асемблера, версії якого доступні під різні операційні системи, і який дозволяє одержувати об'єктні файли для цих систем. Називається цей асемблер NASM (Netwide Assembler).
YASM – це переписана з нуля версія NASM під ліцензією BSD (з деякими винятками).
FASM (Flat Assembler) — молодий асемблер під модифікованою для заборони переліцензування (включаючи GNU GPL) BSD?ліцензією. Є версії KolibriOS, GNU/Linux, MS-DOS і Microsoft Windows, використовує Intel-синтаксис і підтримує інструкції AMD64.

Архітектури RISC


MCS-51
AVR
На даний момент існують 2 компілятори виробництва Atmel (AVRStudio 3 та AVRStudio4). Друга версія - спроба виправити не дуже вдалу першу. Також асемблер є у складі WinAVR.
ARM
AVR32
MSP430
PowerPC

Асемблювання та компілювання

Процес трансляції програми мовою асемблера в об'єктний код прийнято називати асемблюванням. На відміну від компілювання, асемблювання — більш менш однозначний і оборотний процес. У мові асемблера кожній мнемоніці відповідає одна машинна інструкція, у той час як у мовах програмування високого рівня за кожним виразом може ховатися велика кількість різних інструкцій. У принципі, цей поділ досить умовний, тому іноді трансляцію асемблерних програм також називають компіляцією.

Мова асемблера

Мова асемблера- Тип мови програмування низького рівня, що є форматом запису машинних команд, зручний для сприйняття людиною. Часто для стислості його називають просто асемблером, що не так.

Команди мови асемблера один на один відповідають командам процесора і, фактично, є зручною символьною формою запису (мнемокод) команд та їх аргументів. Також мова асемблера забезпечує базові програмні абстракції: зв'язування частин програми та даних через мітки з символьними іменами (при асемблюванні для кожної мітки вираховується адреса, після чого кожне входження мітки замінюється на цю адресу) та директиви.
Директиви асемблера дозволяють включати в програму блоки даних (описані явно або лічені з файлу); повторити певний фрагмент вказану кількість разів; компілювати фрагмент за умовою; задавати адресу виконання фрагмента, відмінну від адреси розташування в пам'яті [уточнити!]; змінювати значення міток у процесі компіляції; використовувати макровизначення з параметрами та ін.
Кожна модель процесора, в принципі, має свій набір команд та відповідну йому мову (або діалект) асемблера.

Гідності й недоліки

Переваги мови асемблера

Мінімальна кількість надлишкового коду, тобто використання меншої кількості команд та звернень у пам'ять, дозволяє збільшити швидкість та зменшити розмір програми.
Забезпечення повної сумісності та максимального використання можливостей потрібної платформи: використання спеціальних інструкцій та технічних особливостей цієї платформи.
При програмуванні на асемблері стають доступними спеціальні можливості: безпосередній доступ до апаратури, портів вводу-виводу та особливих регістрів процесора, а також можливість написання коду, що самодифікується (тобто метапрограмування, причому без необхідності програмного інтерпретатора).
Останні технології безпеки, що впроваджуються в операційні системи, не дозволяють робити коду, що самомодифікується, тому що виключають одночасну можливість виконання інструкцій і запис в тому самому ділянці пам'яті (технологія W^X в BSD-системах, DEP в Windows).

Недоліки мови асемблера

Великі обсяги коду та велика кількість додаткових дрібних завдань, що призводить до того, що код стає дуже складно читати та розуміти, а отже ускладнюється налагодження та доопрацювання програми, а також труднощі реалізації парадигм програмування та будь-яких інших угод. що призводить до складності спільної розробки.
Найменша кількість доступних бібліотек, їхня мала сумісність між собою.
Непереносимість інші платформи (крім двійково сумісних).

Застосування

Безпосередньо випливає з переваг і недоліків.
Оскільки великі програми на асемблері писати дуже незручно, їх пишуть мовами високого рівня. На асемблері пишуть невеликі фрагменти або модулі, для яких критично важливі:
швидкодія (драйвери);
розмір коду (завантажувальні сектори, програмне забезпечення для мікроконтролерів та процесорів з обмеженими ресурсами, віруси, програмні захисту);
спеціальні можливості: робота безпосередньо з апаратурою чи машинним кодом, тобто завантажувачі операційних систем, драйвери, віруси, системи захисту.

Зв'язування асемблерного коду з іншими мовами

Оскільки на асемблері найчастіше пишуть лише фрагменти програми, їх необхідно пов'язувати з іншими частинами іншими мовами. Це досягається 2 основними способами:
На етапі компіляції— вставка у програму асемблерних фрагментів (англ. inline assembler) спеціальними директивами мови, зокрема написання процедур мовою асемблера. Спосіб зручний для нескладних перетворень даних, але повноцінного асемблерного коду, з даними та підпрограмами, включаючи підпрограми з безліччю входів і виходів, які не підтримуються високорівневими мовами, за допомогою нього зробити не можна.
На етапі компонування, чи роздільна компіляція. Для взаємодії скомпонованих модулів достатньо, щоб сполучні функції підтримували потрібні угоди про виклики (англ. calling conventions) та типи даних. Написані ж окремі модулі можуть бути будь-якими мовами, в тому числі і на асемблері.

Синтаксис

Загальноприйнятого стандарту для синтаксису мов асемблера немає. Проте, існують стандарти, яких дотримуються більшість розробників мов асемблера. Основними такими стандартами є Intel-синтаксис та AT&T-синтаксис.

Інструкції

Загальний формат запису інструкцій однаковий для обох стандартів:

[мітка:] опкод [операнди] [;коментар]

де опкод - безпосередньо мнемоніка інструкції процесору. До неї можуть бути додані префікси (повторення, зміни типу адресації та ін.).
Як операнди можуть виступати константи, назви регістрів, адреси в оперативній пам'яті та ін. Відмінності між стандартами Intel і AT&T стосуються, в основному, порядку перерахування операндів та їх синтаксису при різних методах адресації.
Використовувані мнемоніки зазвичай однакові всім процесорів однієї архітектури чи сімейства архітектур (серед широко відомих - мнемоніки процесорів і контролерів Motorola, ARM, x86). Вони описуються специфікації процесорів. Можливі винятки:
Якщо асемблер використовує кросплатформний AT&T-синтаксис (оригінальні мнемоніки призводять до синтаксису AT&T)
Якщо спочатку існувало два стандарти запису мнемоніки (система команд була успадкована від процесора іншого виробника).
Наприклад, процесор Zilog Z80 успадкував систему команд Intel i8080, розширив її і поміняв мнемоніки (і позначення регістрів) на свій лад. Наприклад, змінив інтелівські mov на ld. Процесори Motorola Fireball успадкували систему команд Z80, дещо її урізавши. Водночас, Motorola офіційно повернулася до мнемоніків Intel. І зараз половина асемблерів для Fireball працює з інтелівськими мнемоніками, а половина з мнемоніками Zilog.

Директиви

Крім інструкцій, програма може містити директиви: команди, які не переводяться безпосередньо в машинні інструкції, а керують компілятором. Набір і синтаксис їх значно відрізняються і залежать не від апаратної платформи, а від компілятора, що використовується (породжуючи діалекти мов в межах одного сімейства архітектур). Як "джентльменський набор" директив можна виділити:
визначення даних (констант та змінних)
управління організацією програми в пам'яті та параметрами вихідного файлу
завдання режиму роботи компілятора
всілякі абстракції (тобто елементи мов високого рівня) - від оформлення процедур та функцій (для спрощення реалізації парадигми процедурного програмування) до умовних конструкцій та циклів (для парадигми структурного програмування)
макроси

Приклад програми

Приклад програми Hello world для MS-DOS для архітертури x86 на діалекті TASM:

.MODEL TINY CODE SEGMENT ASSUME CS:CODE, DS:CODE ORG 100h START: mov ah,9 mov dx,OFFSET Msg int 21h int 20h Msg DB "Hello World",13,10,"$"

Походження та критика терміна «мова асемблера»

Цей тип мов отримав назву від назви транслятора (компілятора) з цих мов — асемблера (англ. assembler — збирач). Назва останнього обумовлена ​​тим, що на перших комп'ютерах не існувало мов вищого рівня, і єдиною альтернативою створення програм за допомогою асемблера було програмування безпосередньо в кодах.
Мова асемблера в російській мові часто називають «ассемблером» (а щось пов'язане з ним – «ассемблера»), що, згідно з англійським перекладом слова, неправильно, але вписується в правила російської мови. Проте, сам асемблер (програму) теж називають просто асемблером, а не компілятором мови асемблера і т. п.
Використання терміна «мова асемблера» також може спричинити помилкову думку про існування єдиної мови низького рівня або хоча б стандарту на такі мови. При іменуванні мови, якою написана конкретна програма, бажано уточнювати, для якої архітектури вона призначена і на якому діалекті мови написана.

Сьогодні в нашій кунсткамері цікавий приклад - операційна система, написана на чистому асемблері. Разом з драйверами, графічною оболонкою, десятками встановлених програм та ігор вона займає менше півтора мегабайта. Знайомся - виключно швидка та переважно російська ОС «Колібрі».

Розвиток "Колібрі" йшов досить швидко аж до 2009 року. Пташка навчилася літати на різному залозі, мінімально вимагаючи перший «Пентіум» та вісім мегабайт оперативної пам'яті. Мінімальні системні вимоги «Колібрі» такі:

  • ЦП: Pentium, AMD 5×86 або Cyrix 5×86 без MMX з частотою 100 МГц;
  • ОЗП: 8 Мбайт;
  • відеокарта: VESA-сумісна з підтримкою режиму VGA (640×480×16).

Сучасна «Колібрі» - це регулярно оновлювані «нічні зборки» останньої офіційної версії, що вийшла наприкінці 2009 року. Ми тестували білд 0.7.7.0+ від 20 серпня 2017 року.

WARNING

У налаштуваннях за замовчуванням у KolibriOS немає доступу до дисків, які видно через BIOS. Добре подумай і зроби бекап, перш ніж змінювати це налаштування.

Зміни у нічних збірках хоч і невеликі, але за роки їх нагромадилося достатньо. Оновлена ​​«Колібрі» може писати на розділи FAT16–32/ext2 – ext4 та підтримує інші популярні файлові системи (NTFS, XFS, ISO-9660) у режимі читання. У ній з'явилася підтримка USB та мережевих карт, було додано стек TCP/IP та звукові кодеки. Загалом, в ній вже можна щось робити, а не просто подивитися разок на надлегку операційну систему з GUI і вразитись швидкістю запуску.



Як і попередні версії, остання "Колібрі" написана на flat assembler (FASM) і займає одну дискету - 1,44 Мбайт. Завдяки цьому її можна повністю розмістити в якійсь спеціалізованій пам'яті. Наприклад, умільці записали KolibriOS прямо у Flash BIOS. Під час роботи вона може повністю розміщуватися в кеші деяких процесорів. Тільки уяви: вся операційна система разом з програмами та драйверами кешована!

INFO

Під час відвідування сайту kolibrios.org браузер може попередити про небезпеку. Причина, зважаючи на все, - це асемблерні програми в дистрибутиві. Наразі VirusTotal визначає сайт як абсолютно безпечний.

"Колібрі" легко завантажується з дискети, вінчестера, флешки, Live CD або у віртуальній машині. Для емуляції достатньо вказати тип ОС "інша", виділити їй одне ядро ​​процесора і трохи оперативки. Диск підключати необов'язково, а за наявності роутера з DHCP «Колібрі» миттєво підключиться до Інтернету та локальної мережі. Відразу під час завантаження ти побачиш відповідне повідомлення.


Одна проблема – протокол HTTPS не підтримується вбудованим у «Колібрі» браузером. Тому сайт подивитися в ній не вдалося, так само як відкрити сторінки Google, Yandex, Wikipedia, Ощадбанку ... власне, ніяка звична адреса. Усі давно перейшли на захищений протокол. Єдиний сайт з олдскульним чистим HTTP, який мені попався, - це «портал Уряду Росії», але й він виглядав у текстовому браузері не найкращим чином.



Налаштування зовнішнього вигляду у «Колібрі» з роками покращуються, але все ще далекі від ідеалу. Список відеорежимів, що підтримуються, відображається на екрані завантаження «Колібрі» при натисканні клавіші з латинською літерою a.



Список доступних варіантів невеликий, і потрібного дозволу в ньому може не виявитися. Якщо ти маєш відеокарту з ДП AMD (ATI), то можна відразу додати кастомні налаштування. Для цього потрібно завантажувачу ATIKMS передати параметр -m x x , наприклад:

/RD/1/DRIVERS/ATIKMS -m1280x800x60 -1

Тут /RD/1/DRIVERS/ATIKMS – це шлях до завантажувача (RD – RAM Disk).

Під час роботи системи вибраний відеорежим можна переглянути командою vmode і (теоретично) перемикати вручну. Якщо «Колібрі» запущена у віртуалці, це вікно залишиться порожнім, а ось при чистому завантаженні драйвери відео Intel можна додати від i915 до Skylake включно.

Дивно, але в KolibriOS вмістилася купа ігор. Серед них є логічні та аркадні, цятки, змійка, танки (ні, не WoT) – цілий «Ігровий центр»! На «Колібрі» портували навіть Doom та Quake.



Ще з важливого знайшлася читалка FB2READ. Вона коректно працює з кирилицею та має налаштування відображення тексту.



Всі файли користувача рекомендую зберігати на флешці, але підключати її потрібно обов'язково через порт USB 2.0. Наша флешка USB 3.0 (у порті USB 2.0) об'ємом 16 Гбайт із файловою системою NTFS визначилася відразу. Якщо потрібно записувати файли, варто підключити флешку з розділом FAT32.



У дистрибутив «Колібрі» входить три файлові менеджери, утиліти для перегляду зображень і документів, аудіо- та відеоплеєри та інші користувацькі додатки. Однак основна увага приділена розробці на асемблері.



Вбудований текстовий редактор має підсвічування ASM-синтаксису і навіть дозволяє відразу запускати набрані програми.



Серед засобів розробки є компілятор Oberon-07/11 для i386 Windows, Linux та KolibriOS, а також низькорівневі емулятори: E80 – емулятор ZX Spectrum, FCE Ultra – один з найкращих емуляторів NES, DOSBox v.0.74 та інші. Всі вони були спеціально портовані на «Колібрі».

Якщо залишити KolibriOS на кілька хвилин, то запуститься скрінсейвер. На екрані побіжать рядки коду, в яких можна побачити посилання на MenuetOS.

Продовження доступне лише учасникам

Варіант 1. Приєднайтесь до спільноти «сайт», щоб читати всі матеріали на сайті

Членство у спільноті протягом зазначеного терміну відкриє тобі доступ до ВСІХ матеріалів «Хакера», збільшить особисту накопичувальну знижку та дозволить накопичувати професійний рейтинг Xakep Score!


Top