Чи виробляє перекомпіляція програми біт-біт ідентичний бінарний файл?


25

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

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


8
Це залежить від компілятора. Деякі з них вкладають часові позначки, тому для них відповідь "ні".
ta.speot.is

Насправді це залежить від виконуваного формату , а не компілятора. Деякі виконувані формати, такі як формат PE в Windows, включають часову позначку, яка торкається часу та дати компіляції, а інші формати, такі як формат ELF Linux Linux, не мають. Так чи інакше, це питання залежить від визначення поняття "однакове бінарне". Саме зображення буде / повинно бути бітовим ідентичним, якщо один і той же вихідний файл зібраний з тим же компілятором, бібліотеками та комутаторами і все, але заголовок та інші метадані можуть змінюватися.
Synetech

Відповіді:


19
  1. Скомпілюйте ту ж програму з тими ж налаштуваннями на одній машині:

    Хоча остаточна відповідь - "це залежить", розумно очікувати, що більшість компіляторів буде детермінованою більшу частину часу, а створені двійкові файли повинні бути ідентичними. Дійсно, деякі системи управління версіями залежать від цього. І все-таки завжди є винятки; то цілком можливо , що деякі компілятор де - то вирішить вставити мітку або деякі такі (IIRC, Delphi робить, наприклад). Або сам процес збирання може це зробити; Я бачив makefiles для програм C, які встановлюють макрос препроцесора на поточну часову позначку. (Я думаю, що це вважатиметься різним параметром компілятора.)

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

  2. Скомпілюйте ту саму програму на іншій машині з іншим процесором.

    Тут усі ставки відключені. Більшість сучасних компіляторів здатні робити оптимізацію, орієнтовану на ціль; якщо цей параметр увімкнено, то ймовірні файли можуть відрізнятися, якщо процесори не схожі (і навіть тоді це можливо). Також див. Вищенаведене зауваження про статичне з'єднання: середовище конфігурації виходить далеко за рамки параметрів компілятора. Якщо у вас дуже жорсткий контроль конфігурації, то надзвичайно ймовірно, що між двома машинами щось відрізняється.


1
Скажімо, я використовував GCC, і я не використовував параметр марш (параметр, який оптимізує двійкові файли для конкретного сімейства процесорів), і я повинен був скласти двійковий файл з одним процесором, а потім з іншим процесором був би різниця?
Девід

1
@David: Це все ще залежить. По-перше, бібліотеки, до яких ви посилаєтесь, можуть мати особливості архітектури. Таким чином, вихід gcc -cможе бути ідентичним, але пов'язані версії відрізняються. Крім того, це не просто -march; є також -mtune/-mcpu і -mfpmatch(і, можливо, інші). Деякі з них можуть мати різні за замовчуванням у різних установках, тому вам може знадобитися явно примусити найгірший варіант для ваших машин; це може значно знизити продуктивність, особливо якщо ви повернетесь до i386 без sse. І, звичайно, якщо один з ваших процесорів є ARM, а інший i686 ...
rici

1
Також GCC є одним із спірних компіляторів, які додають часову позначку до двійкових файлів?
Девід

@david: afaik, ні.
rici

8

Що ви запитуєте, це " детермінований вихід ". Якщо ви скомпілювали програму один раз, негайно скомпілювали її ще раз, ви, ймовірно, отримаєте той самий вихідний файл. Однак якщо щось змінилося - навіть невелика зміна - особливо в компоненті, який використовує компільована програма, то вихід компілятора також може змінитися.


2
Справді, дуже хороший момент. У цій статті є дуже цікаві спостереження. Зокрема, компіляція з GCC може не бути детермінованою щодо вхідних даних у певних випадках, наприклад, як вона керує функціями в анонімних просторах імен, для яких він використовує внутрішньо генератор випадкових чисел. Щоб отримати детермінізм у цьому конкретному випадку, подайте початкове випадкове насіння, вказавши варіант -frandom-seed=string.
ак

7

Чи виробляє перекомпіляція програми біт-біт ідентичний бінарний файл?

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

Ерік Ліпперт дуже глибоко розбив про те, чому вихід компілятора не є детермінованим .

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

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

Хоча це стосується і версії компілятора C #, багато пунктів статті можуть бути застосовані до будь-якого компілятора.

По-перше, ми припускаємо, що ми завжди отримуємо один і той же список файлів кожного разу в тому ж порядку. Але це в деяких випадках залежить від операційної системи. Коли ви говорите "csc * .cs", порядок, в якому операційна система записує список відповідних файлів, - це деталь реалізації операційної системи; компілятор не сортує цей список у канонічному порядку.


Зробити вбудоване відтворення не повинно бути важким (крім кількох полів, що легко відкидаються, наприклад, час компіляції та GUID складання). Наприклад, сортування вхідних файлів у канонічному порядку - це однолінійний. Навіть цей GUID міг би бути хешем залишку збірки замість новогенерованого.
CodesInChaos

Я припускаю, що ви маєте на увазі компілятор Microsoft C #, чи це вимога специфікації?
Девід

@David Спеціалізація CLI цього вимагає. Компілятор C # Mono повинен був би зробити те саме. Дітто для будь-якого компілятора 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варіант, який може дати деякі ідеї на рівні пакету, але це ще далеко не завершено.

Пов'язані теми:


3

Проект https://reproducible-builds.org/ про все це і намагається зробити відповідь на ваше запитання "ні, вони не будуть відрізнятися" в якомога більшій кількості місць. В даний час NixOS та Debian відтворюють понад 90% відтворюваності своїх пакетів.

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

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


1
Класні посилання. Я фанбук Buildroot, але якщо хтось дасть мені налаштування хрестової арки Nix ARM, яка завантажується на QEMU, я буду радий :-)
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Я не згадував Guix, тому що не знаю, де знайти їх номери, але вони були раніше, ніж NixOS на поїзді відтворюваності з інструментами перевірки та інше, тому я впевнений, що вони на рівних умовах чи краще.
clacke

2

Я б сказав, ні, це не на 100% детерміновано. Раніше я працював з версією GCC, яка генерує цільові бінарні файли для процесора Hitachi H8.

Це не проблема з позначкою часу. Навіть якщо проблема з марками часу ігнорується, конкретна архітектура процесора може дозволити кодування однієї і тієї ж інструкції двома дещо різними способами, де деякі біти можуть бути 1 або 0. Мій попередній досвід показує, що згенеровані бінарні файли були однаковими МОСТО часу але іноді gcc генерує бінарні файли однакового розміру, але деякі байти відрізняються лише на 1 біт, наприклад 0XE0 стає 0XE1.


І це призвело до різної поведінки чи "серйозних проблем"?
Флоріан Штрауб

1

Загалом, ні. Більшість досить складних компіляторів включатиме час компіляції в об'єктний модуль. Навіть якби ви скинули годинник, вам довелося б бути дуже точними щодо того, коли ви розпочали компіляцію (і тоді сподіваєтесь, що доступ до диска тощо повинен бути такою ж швидкістю, як і раніше).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.