Я хотел убрать все слои абстракции между собой и компьютером. Оставить своё намерение, ИИ и байты, которые исполняет процессор.

В процессе появились программы на RISC-V и ARM64, флешка с собственным UEFI-приложением, физический Dell, цветные объекты на экране и кот, которому можно передать новый мир по Bluetooth. Исчезли далеко не все слои. Зато стало понятнее, что именно они делают и сколько работы за нас обычно выполняют.

Проект я назвал Rabbit Stack. Сейчас его засосала моя основная работа: до Нового года я, скорее всего, не смогу им заниматься. Поэтому хочу показать, до чего мы с ИИ дошли, где остались открытые вопросы, и попробовать найти людей, которым будет интересно продолжить вместе.

Домашний стенд: Dell, экран, клавиатура и Mac, с которого шла работа.
Домашний стенд: Dell, экран, клавиатура и Mac, с которого шла работа.

1. Насколько близко к железу можно подобраться с ИИ?

Хотелось разобраться, как моё описание желаемого превращается в действие машины. Между ними обычно оказываются язык программирования, библиотеки, компилятор, операционная система, драйверы. Что произойдёт, если начать убирать эти слои? Сможет ли ИИ помочь пройти этот путь человеку, который ещё разбирается в устройстве компьютера?

Один из первых опытов получился буквально с отсылкой к «Матрице». ARM64-программа предлагала выбрать красную или синюю таблетку. На r или R отвечала Wake up, Neo., на b — The story ends.. Для других байтов и конца ввода был отдельный ответ и код завершения.

Мы проверили все 256 однобайтовых значений и конец ввода. Программа была маленькой, её поведение можно было описать полностью и затем проверить.

При этом она всё ещё работала как процесс macOS. В ней оставались вызовы getchar и puts, сборка использовала ассемблер и линкер. Мы убрали C из исходника, но до «только ИИ и железо» было очень далеко.

Следующий опыт решили сделать так, чтобы машинные инструкции можно было увидеть целиком.

2. Восемь инструкций, 32 байта и буква A

Для этого выбрали RV32I — базовый 32-битный набор команд RISC-V — и эмулятор QEMU. На стороне исполняемой программы не было ОС, прошивки и стандартной библиотеки. Был файл из 32 байтов.

Восемь инструкций делали несколько вещей: помещали адрес UART в регистр, записывали туда ASCII-код буквы A, а затем сообщали тестовому устройству QEMU об успешном завершении.

Задача была достаточно маленькой для точного ожидания:

Размер программы: 32 байта
Вывод: ровно A, без перевода строки
Код завершения: 0

Здесь важно различать машину, на которой мы работали, и машину, которую моделировали. На Mac оставались macOS и QEMU. Внутри QEMU исполнялись наши RISC-V-инструкции без гостевой ОС. Процессор, память и устройства для этого опыта предоставлял эмулятор.

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

A: 13 03 10 04
B: 13 03 20 04

Менялся один байт. Можно было заранее предсказать изменение, посмотреть на файл и затем проверить поведение.

Для меня в этом и состоял смысл маленького опыта. Результат помещался в голове: я мог спросить, за что отвечает конкретное место в этих 32 байтах, поменять его и увидеть следствие.

Схема по исходникам: изменение A на B меняет один байт инструкции в 32-байтном образе
Схема по исходникам: изменение A на B меняет один байт инструкции в 32-байтном образе

3. ИИ объясняет. Но кто проверяет результат?

Чем ниже мы спускались, тем важнее становился способ проверки. Убедительное объяснение инструкции ещё не означает, что программа делает ожидаемое.

Рабочий порядок складывался вокруг небольших опытов. Сначала — что должно произойти и что будет считаться ошибкой. Затем — предложение ИИ, разбор, сборка и наблюдение. Для простого вывода можно проверить размер файла, точные байты ответа и код завершения. Для изменения — сравнить артефакты и убедиться, что убранный патч возвращает исходный результат.

Проверять нужно было и неправильные входы. Что произойдёт, если запросить неизвестный модуль, превысить допустимый размер или применить изменение к другой версии программы? Система должна отказаться, а не пытаться догадаться о намерении.

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

Но это не освобождало от проверки самих проверок. Если ИИ написал и преобразователь, и тест, они могут разделять одну ошибку. Нужны были сравнения с другими инструментами, точные ожидаемые результаты и реальные запуски. Позже физический компьютер очень наглядно показал, почему одного эмулятора недостаточно.

4. Поведение отдельно, компьютер отдельно

Когда первый результат заработал, возник другой вопрос: как описать желаемое действие так, чтобы в нём не были зашиты адреса устройств конкретной машины?

В Rabbit появилось разделение. В одном описании хранилось поведение: например, вывести символ и завершиться. В другом — сведения о целевой машине: архитектура, способ вывода, адреса устройств, формат результата и способ запуска. Этот второй набор мы назвали Target Pack.

Схематично путь выглядел так:

Схема: описание поведения и Target Pack проходят проверку, затем создаётся артефакт для целевой машины.
Схема: описание поведения и Target Pack проходят проверку, затем создаётся артефакт для целевой машины.

Одно и то же описание вывода A удалось выполнить двумя способами: как RISC-V-программу в QEMU и как ARM64-процесс на Mac. Машинные артефакты получались разными, ожидаемый результат оставался общим. У ARM64-варианта сохранялись macOS и обычная инструментальная цепочка сборки.

Затем появился небольшой граф модулей. Базовая версия выводила HI. Отдельный патч добавлял модуль с восклицательным знаком и менял связи так, чтобы получилось HI!. Для RISC-V это были соответственно 40 и 48 байтов.

Патч привязывался к точной версии базы. Если база другая, изменение отклонялось. Удаление патча возвращало исходный граф и исходные артефакты.

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

5. Эмулятор закончился. Начался Dell

Первой физической машиной стал Dell OptiPlex 3060: Core i5-8500T, 8 ГБ оперативной памяти, UEFI. Для опыта он работал без установленной ОС, с загрузкой с USB.

На флешке лежало собственное приложение в стандартном месте EFI/BOOT/BOOTX64.EFI. Прошивка загружала его, после чего процессор исполнял нашу программу. Первый результат был скромным: HI на экране и ожидание клавиши.

UEFI при этом оставалась важным слоем. Она обеспечивала загрузку и, в текстовом опыте, вывод и ввод. Утверждать, что мы убрали все абстракции, было бы неверно.

Даже подготовка флешки дала полезный сбой ожиданий. После записи хеш первых 64 МиБ носителя не совпал с хешем исходного образа. Оказалось, macOS автоматически смонтировала раздел и добавила служебный каталог .fseventsd. Сам BOOTX64.EFI остался побайтно тем же.

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

Меню UEFI на Dell: загрузочная Kingston уже видна, Secure Boot ещё включён. Кадр сделан до описанного изменения настройки.
Меню UEFI на Dell: загрузочная Kingston уже видна, Secure Boot ещё включён. Кадр сделан до описанного изменения настройки.

Для запуска неподписанного приложения на этой домашней лабораторной машине я отключил Secure Boot. Это было отдельное решение для конкретного стенда; ключи не удалялись, Legacy-режим не включался.

На Dell мы прошли полный маленький цикл: загрузили HI, применили изменение и увидели HI!, затем восстановили исходную версию и снова увидели HI. Откат был проверен на самом компьютере.

Позже один из следующих опытов прекрасно работал в QEMU, но на Dell давал чёрный экран. В коде вспомогательная функция вызывала прошивку без нужного резервирования стека и выравнивания по соглашению Microsoft x64. Эмулятор этот дефект терпел. На реальной машине программа не доходила до видимых диагностических этапов.

После исправления вызова Dell показал все три этапа проверки. Это был хороший повод пересмотреть, что именно доказывает успешный запуск в QEMU.

6. От первого цветного объекта до кота

После текста захотелось увидеть на экране что-то, чем можно управлять.

Сначала появился цветной квадрат: программа получала через UEFI сведения о графическом буфере и записывала пиксели в память. Следом — движение с клавиатуры, стирание старого положения и ограничения по краям экрана.

В отдельном опыте я описал поведение обычными словами: жёлтый игрок двигается стрелками, по пробелу подпрыгивает, по Z оставляет камень. Через камень пройти нельзя, но его можно обойти. Этот сценарий был проверен в QEMU; физический запуск именно этой версии тогда пропустили.

Дальше росли описания сцен, спрайты и анимации. Появились компоненты, которые можно соединять и повторно использовать: движение, столкновения, смена кадров. В итоге дошли до шагающего рыжего кота.

На экране — синий треугольник; загрузчик сообщает PROGRAM APPLIED. На этом этапе программа задаёт форму, положение и управление стрелками. Это более поздний беспроводной загрузчик, а не первый framebuffer-опыт.
На экране — синий треугольник; загрузчик сообщает PROGRAM APPLIED. На этом этапе программа задаёт форму, положение и управление стрелками. Это более поздний беспроводной загрузчик, а не первый framebuffer-опыт.
Следующий визуальный этап — рыжий кот. Это кадр из записи; движение показывается только на видео.
Следующий визуальный этап — рыжий кот. Это кадр из записи; движение показывается только на видео.
Кот в сцене с домами. Кадр из записи монитора.
Кот в сцене с домами. Кадр из записи монитора.

Важным следующим шагом стало обновление сцены без очередного переноса флешки между компьютерами. Мы пошли через Bluetooth: на Mac — отправитель, на Dell — свой код работы с контроллером и приёма данных под UEFI.

Это потребовало отдельной серии опытов: найти устройство, прочитать его сведения, разобраться с инициализацией и только затем переходить к передаче данных. Проект перестал помещаться в восемь инструкций.

В последних записях уже есть передача кота на Dell и опыт с намеренным прерыванием передачи. Новый процесс на Mac продолжил ту же сессию с подтверждённого смещения, а принимающая сторона вернула результат с хешем пакета. Движение кота я наблюдал на физическом экране.

Такое подтверждение позволяет связать передачу с конкретным пакетом. При этом оно не является криптографической аттестацией самого устройства. В проекте эти границы приходилось обозначать отдельно, иначе слово «проверено» начинало означать слишком многое.

7. Где я остановился

В какой-то момент меня засосала работа. До Нового года я, скорее всего, не смогу выделить Rabbit достаточно времени. Это и стало причиной паузы.

К этому моменту накопились исходники опытов, сборщики, проверки, графические компоненты, инструкции, записи результатов и открытых проблем. Есть физический стенд и несколько направлений продолжения.

Например, передача нового мира и замена самого движка — разные задачи. Совместимые обновления движка с сохранением состояния сцены проверялись в эмуляторе, но физическая проверка этого пути в последних записях ещё оставалась открытой.

Есть и отдельная ветка нативной сети для QCA9377. В ней много реализации и локальных проверок, однако полный путь на физическом Wi-Fi-устройстве не завершён. Готовый удалённый стенд «подключился из интернета и работаешь с Rabbit» я пока предложить не могу.

Можно также вернуться к переносимости: взять небольшой работающий мир и воспроизвести его на другой физической машине. Это помогло бы увидеть, какие части действительно общие, а какие пока слишком зависят от одного Dell.

Мне хочется сохранить именно такой способ двигаться: небольшой вопрос, наблюдаемый результат, возможность объяснить, что произошло. При работе с ИИ было легко получить следующий большой план. Значительно полезнее оказывалось довести маленький опыт до состояния, в котором можно показать и успех, и отказ, и восстановление.

8. Попробуем продолжить вместе?

Можно продолжить беспроводное управление, покопаться в UEFI, перенести Rabbit на другое железо или предложить свой эксперимент. Для первого шага достаточно выбрать небольшой опыт, воспроизвести его и понять, что хочется изменить дальше. Моя доступность до Нового года ограничена, поэтому особенно интересно, смогут ли участники двигаться самостоятельно, используя уже собранный контекст.

У нас есть GPU — Gifted People United, Telegram-группа разработчиков и вайбкодеров. Мне хочется попробовать собирать там людей с разными знаниями вокруг общих проектов — как в корпорации, только без корпорации. Rabbit — первая такая попытка. Готов поделиться всеми наработками и предоставить своё железо для совместных экспериментов.

Это код, сборщики, проверки, графические компоненты, инструкции и история экспериментов. Из железа — тот самый Dell; о способе доступа договоримся с участниками.

Если интересно — присоединяйся к обсуждению. Или напиши здесь «Интересно» — я напишу тебе в личку. «Не интересно» тоже можно — тогда не напишу :)

Комментарии (0)