Немного Zig в 3D-принтере на Linux
Перевод статьи Putting Some Zig In A Linux-Based 3D Printer — Al Williams, Hackaday.

AD5X работает на Linux… вроде как.
То, что Linux сегодня стоит на огромном количестве устройств, — это одновременно и благословение, и проклятие. С одной стороны, здорово, что их можно ковырять, дорабатывать и даже полностью перепрофилировать. С другой — это означает, что у тебя на руках целый парк Linux-устройств, за которыми нужно следить.
Мой нынешний «основной» 3D-принтер — Flashforge AD5X: приятная и недорогая машина, которая печатает в четыре цвета методом продувки/смены филамента. И на ней вроде как работает Klipper. Я говорю «вроде как», потому что Flashforge запускает Klipper на Linux-хосте внутри устройства, но он сильно урезан и переделан. Уверен, для большинства людей этого достаточно. Уверен и в том, что если ты вообще ничего не знаешь про Linux, Klipper или 3D-печать, то опыт скорее всего даже лучше — благодаря облачным интерфейсам в стиле «тыкни мышкой». Но, разумеется, ни один из этих пунктов ко мне не относится.
Принтер у меня уже, наверное, год с лишним. Почти сразу я поставил на него «мод», который дал более настоящий интерфейс Klipper и такие вещи, как доступ к шеллу. Есть несколько вариантов, которые это умеют, но я использовал Zmod — он не заменяет прошивку принтера целиком, а как бы патчит и расширяет её. Его легко обойти или даже удалить и вернуться к стоковому принтеру, хотя мне бы этого не хотелось.
В моём случае речь про принтер, но та же идея применима к любой встраиваемой Linux-системе — от роутера до термостата. Да, оно работает на Linux, но это тот Linux, который ты можешь менять?
В чём проблема#
Прошивка Flashforge и Zmod работают также на младшей сестре AD5X — модели AD5M. Однако у AD5M заметно менее мощная плата, чем у AD5X. Это значит, что Linux на этих коробочках очень сильно урезан. С точки зрения Flashforge в Linux-ОС всё равно никто лезть не должен, а автор Zmod, видимо, считает, что каждый занятый байт — это байт, отобранный у пользователя или у других продвинутых возможностей Zmod.
Может показаться, что это проблема из разряда «зажрались», но меня в Linux этого принтера раздражали две вещи. Не было команд less или more, чтобы ковыряться в файлах. И единственным редактором был vi. Я сделал пару хаков, чтобы себя утешить. Например, написал пейджер на shell-скрипте. Старался вспоминать про свой десктопный emacs и редактировать файлы на коробке через tramp. Но всё же было обидно, что не хватает совсем базовых инструментов. Кроме того, даже у имеющихся утилит вроде ls не было справки — на случай, если понадобится какая-нибудь редкая опция, которую не можешь вспомнить.
Ради экономии места на принтере нет отдельных программ вроде ls, cat и grep. Вместо этого есть единственный исполняемый файл busybox. Для маленьких систем это обычное дело. У тебя одна копия библиотек и один исполняемый файл, который делает всю нужную работу. Можно вызвать, например, grep, запустив busybox grep, или — если сделать симлинк на busybox с именем grep — пользователь может вообще не догадаться, что настоящего grep у него нет.
Однако busybox нужно собирать. Просто так поставить в него пакеты нельзя. Так что я не мог взять и запустить какой-нибудь пакетный менеджер, чтобы установить less или что-то ещё. Мой план был сам собрать новый пакет busybox, чтобы добавить хотя бы недостающие команды, а может и какие-то из базовых. Насколько это может быть сложно?
Разбираемся, что там внутри#
Первой задачей было выяснить, что именно крутится на принтере. ОС оказалась собранной через buildroot, как показал /etc/os-release:
NAME=Buildroot
VERSION=2020.02.1-g0b1f992-dirty
ID=buildroot
VERSION_ID=2020.02.1
PRETTY_NAME="Buildroot 2020.02.1"
Теоретически можно было бы попробовать пересобрать всё из этой информации, но это казалось плохой идеей. Кто знает, какие изменения они внесли и какие странные зависимости там водятся?
Следующим кусочком пазла была архитектура. Ею оказался MIPS, у которого много вариаций — проблема, которая ещё вернётся, чтобы меня помучить. Чтобы во всём разобраться, я вытащил busybox с машины и скопировал на свой обычный компьютер. Это позволило прочитать ELF-файл:
# readelf -h busybox
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 03 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 3
Type: EXEC (Executable file)
Machine: MIPS R3000
Version: 0x1
Entry point address: 0x403fa0
Start of program headers: 52 (bytes into file)
Start of section headers: 775880 (bytes into file)
Flags: 0x70001405, noreorder, cpic, nan2008, o32, mips32r2
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 10
Size of section headers: 40 (bytes)
Number of section headers: 30
Section header string table index: 29
Одного знания, что процессор — MIPS, было явно мало. Процессор мог быть big-endian, но этот оказался little-endian. Более того, флаги показывали несколько вещей, и важными в итоге оказались mips32r2 (определяет конкретный набор инструкций) и nan2008 (означает, что используется более современный стек операций с плавающей точкой по сравнению с некоторыми старыми MIPS-конфигурациями).
Выбор тулчейна#
Следующий шаг — найти тулчейн, который работает на моей Linux-машине и умеет производить исполняемые файлы под такую архитектуру. Было два основных кандидата: zig и crosstool-ng.
Возможно, вы помните, что Zig — это совершенно другой язык, не C. Так что может показаться странным, что я хочу компилировать им busybox — программу на C. Однако у Zig есть интересная особенность. Он может притворяться gcc, а может и компиляторами для некоторых других языков. Идея в том, что если у тебя много C-кода, ты можешь начать переводить его на Zig, не переписывая всё сегодня — да и вообще когда-либо. Используя Clang и LLVM, Zig претендует на то, чтобы просто встать в проект как системный компилятор C.
Почему это важно для данного проекта? Zig ещё и поддерживает множество целевых платформ, включая MIPS, и предоставляет для них рантайм-библиотеки. Так что если я просто скажу системе сборки использовать Zig, всё будет хорошо, верно?
Естественно, всё сработало не так гладко, как я ожидал. Может, я просто недостаточно хорошо понимаю Zig, но передача --target mipsel-linux-musleabihf -mcpu=mips32r2 в zig cc не смогла собрать рабочий «hello world». Причина? Похоже, Zig не поставляет библиотеку с nan2008 для MIPS. Или, может, я просто не знал, как её включить. Насколько я могу судить, Zig, вероятно, сделал бы всё правильно для mips32r6, но это, что неудивительно, привело к ошибкам «illegal instruction». К тому же я хотел держаться как можно ближе к существующей конфигурации.
Я мог бы заставить Zig пересобрать свои библиотеки, но тогда с тем же успехом можно собрать полноценный тулчейн. Впрочем, я задумался: а сильно ли вообще важна плавающая точка для того, что я делаю? И решил схитрить. Я дал Zig собрать исполняемый файл, а затем просто пропатчил заголовок в выводе, чтобы там значилось, что используется nan2008. Для простой программы это сработало.
Сборка busybox#
Конфигурирование busybox — это система в стиле menuconfig, похожая на сборку ядра. В основном я попросил сделать статическую копию в настройках, а затем просто натыкал кучу нужных вещей, особенно less и more.

Меню конфигурации busybox позволяет настроить его под свой вкус или, по крайней мере, под свои нужды.
У многих программ, включая less, есть опции, помогающие уменьшить размер. Например, можно отключить регулярные выражения или запретить чтение переменной $LESS для опций. Я оставил включённым почти всё для большинства программ.
Когда всё было готово, оставалось запустить make с компиляторами Zig, а затем сделать патч, чтобы обмануть принтер и заставить его запустить результат.
Лучше всего сработала такая командная строка:
make V=1 CC="zig cc -target mipsel-linux-musleabihf -mcpu=mips32r2 -static -Os" STRIP='llvm-strip' -j6
Разумеется, -j6 можно подстроить под то, сколько у тебя ядер. Я люблю задействовать примерно половину своих при компиляции. Затем патч так же прост:
printf '\024' | dd of=busybox bs=1 seek=37 count=1 conv=notrunc
Перенеся файл на принтер и переименовав его в busybox-ad5x, я смог успешно запускать busybox less и даже busybox ls. Всё вроде бы работало. Можно попросить busybox установить кучу симлинков для всего, что он умеет, но я хотел начать с малого, поэтому сделал симлинки только для того, чего ещё не было в родном busybox.
Убедившись, что проблем нет, я в итоге заменил новыми утилитами даже старые busybox-овские. Я даже сделал busybox-ad5x симлинком, а эту версию переименовал в busybox-zig, потому что хотел попробовать ещё один эксперимент: собрать нормальный тулчейн с помощью crosstool-ng.
Но это уже совсем другая тема на другой день. А пока Zig — с небольшим патчем — справился с задачей. В Linux есть много способов добиться своего. Оказывается, вендору очень трудно полностью залочить систему, и если есть путь внутрь, то почти всегда найдётся способ подстроить всё под себя. Busybox — отличный инструмент для урезанных систем. Даже для загрузки с дискет.