La ricompilazione di un programma produce un binario identico bit per bit?


25

Se dovessi compilare un programma in un singolo binario, creare un checksum e quindi ricompilarlo sulla stessa macchina con le stesse impostazioni del compilatore e del compilatore e fare il checksum del programma ricompilato, il checksum fallirebbe?

Se è così, perché? In caso contrario, avere una CPU diversa comporterebbe un binario non identico?


8
Dipende dal compilatore. Alcuni incorporano timestamp, quindi la risposta è "no" per quelli.
ta.speot.is

In realtà dipende dal formato eseguibile , non dal compilatore. Alcuni formati eseguibili come il formato PE di Windows includono un timestamp che viene toccato alla data e ora di compilazione, mentre altri formati come il formato ELF di Linux no. In entrambi i casi, questa domanda si basa sulla definizione di "binario identico". L'immagine stessa sarà / dovrebbe essere identica per bit se lo stesso file di origine viene compilato con lo stesso compilatore e le stesse librerie e opzioni e tutto, ma l'intestazione e altri metadati possono variare.
Synetech,

Risposte:


19
  1. Compila lo stesso programma con le stesse impostazioni sulla stessa macchina:

    Sebbene la risposta definitiva sia "dipende", è ragionevole aspettarsi che la maggior parte dei compilatori sarà deterministica per la maggior parte del tempo e che i binari prodotti dovrebbero essere identici. In effetti, alcuni sistemi di controllo della versione dipendono da questo. Tuttavia, ci sono sempre delle eccezioni; è del tutto possibile che un compilatore da qualche parte decida di inserire un timestamp o alcuni di questi (iirc, Delphi, per esempio). O lo stesso processo di compilazione potrebbe farlo; Ho visto i makefile per i programmi C che impostano una macro di preprocessore sul timestamp corrente. (Immagino che conterebbe come una diversa impostazione del compilatore.)

    Inoltre, tieni presente che se colleghi staticamente il binario, allora stai effettivamente incorporando lo stato di tutte le librerie rilevanti sul tuo computer e qualsiasi modifica in una di queste influirà anche sul tuo binario. Quindi non sono rilevanti solo le impostazioni del compilatore.

  2. Compilare lo stesso programma su una macchina diversa con una CPU diversa.

    Qui, tutte le scommesse sono disattivate. I compilatori più moderni sono in grado di eseguire ottimizzazioni specifiche per target; se questa opzione è abilitata, è probabile che i binari differiscano a meno che le CPU non siano simili (e anche allora, è possibile). Inoltre, vedi la nota sopra sul collegamento statico: l'ambiente di configurazione va ben oltre le impostazioni del compilatore. A meno che tu non abbia un controllo di configurazione molto rigoroso, è estremamente probabile che qualcosa differisca tra le due macchine.


1
Diciamo che stavo usando GCC, e non stavo usando l'opzione march (l'opzione che ottimizza il binario per una specifica famiglia di CPU), e dovevo compilare un binario con una CPU, e poi con un'altra CPU ci sarebbe stato un differenza?
David

1
@ David: dipende ancora. Innanzitutto, le librerie a cui stai collegando potrebbero avere build specifiche per l'architettura. Quindi l'output di gcc -cpotrebbe essere identico, ma le versioni collegate sono diverse. Inoltre, non è solo -march; c'è anche -mtune/-mcpu e -mfpmatch(e possibilmente altri). Alcuni di questi potrebbero avere impostazioni predefinite diverse su installazioni diverse, quindi potrebbe essere necessario forzare il caso peggiore possibile per le macchine in modo esplicito; farlo potrebbe ridurre significativamente le prestazioni, in particolare se si torna a i386 senza sse. E, naturalmente, se uno dei tuoi cpus è un ARM e l'altro un i686 ...
rici,

1
Inoltre, GCC è uno dei compilatori in questione che aggiunge un timestamp ai binari?
David

@david: afaik, no.
rici,

8

Quello che stai chiedendo è "è l'output deterministico ". Se hai compilato il programma una volta, lo hai immediatamente compilato di nuovo probabilmente finiresti con lo stesso file di output. Tuttavia, se qualcosa cambia, anche una piccola modifica, specialmente in un componente utilizzato dal programma compilato, anche l'output del compilatore potrebbe cambiare.


2
Ottimo punto davvero. Questo articolo contiene alcune osservazioni molto interessanti. In particolare, la compilazione con GCC potrebbe non essere deterministica per quanto riguarda gli input in alcuni casi, ad esempio nel modo in cui si manipola in spazi dei nomi anonimi, per i quali utilizza internamente un generatore di numeri casuali. Per ottenere determinismo in questo caso particolare, fornire un seme casuale iniziale specificando l'opzione -frandom-seed=string.
ack

7

La ricompilazione di un programma produce un binario identico bit per bit?

Per tutti i compilatori? No. Il compilatore C #, almeno, non è autorizzato a farlo.

Eric Lippert ha un'analisi molto approfondita del perché l'output del compilatore non è deterministico .

[T] il compilatore C # di progettazione non produce mai lo stesso binario due volte. Il compilatore C # incorpora un GUID appena generato in ogni assembly, ogni volta che lo si esegue, garantendo in tal modo che due assembly non siano mai identici bit per bit. Per citare dalla specifica CLI:

La colonna Mvid indicizzerà un GUID univoco [...] che identifica questa istanza del modulo. [...] Il Mvid dovrebbe essere appena generato per ogni modulo [...] Mentre il [runtime] stesso non fa uso del Mvid, altri strumenti (come i debugger [...]) si basano sul fatto che il Mvid differisce quasi sempre da un modulo all'altro.

Sebbene sia specifico per una versione del compilatore C #, molti punti dell'articolo possono essere applicati a qualsiasi compilatore.

Prima di tutto, supponiamo che otteniamo sempre lo stesso elenco di file ogni volta, nello stesso ordine. Ma questo in alcuni casi dipende dal sistema operativo. Quando si dice "csc * .cs", l'ordine in cui il sistema operativo fornisce l'elenco dei file corrispondenti è un dettaglio di implementazione del sistema operativo; il compilatore non ordina tale elenco in un ordine canonico.


Non dovrebbe essere difficile rendere riproducibile il build (a parte alcuni campi facilmente scartati come il tempo di compilazione e il GUID dell'assembly). Ad esempio, l'ordinamento dei file di input in un ordine canonico è una riga. Anche quel GUID potrebbe essere un hash del resto dell'assembly invece che appena generato.
CodesInChaos,

Presumo che tu intenda il compilatore Microsoft C # o è un requisito della specifica?
David

@David Le specifiche della CLI lo richiedono. Il compilatore C # di Mono dovrebbe fare lo stesso. Idem per qualsiasi compilatore VB .NET.
ta.speot.is,

4
Lo standard ECMA non deve avere timestamp o differenze MVID. Senza quelli, è almeno possibile per binari identici in C #. Pertanto, il motivo principale è una decisione di progettazione discutibile e non un vero vincolo tecnico.
Shiv

7
  • -frandom-seed=123controlla la casualità interna di GCC. man gccdice:

    Questa opzione fornisce un seed che GCC utilizza al posto di numeri casuali per generare determinati nomi di simboli che devono essere diversi in ogni file compilato. Viene inoltre utilizzato per posizionare timbri univoci nei file di dati di copertura e nei file oggetto che li producono. È possibile utilizzare l'opzione -frandom-seed per produrre file oggetto identicamente riproducibili.

  • __FILE__: metti la fonte in una cartella fissa (es. /tmp/build)

  • per __DATE__, __TIME__, __TIMESTAMP__:
    • libfaketime: https://github.com/wolfcw/libfaketime
    • sovrascrivere quelle macro con -D
    • -Wdate-timeo -Werror=date-time: mettere in guardia o non se uno __TIME__, __DATE__o __TIMESTAMP__si viene utilizzato. Il kernel 4.4 di Linux lo utilizza per impostazione predefinita.
  • usa la Dbandiera aro usa https://github.com/nh2/ar-timestamp-wiper/tree/master per cancellare i timbri
  • -fno-guess-branch-probability: le versioni manuali più vecchie dicono che è una fonte di non determinismo, ma non più . Non sono sicuro se questo è coperto -frandom-seedo no.

Il progetto Debian Reproducible costruisce tentativi di standardizzare i pacchetti Debian byte per byte e recentemente ha ottenuto una concessione di Linux Foundation . Ciò include più della semplice compilazione, ma dovrebbe essere interessante.

Buildroot ha BR2_REPRODUCIBLEun'opzione che può dare alcune idee a livello di pacchetto, ma è tutt'altro che completo a questo punto.

Discussioni correlate:


3

Il progetto https://reproducible-builds.org/ è tutto su questo e si sta impegnando a fondo per rispondere alla tua domanda "no, non differiranno" nel maggior numero di posti possibile. NixOS e Debian sono ora oltre il 90% in riproducibilità per i loro pacchetti.

Se compili un binario, e io compilo un binario, e sono identici bit per bit, allora posso essere rassicurato che il codice sorgente e gli strumenti sono ciò che determina l'output e che non ti sei nascosto codice trojan lungo la strada.

Se uniamo la riproducibilità alla bootstrappability da una fonte leggibile dall'uomo, come sta lavorando su http://bootstrappable.org/ , otteniamo un sistema determinato da una fonte leggibile dall'uomo, e solo allora siamo nel punto in cui possiamo fidarci di sapere cosa sta facendo il sistema.


1
Link interessanti. Sono un fan di Buildroot, ma se qualcuno mi dà un'installazione cross-arm Nix ARM che si avvia su QEMU, sarò felice :-)
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Non ho menzionato Guix perché non so dove trovare i loro numeri, ma erano prima di NixOS sul treno della riproducibilità con strumenti di verifica e simili, quindi sono sicuro che siano su un piano di parità o migliore.
clacke

2

Direi NO, non è deterministico al 100%. In precedenza ho lavorato con una versione di GCC che genera binari di destinazione per il processore Hitachi H8.

Non è un problema con il timestamp. Anche se il problema del timestamp viene ignorato, l'architettura specifica del processore può consentire la codifica della stessa istruzione in 2 modi leggermente diversi in cui alcuni bit possono essere 1 o 0. La mia esperienza precedente mostra che i binari generati erano gli stessi la maggior parte delle volte ma occasionalmente gcc genererebbe binari di dimensioni identiche ma alcuni byte diversi di solo 1 bit, ad es. 0XE0 diventa 0XE1.


E questo ha portato a comportamenti diversi o "problemi seri"?
Florian Straub,

1

In generale, no. I compilatori più ragionevolmente sofisticati includeranno il tempo di compilazione nel modulo oggetto. Anche se dovessi ripristinare l'orologio, dovresti essere molto preciso rispetto a quando hai iniziato la compilazione (e quindi sperare che gli accessi al disco, ecc., Avessero la stessa velocità di prima).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.