Воспроизводимые сборки
- title
- Воспроизводимые сборки
- type
- concept
- summary
- Бит в бит идентичный результат из одного исходника и почему компиляторы постоянно его ломают
- tags
- reproducible-builds, compilers, supply-chain, build-systems
- sources
- anubis-wasm-vendor-binary
- created
- 2026-07-21
- updated
- 2026-07-21
- lang
- ru
- translation_of
- reproducible-builds
- source_updated
- 2026-07-21
- translated
- 2026-09-01
- translator
- lllm/antigravity/gemini-3.7-flash-medium
Сборка считается воспроизводимой, когда один и тот же исходный код, собранный с теми же флагами под ту же целевую платформу, даёт байт в байт идентичный результат. Интуитивно кажется, что компилятор - это детерминированная функция из исходников в байт-код, поэтому всё должно работать само собой. На практике это не так. Реальные тулчейны подмешивают в выходные данные состояние окружения, не связанное с исходниками, и это вскрывается только при сравнении diff'ом двух сборок одного и того же коммита.
Зачем это нужно: именно воспроизводимость позволяет третьей стороне верифицировать бинарник. Если проект коммитит готовый бинарник в свой репозиторий (см. dependency-vendoring) и заявляет, что тот собран из определённого исходного кода, мейнтейнер пакета может доверять этому заявлению, только если пересоберёт его и убедится в совпадении хешей. Без воспроизводимости утверждение "вот бинарник, а вот исходники, из которых он получен" проверить невозможно. Это делает воспроизводимость свойством безопасности цепочки поставок, а не просто аккуратностью.
Где теряется детерминизм
Журнал сборки anubis-wasm-vendor-binary подробно разбирает три разных сценария сбоев, с каждым из которых пришлось столкнуться при подготовке одного небольшого завендоренного бинарника.
Встроенные метаданные сборки. Классический случай: макросы __DATE__ и __TIME__ зашивают в результат астрономическое время компиляции. Две сборки с разницей в несколько секунд различаются просто потому, что содержат разные временные метки. Решение - никогда не встраивать время сборки или фиксировать его (распространённая конвенция - SOURCE_DATE_EPOCH).
Вызов внешних утилит из $PATH. При сборке под WebAssembly clang молча вызывает wasm-opt из $PATH. На разных машинах установлены разные версии wasm-opt - на одной v108, на другой v130 - из-за чего "один и тот же" тулчейн выдаёт разные байты, а старая версия может и вовсе упасть на новых инструкциях (в случае Anubis это были WebAssembly Exceptions). Компилятор, который лезет за пределы своих зафиксированных входных данных, перестаёт быть чистой функцией от своих аргументов. В Anubis это решили передачей флага --no-wasm-opt, отрезав скрытую зависимость.
Генерация кода, зависящая от адресов памяти. Механизм обработки исключений в clang допускал утечку значений сырых указателей в порядок генерации блоков try_table. Вычисления были идентичными, а вот порядок байтов и индексы catch_all_ref - нет, поэтому сборки различались примерно на 29 байт. Помогли две меры: отключение рандомизации адресного пространства через setarch --addr-no-randomize, чтобы указатели ложились по одинаковым адресам, и фиксация эталонных контрольных сумм sha256 для каждой архитектуры с проверкой в CI как на x86_64, так и на arm64.
Порядок итерации между архитектурами. Проблема с порядком указателей проявляется и между архитектурами, поскольку порядок обхода указателей на arm64 отличается от x86_64. В Anubis добились воспроизводимости внутри одной архитектуры, но не между ними - оставшееся расхождение связано с багом в апстриме LLVM (порядок итерации не инициализируется сидом). Пришлось ограничиться критерием "детерминировано в пределах архитектуры".
Общая картина такова: в теории компилятор детерминирован, но реальный тулчейн тянет за собой системные часы, $PATH, ASLR и зависящий от адресов порядок итерации. Каждый из этих факторов нужно зафиксировать или учесть, прежде чем результат станет по-настоящему воспроизводимым. Или, как сказано в первоисточнике: "Казалось бы, на одни и те же байты на входе ты должен получить те же байты на выходе. lol. lmao. А вот и нет".
Связанные страницы
- anubis-wasm-vendor-binary - практический пример, на котором основана эта страница
- dependency-vendoring - воспроизводимость делает закоммиченный завендоренный бинарник заслуживающим доверия
- supply-chain-security - цепочка доверия, которую защищают воспроизводимые сборки
- abi-stability - другой контракт на уровне бинарного кода; обе темы касаются гарантий скомпилированных артефактов