Производит ли перекомпиляция программы одинаковый двоичный файл?
Если бы я должен был скомпилировать программу в один двоичный файл, создать контрольную сумму, а затем перекомпилировать ее на той же машине с теми же настройками компилятора и компилятора и контрольной суммой перекомпилированной программы, провалилась бы контрольная сумма?
Если так, то почему? Если нет, то будет ли иметь другой процессор результат в неидентичном двоичном файле?
7 ответов
Скомпилируйте ту же программу с теми же настройками на той же машине
Хотя окончательный ответ "это зависит", разумно ожидать, что большинство компиляторов будет детерминистическим большую часть времени, и что создаваемые двоичные файлы должны быть идентичными. Действительно, некоторые системы контроля версий зависят от этого. Тем не менее, всегда есть исключения; вполне возможно, что какой- то компилятор решит вставить метку времени или что-то подобное (например, iirc, Delphi). Или сам процесс сборки может сделать это; Я видел make-файлы для программ на C, которые устанавливают макрос препроцессора на текущую метку времени. (Я думаю, это будет считаться другой настройкой компилятора.)
Также имейте в виду, что если вы статически связываете двоичный файл, то вы фактически включаете состояние всех соответствующих библиотек на своем компьютере, и любое изменение в любой из них также повлияет на ваш двоичный файл. Таким образом, важны не только настройки компилятора.
Скомпилируйте ту же программу на другом компьютере с другим процессором.
Здесь все ставки сняты. Большинство современных компиляторов способны выполнять целевые оптимизации; если эта опция включена, то двоичные файлы, вероятно, будут отличаться, если процессоры не похожи (и даже тогда, это возможно). Также см. Примечание о статической компоновке: среда конфигурации выходит далеко за пределы настроек компилятора. Если у вас нет очень строгого контроля конфигурации, очень вероятно, что что-то отличается между двумя машинами.
-frandom-seed=123
контролирует некоторую внутреннюю случайность GCC.man gcc
говорит:Эта опция обеспечивает начальное число, которое GCC использует вместо случайных чисел при генерации определенных имен символов, которые должны быть разными в каждом скомпилированном файле. Он также используется для размещения уникальных штампов в файлах данных покрытия и объектных файлах, которые их производят. Вы можете использовать опцию -frandom-seed для создания воспроизводимых идентичных объектных файлов.
__FILE__
: поместите источник в фиксированную папку (например,/tmp/build
)- за
__DATE__
,__TIME__
,__TIMESTAMP__
:- libfaketime: https://github.com/wolfcw/libfaketime
- переопределить эти макросы с
-D
-Wdate-time
или же-Werror=date-time
: предупредить или потерпеть неудачу, если либо__TIME__
,__DATE__
или же__TIMESTAMP__
это используется. Ядро Linux 4.4 использует его по умолчанию.
- использовать
D
флаг сar
или используйте https://github.com/nh2/ar-timestamp-wiper/tree/master чтобы стереть марки -fno-guess-branch-probability
: старые версии руководства говорят, что это источник недетерминизма, но не больше. Не уверен, что это покрыто-frandom-seed
или нет.
Debian Reproducible строит проект, пытаясь стандартизировать пакеты Debian побайтово, и недавно получил грант Linux Foundation. Это включает в себя больше, чем просто компиляция, но она должна представлять интерес.
Buildroot имеет BR2_REPRODUCIBLE
вариант, который может дать некоторые идеи на уровне пакета, но это далеко не завершено на данный момент.
Связанные темы:
То, что вы спрашиваете, это " детерминирован ли выход". Если вы скомпилировали программу один раз, сразу же скомпилировали ее снова, вы, вероятно, получили бы тот же выходной файл. Однако, если что-то изменилось - даже небольшое изменение - особенно в компоненте, который использует скомпилированная программа, то выходные данные компилятора также могут измениться.
Производит ли перекомпиляция программы одинаковый двоичный файл?
Для всех компиляторов? Нет. Компилятору C#, по крайней мере, не разрешено.
У Эрика Липперта очень подробное объяснение, почему вывод компилятора не является детерминированным.
[T] Компилятор C# по своей конструкции никогда не создает один и тот же двоичный файл дважды. Компилятор C# внедряет только что сгенерированный GUID в каждую сборку, каждый раз, когда вы его запускаете, тем самым гарантируя, что никакие две сборки никогда не будут побитово идентичны. Чтобы процитировать из спецификации CLI:
Столбец Mvid должен индексировать уникальный GUID [...], который идентифицирует этот экземпляр модуля. [...] Mvid должен быть сгенерирован заново для каждого модуля [...]. Хотя [время выполнения] само по себе не использует Mvid, другие инструменты (такие как отладчики [...]) полагаются на тот факт, что Мвид почти всегда отличается от одного модуля к другому.
Хотя это специфично для версии компилятора C#, многие пункты в статье могут быть применены к любому компилятору.
Во-первых, мы предполагаем, что мы всегда получаем один и тот же список файлов каждый раз в одном и том же порядке. Но это в некоторых случаях зависит от операционной системы. Когда вы говорите "csc *.cs", порядок, в котором операционная система выводит список подходящих файлов, является подробностью реализации операционной системы; компилятор не сортирует этот список в каноническом порядке.
Я бы сказал, НЕТ, это не на 100% детерминировано. Ранее я работал с версией GCC, которая генерирует целевые двоичные файлы для процессора Hitachi H8.
Это не проблема с отметкой времени. Даже если проблема с отметкой времени игнорируется, конкретная архитектура процессора может позволять кодировать одну и ту же инструкцию двумя слегка различными способами, где некоторые биты могут быть равны 1 или 0. Мой предыдущий опыт показывает, что сгенерированные двоичные файлы были одинаковыми MOST времени но иногда gcc генерирует двоичные файлы одинакового размера, но некоторые байты отличаются только на 1 бит, например, 0XE0 становится 0XE1.
Проект https://reproducible-builds.org/ - это все об этом, и он изо всех сил старается ответить на ваш вопрос "нет, они не будут различаться" в максимально возможном количестве мест. Сейчас NixOS и Debian воспроизводят свои пакеты более чем на 90%.
Если вы скомпилируете двоичный файл, а я скомпилировал двоичный файл, и он по битам идентичен, то я могу быть уверен, что исходный код и инструменты определяют выход, и что в некоторых случаях вы не крались троянский код по пути.
Если мы объединяем воспроизводимость с загрузкой из читаемого человеком источника, как работает http://bootstrappable.org/, мы получаем систему, определяемую с нуля читаемым человеком источником, и только тогда мы находимся в точке, где мы можем верить, что знаем, что делает система.
В общем нет. Наиболее разумно сложные компиляторы будут включать время компиляции в объектный модуль. Даже если бы вам пришлось сбрасывать часы, вы должны были бы быть очень точными в отношении того, когда вы запускали компиляцию (и затем надеяться, что обращения к диску и т. Д. Будут такими же, как и раньше).