Назад к истокам: рулим компьютером прямо из MBR +94



Разворачивал в очередной раз Linux-образ на USB-drive (почему-то им оказался Manjaro, но это совсем другая история), и в голову пробрались странные мысли: BIOS увидел флешку, а дальше-то что? Ну да, там MBR, скорее всего GRUB и… А раз в MBR затесался чей-то кастомный код, значит и простой человек из Адыгеи может запрограммировать что-нибудь на «большом» компьютере, но вне операционной системы.


А так как делать такие штуки на языках высокого уровня слишком жирно, а ассемблеров мы не знаем, будем шпарить прямо на опкодах для 8086.



Вступление


План:


  1. Вывести #
  2. Вывести Hello, Habrauser!
  3. Выводить вводимые символы (уже можно детей развлекать).

Предупреждения и отказы от ответственности


Чтобы не докучать домашних грохотом флоповода, тренироваться будем на кошках QEMU. Но, полагаю, желающие смогут всё то же самое нарезать с помощью dd на флешку и запустить на любой x86-совместимой железяке. Это раз.


Мы будем крушить MBR, так что если вы где-то её еще используете (зачем?) и захотите нарезать наши результаты на живой накопитель (зачем?) — думайте, прежде чем надавить Enter. Это два.


Автор — не настоящий сварщик, и может нести (и обязательно донесёт!) какую-то ересь. (У автора вообще детство Бейсиком сломано.) Набегите в комментарии и всё исправьте! Это три.


Немного про MBR


Для наших низменных целей нам достаточно знать следующее:


  • Структуру, а из самой структуры нам нужна только Bootstrap Area и финальная сигнатура.
  • Факт того, что бутстрап загрузится по фиксированному (слава богу) адресу 0x7c00 (если вы не счастливый обладатель Compaq).
  • Ну и то, что работать мы будем в реальном режиме процессора, и доступна нам будет вся память (злобный смех, муа-ха-ха). Ну как вся: все те 640KB, которых всем хватит. (Даже не знаю, чем нам это поможет или помешает.)

Опкоды


Для начала, что такое опкоды — для тех, кто не знает.


Давным давно, когда компьютеры были большими, а программисты еще не назывались разработчиками, но уже перестали вырезать окошки в перфокартах, они решили писать программы прямо (sic!) на компьютерах. А так как программисты быстро поняли, что делать это в двоичных кодах не очень сподручно (места всё-таки уходит многовато), переводить двоичные числа в шестнадцатеричные может любой дурак, то и листинги писали прямо хексами.


Если видели у бати, а то и деда какой-нибудь «Радио» за 80-е годы или «Моделист-Конструктор» за начало 90-х, то в конце наверняка находили листинги для соответствующих самопайных компьютеров: «РК» или «Специалиста». Там были и «ХО», и клоны Lode Runner, и драйверы для подключения печатной машинки «Консул».


Большой скриншоты «Пещеры» + запись стрима


И это только первая страница!



Да-да, всё это вбивали ручками, сверяли контрольные суммы, долго матерясь, искали ошибки, и еще больше матерясь — ждали следующего выпуска с errata.


Вообще тема непростая, и мне, не имеющему опыта в низкоуровневом программировании, в некоторых местах пришлось думать и яростно откапывать и внимательно читать документацию.


Какие моменты нужно взять на заметку:


  • Проглядеть ассемблеровские мнемоники, все эти MOV, INT, ADD, DIV — это, наверное, и всё, что нам понадобится. Посмотрите, как они работают, какие аргументы принимают, куда складывают результаты.
  • Осознать, что обозначают типы аргументов, которые в интеловской документации выглядят, как: imm8, r16, r/m32, rel8. У меня, вот, довольно много времени (наверное, с час) ушло, чтобы сообразить, как DIV BL превращается в F6 F3 (DIV принимает r/m8, который может указывать, как на регистр, так и адрес памяти — в зависимости от хитросплетений байтов.) и почему опкод F6 — это не только DIV, но и NEG, и еще пара операций (Это зависит от opcode extension — трех байтов в операнде.)

Тулзятина


Решил я по началу писать прямо в файлик, который потом подсовывать сначала эмулятору, а потом и dd, чтобы затолкать на железку, но понял, что так для нас, зумеров, будет решительно неудобно — без красивого оформления, комментариев, да билд-системы. Посему я собрался с духом и накатал себе чудо-скрипт, а вот и он… Хотел было написать я, но подумал что умные дядьки из POSIX наверняка всё сделали за меня, и таки да почти да!


?  $ echo "48 65 6c 6c 6f 2c 20 48 61 62 72 21" | xxd -r -p 
Hello, Habr!%    

Осталось придумать синтаксис комментариев и стрипать их:


?  $ echo -e "# Comment\n48 65 6c 6c 6f 2c 20  # First line\n48 61 62 72 21        # Last line" | sed 's/#.*$//g' | xxd -r -p
Hello, Habr!%  

(На самом деле такой выхлоп будет и без sed-а, потому что xxd просто пропускает то, что не смог распарсить как hex-dump. Но мы ведь не хотим неприятностей?)


В итоге скриптецкий набросать пришлось, но он оказался не таким большим, каким имел шансы быть.


А вот и я, sh-скрипт
#!/bin/sh

IN="${1:-/dev/stdin}"
OUT="${2:-/dev/stdout}"

> $OUT

while read line
do
    echo "$line" | sed 's/#.*$//' | xxd -r -p >> $OUT
done < $IN

Скрипт в репо


В нём есть одна недоработка: в конце обязательно должен быть LF (aka \n), иначе последняя строка обработана не будет. Не могу сказать, что меня это сильно беспокоит, или я думал над тем, как это починить, но если кто-то знает, как это сделать быстро — буду рад помощи.

А теперь — делай, как я!


?  $ ./build loader.mbr loader.img && stat -f %z loader.img
512

512 — именно тот размер, который нас устроит. А как его получить, мы узнаем дальше.


Наступление


Бойлерплейтим


Для начала сделаем болванку, которая сформируется в bin-файлик размером в 512B, забитый исключительно ноликами. «Это можно было сделать с помощью dd и /dev/zero, болван!» — скажете вы и окажетесь правы. Но вы только посмотрите, как красиво я расставил эти нолики по колонкам разделил на блоки и расставил поcчитанные на калькуляторе (ну ладно, в ipython) адреса!


Ничего интересного, просто нолики с адресами
# 0x0000:0x007F (0-127)
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

# 0x0080:0x00FF (128-255)
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

# 0x0100:0x017F (256-383)
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

# 0x0180:0x0200 (384-512)
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

Болванка в репо


Естественно, наши нули ни к чему хорошему не приведут, и, что QEMU, что живая железка обругают нас благим Exception'ом.



Расчешем деревяшку еще немного, отделив блоки, в которых должны будут описываться разделы (но они нам не пригодятся), и сигнатура MBR.


Немножко интереснее, с заготовкой таблицы разделов
...

# 0x0180:0x0200 (384-445)
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00

# Partition 1     0x01BE:0x01CD (446-461) 
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

# Partition 2     0x01CE:0x01DD (462-477)
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

# Partition 3     0x01DE:0x01ED (478-493)
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

# Partition 4     0x01EE:0x01FD (494-509)
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

# MBR Signature   0x1FE:0x1FF (510-511)
00 00

Файл целиком


Оживим же нашего Буратино, сменив два финальных байта на валидную сигнатуру:


# MBR Signature   0x1FE:0x1FF (510-511)
55 AA

Что получилось


Запрягаем:


$ qemu-system-i386 -nic none loader.img

-nic none отключит сетевые интерфейсы, что избавит эмулятор от пустых надежд загрузиться через PXE, а нас — от лишних ожиданий.


Оно живо, BIOS думает, что эта балалайка его загрузит! Ура, товарищи!



Хватит уже мять мышку, пора печатать!


Для нашей первой проказы проделки не понадобится практически ничего, даже думать. Посмотрите сами:


# 0x0000:0x007F (0-127)
B4 0E                      # Set a console output mode
B0 23                      # Set an octothorp sign
CD 10                      # Call a print function

00 00
00 00 00 00 00 00 00 00

Файл в репо


Всё остальное забито теми же нулями и сигнатуркой (следите за тем, чтобы байтов было 512).


Собираем наше ООП, заталкиваем в QEMU и вуаля!



Я уверен, мои безграмотные комментарии всё прояснили, но, на всякий случай, давайте еще разок:


  • B4 0E — здесь мы отправляем в регистр AH значение 0E (нормальные люди написали бы здесь mov ah, 0e), что укажет одной интересной функции BIOS (о ней ниже), что мы нуждаемся в консольном выводе, то есть просто будет печатать символы на экран.
  • B0 23 — тут всё столь же просто: мы заталкиваем в AL код символа #. Где я его взял? Ну что значит «где»? Я же писал выше — в ASCII-таблице из man ascii!
  • CD 10 — это вообще изян: дергаем BIOS-функцию, отвечающую за вывод всякой ерунды на экран. Она подхватит те аргументы, что мы затолкали в AL и AH, ну и сделает то, что мы от неё хотели: напечатает несчастный октоторп.

Особо инициативные могут поиграться с шрифтами с кодом, отправляемым в AL и добиться вывода:


  • $ (B0 24)
  • % (B0 25)
  • или даже a (B0 A0, но возможно мне просто повезло)


Но все эти одиночные символы, конечно, цветочки. Волчьи ягодки нас ждут впереди.


Давайте же принтанём что-нибудь посерьезнее. Тем более сделать это на опкодах — это вам не printf('Hell of word') наклепать.


Конечно же, мы можем сделать, как полные удоды:


Мне стыдно показывать, спрячу под спойлер
# 0x0000:0x007F (0-127)
B4 0E    # Set a console output mode

B0 0A    # LF
CD 10

B0 48    # H
CD 10    # print 

B0 65    # e
CD 10

B0 6C    # l
CD 10

B0 6C
CD 10

B0 6F    # o
CD 10

B0 2C    # ,
CD 10

B0 20    # SPC
CD 10

B0 48    # H
CD 10

B0 61    # a
CD 10

B0 62    # b
CD 10

B0 72    # r
CD 10

B0 61    # a
CD 10

B0 75    # u
CD 10

B0 73    # s
CD 10

B0 65    # e
CD 10

B0 72    # r
CD 10

B0 21    # !
CD 10

00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

Да, я это закоммитил


И, в принципе, мы добились результата:



Давайте немного очеловечим эту штуку:



Го смотреть, что получилось в итоге:


# 0x0000:0x007F (0-127)
B8 00 06          # Clear screen
CD 10

B4 0E             # Set a console output mode
BE 80 7C          # Place 0x0080 + 0x7c00 = 0x7c80 into SI
AC                # Load a byte at address SI into AL, increment SI
3C 00             # AL == 00?
74 06             # If yes, go to +6 bytes (to zeroes)
CD 10             # Print a char in AL
EB F7             # Go to -7 bytes (to AC opcode)

00 00 00 00 00
00 00 00 00 00 00 00 00

...

# 0x0080:0x00FF (128-255)
48 65 6C 6C 6F 2C 20 48   # Hello, H
61 62 72 61 75 73 65 72   # abrauser
21                        # !

00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

На деле всё просто:


  • Очистку экрана пропускаем
  • Важная штука: нам пришлось вспомнить, что наша программка грузится в адрес 0x7c00, а значит адреса в нашей программке должны плясать именно от этого значения. Как именно плясать? Мы ручками изобразили data-блок (да просто написали текст отдельно от кода), заботливо посчитали байты (не зря я всё делил на блоки и подписывал их!), заплюсовали с начальным адресом и положили получившуюся позицию в регистр SI — самое то для хранения адреса с данными.
  • Дальше об SI будет заботиться опкод AC (ньюфаги знают его по мнемонике LODS). Этот красавчик не просто вытащит данные из адреса, лежащего в SI и толкнёт его в AL, но и заинкрементит сам SI! Вай, молодец!
  • Теперь нужно подумать об окончании строки. Раз у нас всё забито нулями, пусть ноль и будет терминатором строки. Свежо, ново, не так ли?
  • AL, в котором лежит текущий символ будем сравнивать с нулём, и если оно так — просто выйдем за пределы кода (в нашем случае, нужно сместиться на 6 байт вперед), а 00 процессор выполнять не хочет.
  • Если же наш кремниевый друг обнаружит в AL что-то стоящее, то он вызовет уже знакомую нам BIOS-функцию...
  • … и джампнется на 7 байт назад — как раз к LODS!

На самом деле я изрядно подпортил себе нервишки, копаясь в документации к командам процессора и подбирая вручную байтики для переходов. Но пара сеансов у психотерапевта всё поправят, не переживайте.


Это всё уже почти похоже на настоящее программирование, но что-то маловато в нашем софте интерактива. Давайте сделаем примитивнейшую печатную машинку: будем с помощью тех же BIOS-функций печатать вводимые символы. А сохранять… Ну сфотографируете экран на телефон. Или потом напишем с вами не менее примитивную файловую систему — но уже в другой статье (не забывайте, статьи я пишу раз в десять лет — и изменять этому правилу я не намерен).


Я принял коньяк волевое решение и решил, что печатная машинка достойна отдельного файла. И теперь в репозитории есть printer.mbr и typewriter.mbr.

Эх, да простят меня низкоуровневые программисты:


# 0x0000:0x007F (0-127)
B4 07   # Clear screen
B0 00   #
CD 10   #

B4 00   # Set Get keystroke mode
CD 16   # Read a char -> AL

3C 0D   # AL == 0D? (CR, Return pressed)
75 06   # If no, go to +6 bytes

B4 0E   # Print CR
CD 10   #
B0 0A   # Then print NL

B4 0E   # Print a char
CD 10   # 

EB EC   # Go to -20 bytes

Новый блоб 1


Давайте разбираться:


  • Очистку экрана (которая не работает в QEMU) мы уже видели.
  • Дальше мы с помощью AH = 00h скомандуем прерыванию 16h, которое отвечает за работу с кливиатурой, что нам нужно достать символ нажатой кнопки, который функция окунёт в регистр AL.
  • Далее я натнулся на маленькую траблу, связанную с переводами строк: если мы возьмем символ OD (aka CR aka перевод каретки), который получаем от нажатия клавиши Return/Enter, и напечатаем его, то он у нас только каретку и переведёт (у нас же печатная машинка всё-таки), то есть поставит курсор в начало текущей строки.
  • Поэтому обнаружив CR мы напечатаем не только CR, но и символ LF, который провернёт барабан с бумагой на одну строку, сотворив ожидаемое поведение от Enter.
  • Если же у нас в AL вовсе не OD, то мы всё это пропускаем, перепрыгивая через шесть байтов к инструкции печати символа.
  • Мы молодцы: считали-проверили-напечатали символ, можно повторять сначала! Прыгаем на заботливо посчитанные 20 байт назад.

Ух, поразвлекаемся немножко:



Итого, наша штука может:


  • Выводить символы, привязанные к «текстовым» клавишам,
  • Выводить всякую дичь, привязанную к служебным символам,
  • Делать «забой»: по нажатию Backspace курсор перемещается назад, и мы можем на месте старого символа поставить новый.

Но перемещение символов ограничено новой строкой. Что б жизнь emacs-ом не казалась.


Отступление


Маленькие дополнения для тех, кто дочитал до конца.


КДПВ


КДПВ родилась из такого выхлопа, который получился из-за неправильного подсчета байтов для джампа:



Я его немного подрихтовал, добавил красивых цветов. В общем, смотрите сами:


# 0x0000:0x007F (0-127)
B8 12 00          # Set VGA mode 640x480x16
CD 10

B4 0E             # Set a console output mode
B3 00             # Set FG color to black
FE C3             # Color++

BE 80 7C          # Place 0x0080 + 0x7c00 = 0x7c80 into SI
AC                # Load a byte at address SI into AL, increment SI
3C 00             # AL == 00?
74 F6             # If yes, go to -10 bytes (to FE C3)

CD 10             # Print a char in AL
EB F7             # Go to -9 bytes (to AC)

00
00 00 00 00 00 00 00 00

...

# 0x0080:0x00FF (128-255)
48 65 6C 6C 6F 2C 20 48   # Hello, H
61 62 72 61 68 61 62 72   # abrahabr
21 20                     # ! 

00 00 00 00 00 00
00 00 00 00 00 00 00 00

color-printer.mbr


  • Сперва мы переключаем наш вывод (монитор? видеокарту? BIOS?) на цветной режим, а то не будет цветной красоты,
  • Кладём в BL нужный цвет шрифта (чёрный. Да, чёрный.)
  • С помощью инкрементирующего FE инкрементируем BL
  • Ну а следующий фрагмент вы уже видели: печатаем текст, который лежит отдельно, но по завершению не выходим, а возвращаемся к операции инкремента цвета.

Вот и весь меджик.


Ссылки


Определенно, самая полезная глава в моём рассказе.





Постскриптум


Жена подходит, говорит:
— Хватит работать!
— А я и не работаю.
Заглядывает в экран, видит Sublime Text со всем этим безобразием:
— А-а-а, какой ужас! Это зашифрованная порнуха!
Занавес.


В чём-то ведь она права.




UPD: Спасибо eisaev, Andrew_Pinkerton, MrSmith33, Anthony1025 и Юле, которая не желает регистрироваться на Хабре, за правку моих ошибок и опечаток.




Комментарии (70):

  1. drWhy
    /#21431358

    Мсье знает толк…
    Осталось сетевой стек запрограммировать (конечно, тоже в опкодах), и вуаля — идеальный компьютер для написания постов на Хабр, имунный к любым обновлениям ОС.

    PS. Жена определённо права.

    • DmitryAnatolich
      /#21431470

      Думаю, сначала всё-таки драйвер swap на опкодах и, а потом уже и TCP/IP, а то в 640 килобайт даже хромовый тирекс не влезет


      /конец ночному пятничному сарказму

      • drWhy
        /#21432328 / +1

        Думаю, сначала всё-таки драйвер swap на опкодах и, а потом уже и TCP/IP, а то в 640 килобайт даже хромовый тирекс не влезет
        Действительно, стек от МС занимает около 350 КБ, оставляя памяти только на «Hello Habr».
        Но необязательно. Есть расширители DOS, переводящие процессор в защищённый режим, Open Watcom, например, поддерживает парочку (правда, +700К Б к исполнимому файлу). Есть стеки экономичные, в Watcom поддерживается wattcp. smb толстоват, но можно и его впихнуть, зато самый маленький ftp сервер, который удалось найти добавляет всего 27 КБ.
        Для пуристов есть Unreal mode процессора, предоставляющий 4 ГБ ОЗУ. 4 ГБ опкодов, ага. Программа unreal.com, переводящая процессор в этот замечательный режим весит 2,5 КБ ЕМНИП.

        • JerleShannara
          /#21433016 / +2

          При работе с Unreal надо учитывать, что читать/писать в 4Гб можно, а вот выполнять код… Уж проще в flat protected перейти с одним сегментом. Правда прийдется написать кучу драйверов, т.к. биосных прерываний больше не будет.

          • rogoz
            /#21433074 / +1

            Биос и весь первый мегабайт вызывается в V8086 когда надо.

            • JerleShannara
              /#21433140

              Это конечно можно (и Windows 95/98 это даже делали штатно, когда отсутствовали драйвера для некоторых железок(контроллера жесткого диска например)), но это дикая трата процессорного времени.

    • dartraiden
      /#21431504

      Всё уже придумано до нас, у ASUS была Express Gate на десктопных материнках и ноутбуках (в бюджетных моделях ставилась прямо на системный накопитель, а в топовых — на распаянный 16-гиговый SSD). Как раз для того, чтобы фоточки посмотреть, в скайпе пообщаться и интернет посерфить без необходимости грузиться в основную ОС.

      • DmitryAnatolich
        /#21431800

        Да что уж там, на ноуте RoverBook в 2006-м было нечто подобное. Но, насколько помню, там был сверхподрезанный Линукс с какой-то по-дизайнерски изысканной оболочкой

      • Javian
        /#21431888

        Еще встречался встроенный CD плеер.

  2. Boomburum
    /#21431614

    Не спится, зашёл на Хабр — как же приятно увидеть эталонно оформленный пост! Тема поста не моя, но всё равно лайк :)

  3. razielvamp
    /#21431618

    А где же образ докера?

    • DmitryAnatolich
      /#21431846

      Боюсь, Докер и хостовая система не вынесут такого насилия и ссыпятся с какой-нибудь страшной ошибкой типа Segmentation Fault

  4. awoland
    /#21431786

    А чем, в понимании автора, собственно, отличаются опкоды от (языка) «ассемблеров»?

    • DmitryAnatolich
      /#21431812

      Ассемблер требует трансляции в опкоды (т. е. в машинные коды).
      Опкоды мы можем скормить процессору на исполнение.


      Ну и сложностью добывания и написания, конечно же :)

    • drWhy
      /#21432344 / +1

      Ещё опкоды проще вводить наощупь, с помощью тумблеров.

  5. javavirys
    /#21431856 / +1

    Очень порадовала статья, давно не видел подобных статей аж ностальгия пробирает.
    Автору огромный респект!!!

  6. YMA
    /#21431914

    Какой же груз legacy тянут современные x86 компьютеры. Поискал, оказывается, даже современные видео карты умеют CGA/EGA режимы — с разной степенью успеха, народ на зеленоватый оттенок палитры жалуется, но умеют. Если честно, поражен…

    • drWhy
      /#21432390 / +1

      А ещё время реакции системы было на уровне долей микросекунды, а не пятисот, как сейчас.

    • JerleShannara
      /#21433034

      Тянуть тянут, но вот не очень честно (хотя надо будет для интереса глянуть на обработчик invalid opcode в современном UEFI с CSM режимом) — новые инструкции потребовали несколько испортить совместимость со всякой древностью какраз в опкодах и их префиксах.

    • salnicoff
      /#21436612

      А не с них ли начинается инициализация видео? Текстовый режим 80?25 — это ж CGA…

  7. SNPopov
    /#21432300 / +2

    Хотелось бы предостеречь автора поста. То, чем Вы занялись — очень опасная вещь! Страшно заразная и ничем не лечится. Прошло более сорока лет, а я иногда ловлю себя на том, что где-нибудь на совещании или просто, когда есть чем писать и на чем писать машинально вывожу на бумаге что нибудь этакое — 31 FF 01 CD 03 F8 4F CD 09 F8 C3 03 00…
    Берегите себя! Хочется еще что-нибудь почитать вашего авторства.

    • igormu
      /#21432348 / +1

      Я так в юности «мультики» рисовал, записывая в память последовательность нажатий кнопок и перемежая кадры очисткой экрана 1F, чуть более сложной программой. Даже не сохранял ее, быстрее на память ввести было. До сих пор коды помню.

    • drWhy
      /#21432374

      Ваша правда. Но есть средство — CD 19.

    • BobbieZi
      /#21432766

      FA EB FE в качестве лечения :)

      • YMA
        /#21433116

        F0 0F C7 C8 — тоже вспоминается.

    • JerleShannara
      /#21433040

      Тааакс, у вас скорее всего там нет обращений ко всяким eax и прочим 32битным регистрам. А вот что за 0300 после retn?

      • SNPopov
        /#21433248

        Наверное огорчу Вас, но это опкоды I8080… Я же пишу, что прошло более 40 лет.
        LXI SP,01FFH
        CALL F803 (ввод с клавиатуры)
        MOV C,A
        CALL F809 (вывод на экран)
        JMP 0003

        • JerleShannara
          /#21433270

          Чёрт, о КР580ВМ80 я даже не подумал.

          • SNPopov
            /#21433286 / +3

            Еще бы, живые динозавры ведь не каждый день встречаются.

        • igormu
          /#21433828

          i8080-то больше 40 лет, но судя по адресам, это Радио-86РК, то есть не больше 34 лет. Или адреса от чего-то более раннего, с чем он был совместим?

          • SNPopov
            /#21433910 / +2

            Ну как Вам сказать, посмотрите мой профиль и публикации…

            • apkotelnikov
              /#21436944

              Не ожидал пересечься с Вами… мой первый компьютер рк-86 собранный по публикациям в журнале радио. Клавиатура — герконовые кнопки от калькульторов и наборы «штрих-м», корпус из двух доноров для клавиатуры и бессонные ночи в процессе оживления :-) я тогда ещё в школе учился из источников знаний журнал радио, советы отца. Скорее всего именно эта разработка определила род моих занятий по сей день. Спасибо Вам!

    • ZEvS_Poisk
      /#21435258

      31 FF 01 CD 03 F8 4F CD 09 F8 C3 03 00…


      Дайте угадаю:
      LXI SP,01FF
      CALL F803
      MOV C,A
      CALL F809
      JMP ..03

      Судя по обращению к подпрограммам системного монитора — Микроша.

      • SNPopov
        /#21435772 / +2

        Если дизассемблировали по памяти, то приветствую товарища по несчастью… Эти HEX-коды так приживаются в нейронной сети, как будто они ее родные дети. А что касается таблицы системных вызовов, то ее первая инкарнация была еще в Intel MDS-800 (The Intellec Microcomputer Development Systems).

        • ZEvS_Poisk
          /#21438208

          Да, по памяти. Но не «через годы», а просто увлекаюсь ретрокомпьютерами, и, в общем, постоянно поддерживаю «знание».

          … приветствую товарища по несчастью...

          Я Вас тоже приветствую, однако не считаю это несчастьем. Я пишу и под AVR и под STM32, и, бывает, под 8086, и все на ассемблере. При переходе между проектами и платформами некоторое время путаюсь в синтаксисе, но ненадолго.

  8. veselovi4
    /#21432384

    А еще вирусы любили MBR. В стародавние времена, подхватишь такой и никакое форматирование винта не помагало, пока не очистишь MBR))

    • thealfest
      /#21434138

      Даже лет 20 назад во многих биосах была функция защиты MBR от записи.

      • DmitryAnatolich
        /#21435096

        Но, кстати, на моих престарелых ноутбуках такой штуки нет — затирай сколько угодно

  9. 1234rfvb
    /#21432430

    Какая прелесть, этот полуночный бред (см. время публикации)!
    Просто замечательно, что есть люди, стремящиеся понять: а как командовать железом? Вместо всех этих слоёв шелухи.

  10. eurol
    /#21432856

    Как раз на днях возникла идея загрузить BeagleBone своей простой программой прямо с карточки SD, чтобы поморгать лампочкой без всяких там линуксов… Не хватило усидчивости.
    Видел где-то для распберри примеры, а вот для BB не нашел. Может, кто подскажет?

    • JerleShannara
      /#21433044

      Вам надо лезть глубоко и далеко в доку на Texasовский ARM, который там стоит.

  11. pasivash
    /#21433528 / +1

    Спасибо за статью! Ждем еще :)

  12. movl
    /#21434602 / +2

    В универе как-то было несколько лабораторных работ, в которых требовалось написать несложные программы для эмулятора PDP-11, и делать это предлагалось сразу в опкодах. Но для себя в итоге решил что, проще будет написать корявенький компилятор ассемблерного кода под этот эмулятор, чем так страдать, набирая программы в опкодах.

    • DmitryAnatolich
      /#21435086

      Я посчитал, что полезно (после Python) немного пострадать, тем более больно почти не было.


      Но самодельный транслятор — это идея...

  13. Yoooriii
    /#21435024 / +1

    Респект автору, что-то захотелось написать игру в питона, как в старые времена, олдскульненько, без ООП. А вот поморгать светодиодиком похоже не выйдет: ни одного нормального порта на моей железяке не осталось, USB не в счет. Или пойти сдуть пыль с моего HP, там LPT порт имеется.

    • DmitryAnatolich
      /#21435036

      Всегда можно поморгать светодиодом харда и попищать спикером!

  14. salnicoff
    /#21435044 / +1

    Автору — респект. Вспомнил, как писал школьную выпускную программу на ассемблере… Ну и прочие штучки прямо в кодах на уроках…

  15. filippov_andrey
    /#21435122 / +1

    Как перестать залипать в эту штуку?


    r/m16/32 — сперва мозг ломается от такого описания операндов. Но со временем привыкаешь. И почему то легче всего воспринимаются MMX и SSE из всего остального в спецификации по ссылке.

    • DmitryAnatolich
      /#21435156

      r/m16/32 — сперва мозг ломается

      Тут, как в 1С: мозг ломается только первые две недели, потом втягиваешься

  16. apkotelnikov
    /#21435292 / +1

    Можно было бы и не тревожить BIOS понапрасну. Начиная с B800:0000 нечётные код символа, четные атрибут. Если подойти к вопросу с особым цинизмом, можно весь массив трансфернуть через DMA пересылкой MEM2MEM. ;-)

    • apkotelnikov
      /#21435334

      Если уж совсем заморочиться, можно закинуть в x0f регистр RTC x05, по адресу 0040:0067 записать адрес своего обработчика и наслаждаться недоумением пользователя «Почему кнопка reset не работает?» :-) (но в обработчике придется восстановить режим работы видеоадаптера и помнить что жизнь началась заново и регистрах ничего полезного и не забыть в x0f положить x05 и восстановить адрес обработчика в 0040:0067)

      • JerleShannara
        /#21435460

        Кнопка reset увы ничем (кроме ME/AMT/BMC и прочих уже около-серверных механизмов) не блокируется.

        • apkotelnikov
          /#21435986

          Это не блокировка, это «трюк» с POST и особенностями «входа/выхода» в защищённый режим интеллового процессора, начиная с 286. значения x05 и x0A сообщают о том что сброс был по смене режима. Штатно сброс программный, периферия не сбрасывается. По кнопке сбрасывается ещё и периферия, но RAM не очищается, «восстановить» видеоадаптер не самая сложная задачка. Когда-то давно я этот трюк использовал для снятия дампа памяти. :-)

      • DmitryAnatolich
        /#21436026

        Так, я решительно требую ответной статьи со всем вышеописанным :)

        • apkotelnikov
          /#21436348 / +1

          Это ж было 25 лет назад :-) Вчера в бесплодных попытках вспомнить, что куда и зачем смог найти единственный источник «ISA system bus architecture». За прошедшие 25 лет в памяти остались только воспоминания о принципе :-( Хотя в те времена было много подобных извращений, начиная от хранения данных в GAP между секторами на флоппе, затирания кода в памяти для защиты от дебагеров и прочего. Все кануло в Лету.

          • salnicoff
            /#21436608

            Были даже специально прожженные нечитаемые сектора на дискете…

            • apkotelnikov
              /#21436624 / +1

              Флоповод со сдвоенным шпинделем, данные просто аналогом, а дырки — оптопара и игла от швейной машинки + электромагнит… :-) Автору поста огромнейший респект за напоминание давно забытого старого!

              • salnicoff
                /#21436902 / +1

                Не угадали. Это была чья-то фирменная защита от копирования — установщик читал определенный сектор, если дисковод выдавал ошибку — значит, дискета подлинная, если читается нормально — пиратская копия…

                • apkotelnikov
                  /#21436956 / +1

                  Не угадывал, то что выше, это способ, которым я как раз тиражировал дискеточки с такой защитой ;-) «вырезать» защиту программно в моем случае сходу не удалось. А вообще там было чуть более сложнее, чем просто нечитаемый сектор. Там была проверка на каком бите возникала ошибка. Потом от этого ушли и стали считать длину post gap, там уже до полубита была точность. Но аналоговое копирование спасало и от этого. :-)

                • drWhy
                  /#21437354 / +1

                  Один из вариантов защиты — инсталлятор устанавливал свой обработчик на прерывание по ошибке чтения с дискеты, обработчик сверял номер сбойного сектора с записанным в файле, при совпадении установка продолжалась дальше.
                  Автоматизация прожига дискеты необязательна — можно просто прокалывать иголкой дискету по бумажному шаблону — в сектор попасть таким образом не получится, но в несколько дорожек — вполне, тогда проверять номер дорожки с нечитабельным сектором.

                  • apkotelnikov
                    /#21437406 / +2

                    Был такой вариант — его проще обойти своим резидентом в цепочке. Чтение сектора и определение в каком именно бите «дырка» делается прямым доступом к контроллеру флопа, обходить этот вариант существенно сложнее. Гораздо проще поступить как я писал выше — два флоповода бутербродом с механической связью шпинделей, синхронизация нулевой позиции диска вручную ну а копирование информации аналоговым способом( аля двух кассетный магнитофон :-) ) в принципе можно и не заморачиваться с дырками (при аналоговом копировании сбойный сектор копируется as is но некоторые производители ещё и контроль именно дырки делали. Инсталляция, в процессе нашли дырку в нужном месте, убедились что дискета «родная» перезаписали сектор, вычислили новое смещение, проапдейтили код защиты (там тоже «развлекались» как могли от банального XOR до перестановок с использованием ПСЧ). а когда надо из оригинала сделать более одной копии, без автоматизации никак ;-)

                  • salnicoff
                    /#21437892 / +1

                    Тогда для каждой легальной копии нужно писать «карту» битых секторов, что увеличивает время тиражирования. Да и баги потом отлавливать — угадывай, у какого клиента какой битый сектор был…

                    • apkotelnikov
                      /#21437964 / +1

                      Нет же :-) сектор всегда один (2,3, n это не важно) далее простая задача — найти смещение в битах от начала сектора и использовать это число, как условный ключ. Который можно проверять при каждом запуске или использовать для кодирования какой либо части инсталляшки или бинарника. На заводе набили дырок и за два прохода записали софт. На первом проходе нашли смещения дырок, сделали XOR (просто для примера) части кода. На втором записали на дискету. При запуске инсталляции опять нашли смещения и восстановили ранее закодированное. Или (как в большинстве случаев и делали) запустились прям с дискеты и на лету восстановили что надо. Все остальное — попытки усложнить жизнь потенциальному злоумышленнику — проверить действительно дырка или просто «провал» намагниченности (надо записать сектор по новой, но при этом битовое смещение дырки изменится, надо учесть и переписать кодированные сектора с учётом изменений) полученное число использовать как «мегаважную» константу, указывающую на смещение джампа в коде, для обхода ловушки на использование пошаговой отладки как это использовать, было очень много вариантов, а с учётом того что «взять нормальный дебаггер» или «запустить в виртуалке» в те времена было невозможным, такая защита работала и была адекватной. Да IDA в те времена уже была и можно было ей ковырять чужой код с вполне хорошей «производительностью» но все же тупое повторение дискеты было проще и быстрее.

    • filippov_andrey
      /#21439430 / +1

      Начиная с B800:0000 нечётные код символа, четные атрибут.


      bits 16
      org 7C00h

      mov ax, 3
      int 10h

      mov ah, 07h
      mov si, msg
      xor di, di

      push es
      mov bx, 0B800h
      mov es, bx

      print:
      lodsb
      test al, al
      jz exit
      stosw
      jmp print
      exit:
      pop es

      msg: db "Hello habr", 0
      times 510-($-$$) db 0
      dw 0xaa55


      Подозреваю что то такое в итоге получиться должно.
      Добавить цикл, добавить inc ah и получится версия топикстартера, только без насилования биоса

  17. Fuckin_butcher
    /#21436020 / +1

    Автор этой годной статьёй заставил меня выйти из readonly. Я получил удовольствие от статьи, от подачи, огромный респект. Хотя сейчас почему-то принято считать околоассемблерную тему устаревшей, но в этом же, чёрт возьми, есть огромный кайф.