23

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

Если так, то почему? Если нет, то будет ли иметь другой процессор результат в неидентичном двоичном файле?

6 ответов6

18
  1. Скомпилируйте ту же программу с теми же настройками на той же машине:

    Хотя окончательный ответ "это зависит", разумно ожидать, что большинство компиляторов будет детерминистическим большую часть времени, и что создаваемые двоичные файлы должны быть идентичными. Действительно, некоторые системы контроля версий зависят от этого. Тем не менее, всегда есть исключения; вполне возможно, что какой- то компилятор решит вставить метку времени или что-то подобное (например, iirc, Delphi). Или сам процесс сборки может сделать это; Я видел make-файлы для программ на C, которые устанавливают макрос препроцессора на текущую метку времени. (Я думаю, это будет считаться другой настройкой компилятора.)

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

  2. Скомпилируйте одну и ту же программу на другом компьютере с другим процессором.

    Здесь все ставки сняты. Большинство современных компиляторов способны выполнять целевые оптимизации; если эта опция включена, то двоичные файлы, вероятно, будут отличаться, если процессоры не похожи (и даже тогда, это возможно). Также см. Примечание о статической компоновке: среда конфигурации выходит далеко за пределы настроек компилятора. Если у вас нет очень строгого контроля конфигурации, очень вероятно, что что-то отличается между двумя машинами.

8

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

7

Производит ли перекомпиляция программы бинарный идентичный двоичный файл?

Для всех компиляторов? Нет. Компилятору C #, по крайней мере, не разрешено.

У Эрика Липперта очень подробное объяснение того, почему вывод компилятора не является детерминированным.

[T] компилятор C # по своей конструкции никогда не создает один и тот же двоичный файл дважды. Компилятор C # внедряет только что сгенерированный GUID в каждую сборку каждый раз, когда вы его запускаете, тем самым гарантируя, что никакие две сборки никогда не будут побитово идентичны. Чтобы процитировать из спецификации CLI:

Столбец Mvid должен индексировать уникальный GUID [...], который идентифицирует этот экземпляр модуля. [...] Mvid должен быть заново сгенерирован для каждого модуля [...] Хотя само [время выполнения] не использует Mvid, другие инструменты (например, отладчики [...]) полагаются на тот факт, что Mvid почти всегда отличается от одного модуля к другому.

Хотя это специфично для версии компилятора C #, многие пункты в статье могут быть применены к любому компилятору.

Во-первых, мы предполагаем, что мы всегда получаем один и тот же список файлов каждый раз в одном и том же порядке. Но это в некоторых случаях зависит от операционной системы. Когда вы говорите «csc * .cs», порядок, в котором операционная система выводит список подходящих файлов, является подробностью реализации операционной системы; компилятор не сортирует этот список в каноническом порядке.

5
  • -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 которая может дать некоторые идеи на уровне пакета, но на данный момент она далека от завершения.

Связанные темы:

2

Я бы сказал, НЕТ, это не на 100% детерминировано. Ранее я работал с версией GCC, которая генерирует целевые двоичные файлы для процессора Hitachi H8.

Это не проблема с отметкой времени. Даже если проблема с отметкой времени игнорируется, конкретная архитектура процессора может позволять кодировать одну и ту же инструкцию 2 слегка отличающимися способами, где некоторые биты могут быть 1 или 0. Мой предыдущий опыт показывает, что сгенерированные двоичные файлы были MOST того же времени, но иногда gcc генерировал двоичные файлы с одинаковым размером, но некоторые байты отличались только на 1 бит, например, 0XE0 становится 0XE1.

1

В общем нет. Наиболее разумно сложные компиляторы включают время компиляции в объектный модуль. Даже если бы вам пришлось сбрасывать часы, вы должны были бы быть очень точными в отношении того, когда вы запускали компиляцию (и затем надеяться, что обращения к диску и т.д. Были такими же, как и раньше).

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .