Все последние посты были про книги - этот будет про практику.

Невероятный гайд Making our own executable packer от Amos Wenger. Про то, как написать свой загрузчик и упаковщих ELF-файлов на Rust. По сути, наивная версия UPX. Идеальный повод погрузиться во внутренности Linux на Rust.

Сайт частенько был недоступен, поэтому я пользовался https://web.archive.org

По ходу гайда затрагивается много интересного из мира Linux:

  • mmap файлов на Rust
  • сборка бинарей:
    • ассемблер + линковка: nasm, ld
    • C без libc: syscall на asm, gcc -nostartfiles -nodefaultlibs
    • Rust без libc: приседаем с no_std, build.rs и build-std
    • база по Makefile
  • утилиты: objdump, readelf, strace, dd, nm, ugdb, gdb
  • gdb-скрипты на Python + Rust toolkit для анализа mmap текущего pid - такого я еще не видел
  • устройство релокаций в ELF и .o
  • разница между fPIC и non-fPIC
  • краткая история libc

Будет много unsafe-кода: работа с указателями, загрузка сегментов, ручная инициализация окружения. За собой заметил, что unsafe-блоки работают как задумано: каждый unsafe заставляет остановиться и подумать, а точно ли всё правильно. В C/C++ указатели для меня - обычный инструмент, о котором не задумываешься при использовании.

Для продакшена такой unsafe вряд ли будет полезен (если не писать драйверы или bare metal). Но как учебный опыт, почему бы и нет.

Долгая дорога

Легкой прогулкой это точно не назвать. Сам гайд писался два года. У меня повторение заняло около полутора месяцев. Результат выложил в свой «обучательный» репозиторий на GitHub. Идея выкладывать появилась не сразу, поэтому первый коммит начинается сразу со stage3 гайда.

Обычно я люблю гайды на C++ или Python, чтобы самому продумать портирование на Rust, как было с Ray Tracer. Здесь формат «сразу на Rust» зашёл. Задач для самостоятельного разбора хватало.

Решаем проблемы

Гайд написан шесть лет назад. Крейты старые, libc старая. Я же запускал всё в 2025 году — WSL, Ubuntu 22/24, ядро 6.x, glibc 2.35–2.39.

Уже в первой части пришлось переписывать код: nom v5.1 сильно отличается от nom v8. Но это цветочки, ягодки появились на 14 части туториала.

Ода ИИ-ассистенту

У автора в финале stage14 запускались ls и nano. У меня был стабильный segfault. Сравнивая обычный запуск и запуск через мой загрузчик, я понял, что не инициализирован _rtld_global. Временно прописал адрес вручную в регистр через gdb — дошёл до исполнения setlocale, где всё снова упало. Дальше быстро продвинуться не вышло, и я отдал задачу ИИ-ассистенту (Cursor). Скормил ему пару последних частей туториала, код проекта, логи gdb. Попросил запустить nano.

Ассистент работал на Ubuntu 24, где всплыли ещё два типа релокаций и новых тегов (я вел разработку на Ubuntu 22). Это он поправил быстро. Потом упёрся в ту же проблему с _rtld_global.

С помощью серии вызовов gdb, nm, objdump, readelf и модификации кода проекта он разобрался, как хаками можно инициализировать _rtld_global и обойти эту проблему. Ассистент не может вызывать gdb в интерактивном режиме, поэтому он забавно делал это офлайн:

gdb -q --batch -ex 'set pagination off' -ex 'set debuginfod enabled off' -ex 'b "elk::process::jmp"'
-ex 'run' -ex 'autosym' -ex 'p/x  $r14' -ex 'set $r14 = &_rtld_global' -ex 'c' -ex 'p/x $rdi' 
-ex 'x/s $rdi' -ex 'bt' --args ./target/debug/elk run /bin/ls -- --help

Проблема с setlocale оказалась сложнее. После пары часов экспериментов ассистент предложил замокать setlocale и __ctype_*_loc, чтобы вернуть валидные указатели на таблицы в памяти.

Ассистент проверял свой код на запуске nano, он падал. Я в какой-то момент, решил запустить nano --help, и segfault’a не было. Тоже самое произошло чуть позже с ls --help. А когда были замоканы нормально локали, то уже заработал и просто ls.

После 3 часов такой работы я ещё час потратил на вычищение мусора, чтобы прийти к минимальному набору моков и хаков. Итог - я доволен, ls работает, nano --help работает. Полноценный nano не работает, как у автора, но по заверению ИИ дальше нужен другой подход, за рамками этого гайда.

ИИ - крутой инструмент. Позволяет в трудных ситуациях прорваться и решить проблему быстрее. Да, он не идеально все сделает, но доработку напильником можно проделать самому, главное сделать PoC. В PoCах ассистенты хороши.

За ИИ стоит следить: проверять команды, иногда править его gdb-скрипты, читать логи. Если хочешь научиться, а не просто «чтобы работало», нужно следить за ходом его мыслей, обоснованием вызова команд и изменений кода. За собой заметил, что после двух часов работы в таком режиме, я пустил дело на самотёк - перестал внимательно следить за действиями ассистента, просто нажимал далее. Т.е. когнитивная нагрузка высокая, и не получится 8 часов так работать. Нужно давать себе перерыв.

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

Итог

Результат гайда опубликовал на GitHub. Самые интересные места:

Bonus

В ноябре поучаствовал в тренировках Яндекса: Тренировки. Забег по алгоритмам (8.0).
В этот раз ребята немного поленились и не стали записывать новые лекции. На каждую домашку дали по две темы из прошлых тренировок с ссылками на эти уроки. Это нисколько не делает тренировки хуже, я ими доволен.

Решил 36 из 40 задач - мой лучший результат. Одной задачи не хватило до топ-300, которых зовут на собес по упрощённому треку. Может, в следующий раз я попаду туда 😂

cert yandex 8.0