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

25

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

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

Дэвид
источник
8
Это зависит от компилятора. Некоторые из них встраивают метки времени, поэтому ответ для них - «нет».
ta.speot.is
На самом деле это зависит от формата исполняемого файла , а не от компилятора. Некоторые исполняемые форматы, такие как формат PE Windows, включают в себя временную метку, которая касается времени и даты компиляции, в то время как другие форматы, такие как формат ELF Linux, этого не делают. В любом случае, этот вопрос зависит от определения «идентичного двоичного файла». Сам образ будет / должен быть побитовым идентичным, если один и тот же исходный файл скомпилирован с тем же компилятором и библиотеками, и переключателями, и всем, но заголовок и другие метаданные могут отличаться.
Synetech

Ответы:

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

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

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

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

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

RICi
источник
1
Скажем, я использовал GCC, и я не использовал опцию march (опция, которая оптимизирует двоичный файл для определенного семейства процессоров), и я должен был скомпилировать двоичный файл с одним процессором, а затем с другим процессором разница?
Дэвид
1
@ Дэвид: Это все еще зависит. Во-первых, библиотеки, на которые вы ссылаетесь, могут иметь специфичные для архитектуры сборки. Таким образом, выходные данные gcc -cвполне могут быть идентичными, но связанные версии отличаются. Кроме того, это не просто -march; есть также -mtune/-mcpu и -mfpmatch(и, возможно, другие). Некоторые из них могут иметь разные значения по умолчанию в разных установках, поэтому вам может потребоваться явно указать наихудший вариант для ваших машин; это может значительно снизить производительность, особенно если вы вернетесь к i386 без sse. И, конечно, если один из ваших процессоров - ARM, а другой - i686 ...
rici
1
Кроме того, является ли GCC одним из рассматриваемых компиляторов, добавляющих временную метку в двоичные файлы?
Дэвид
@ Давид: афаик, нет.
Ричи
8

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

headkase
источник
2
Очень хорошая мысль. Эта статья имеет несколько очень интересных наблюдений. В частности, компиляция с помощью GCC может не быть детерминированной в отношении входных данных в определенных случаях, например, в том, как это искажает функции в анонимных пространствах имен, для которых он использует генератор случайных чисел внутри. Чтобы получить детерминизм в этом конкретном случае, укажите начальное случайное начальное число, указав параметр -frandom-seed=string.
извед
7

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

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

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

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

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

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

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

ta.speot.is
источник
Это не должно быть сложно сделать встроенным воспроизводимым (кроме нескольких легко сбрасываемых полей, таких как время компиляции и GUID сборки). Например, сортировка входных файлов в канонический порядок является однострочным. Даже этот GUID может быть хешем остальной части сборки вместо вновь сгенерированного.
CodesInChaos
Я предполагаю, что вы имеете в виду компилятор Microsoft C #, или это требование спецификации?
Дэвид
@David Спецификация CLI требует этого. Компилятор Mono C # должен будет сделать то же самое. То же самое для любого компилятора VB .NET.
ta.speot.is
4
Стандарт ECMA не должен иметь временных отметок или различий MVID. Без них это по крайней мере возможно для идентичных двоичных файлов в C #. Таким образом, главная причина - сомнительное дизайнерское решение, а не реальное техническое ограничение.
Шив
7
  • -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опция, которая может дать некоторые идеи на уровне пакета, но на данный момент она далека от завершения.

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

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
источник
3

Проект https://reproducible-builds.org/ - это все об этом, и он изо всех сил старается ответить на ваш вопрос «нет, они не будут различаться» в максимально возможном количестве мест. Сейчас NixOS и Debian воспроизводят свои пакеты более чем на 90%.

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

Если мы совмещаем воспроизводимость с загрузкой из читаемого человеком источника, как работает http://bootstrappable.org/ , мы получаем систему, определяемую с нуля читаемым человеком источником, и только тогда мы находимся в точке, где мы можем верить, что знаем, что делает система.

clacke
источник
1
Прикольные ссылки. Я фанат Buildroot, но если кто-нибудь даст мне перекрестную арку Nix ARM, которая загружается на QEMU, я буду счастлив :-)
Сиро Сантилли (Ciro Santilli)
Я не упомянул Guix, потому что я не знаю, где найти их номера, но они были до NixOS в поезде воспроизводимости с инструментами проверки и тому подобным, так что я уверен, что они на равных или даже лучше.
щелчок
2

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

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

JavaMan
источник
И привело ли это к другому поведению или «серьезным проблемам»?
Флориан Штрауб
1

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

Даниэль Р Хикс
источник