¿Recompilar un programa produce un binario idéntico bit por bit?

25

Si compilara un programa en un solo binario, creara una suma de verificación y luego lo volviera a compilar en la misma máquina con la misma configuración del compilador y del compilador y sumara el programa recompilado, ¿fallaría la suma de verificación?

Si es así, ¿por qué es esto? Si no, ¿tener una CPU diferente daría como resultado un binario no idéntico?

David
fuente
8
Depende del compilador. Algunos de ellos incorporan marcas de tiempo, por lo que la respuesta es "no" para aquellos.
ta.speot.is
En realidad, depende del formato ejecutable , no del compilador. Algunos formatos ejecutables como el formato PE de Windows incluyen una marca de tiempo que se toca a la hora y fecha de compilación, mientras que otros formatos como el formato ELF de Linux no. De cualquier manera, esta pregunta depende de la definición de "binario idéntico". La imagen en sí será / debería ser idéntica a nivel de bits si el mismo archivo fuente se compila con el mismo compilador y bibliotecas y modificadores y todo, pero el encabezado y otros metadatos pueden variar.
Synetech

Respuestas:

19
  1. Compile el mismo programa con la misma configuración en la misma máquina:

    Aunque la respuesta definitiva es "depende", es razonable esperar que la mayoría de los compiladores sean deterministas la mayor parte del tiempo y que los binarios producidos sean idénticos. De hecho, algunos sistemas de control de versiones dependen de esto. Aún así, siempre hay excepciones; es muy posible que algún compilador en algún lugar decida insertar una marca de tiempo o algo así (iirc, Delphi lo hace, por ejemplo). O el proceso de construcción en sí podría hacer eso; He visto archivos MAKE para programas en C que establecen una macro de preprocesador en la marca de tiempo actual. (Sin embargo, supongo que eso contaría como una configuración de compilador diferente).

    Además, tenga en cuenta que si vincula estáticamente el binario, entonces está incorporando efectivamente el estado de todas las bibliotecas relevantes en su máquina, y cualquier cambio en cualquiera de ellas también afectará su binario. Por lo tanto, no son solo las configuraciones del compilador las que son relevantes.

  2. Compile el mismo programa en una máquina diferente con una CPU diferente.

    Aquí, todas las apuestas están apagadas. La mayoría de los compiladores modernos son capaces de realizar optimizaciones específicas del objetivo; Si esta opción está habilitada, es probable que los binarios difieran a menos que las CPU sean similares (e incluso entonces, es posible). Además, consulte la nota anterior sobre la vinculación estática: el entorno de configuración va mucho más allá de la configuración del compilador. A menos que tenga un control de configuración muy estricto, es extremadamente probable que algo difiera entre las dos máquinas.

rici
fuente
1
Digamos que estaba usando GCC, y no estaba usando la opción de marcha (la opción que optimiza el binario para una familia específica de CPU), y debía compilar un binario con una CPU, y luego con otra CPU habría un ¿diferencia?
David
1
@David: todavía depende. Primero, las bibliotecas a las que se vincula pueden tener compilaciones específicas de la arquitectura. Por lo tanto, la salida de gcc -cpuede ser idéntica, pero las versiones vinculadas difieren. Además, no es solo -march; También hay -mtune/-mcpu y -mfpmatch(y posiblemente otros). Algunos de estos pueden tener diferentes valores predeterminados en diferentes instalaciones, por lo que es posible que deba forzar explícitamente el peor caso posible para sus máquinas; hacerlo podría reducir significativamente el rendimiento, especialmente si vuelve a i386 sin sse. Y, por supuesto, si uno de sus cpus es un ARM y el otro un i686 ...
rici
1
Además, ¿es GCC uno de los compiladores en cuestión que agrega una marca de tiempo a los binarios?
David
@david: afaik, no.
rici
8

Lo que está preguntando es "es el resultado determinista ". Si compiló el programa una vez, inmediatamente lo compiló nuevamente, probablemente terminaría con el mismo archivo de salida. Sin embargo, si algo cambió, incluso un pequeño cambio, especialmente en un componente que utiliza el programa compilado, entonces la salida del compilador también podría cambiar.

headkase
fuente
2
Muy buen punto de hecho. Este artículo tiene algunas observaciones muy interesantes. En particular, la compilación con GCC puede no ser determinista con respecto a las entradas en ciertos casos, por ejemplo, en cómo manipula las funciones en espacios de nombres anónimos, para lo cual utiliza un generador de números aleatorios internamente. Para obtener determinismo en este caso particular, proporcione una semilla aleatoria inicial especificando la opción -frandom-seed=string.
ack
7

¿Recompilar un programa produce un binario idéntico bit por bit?

Para todos los compiladores? No. El compilador de C #, al menos, no está permitido.

Eric Lippert tiene un desglose muy completo sobre por qué la salida del compilador no es determinista .

[E] l compilador C # por diseño nunca produce el mismo binario dos veces. El compilador de C # incorpora un GUID recién generado en cada ensamblaje, cada vez que lo ejecuta, asegurando así que no haya dos ensamblajes idénticos bit por bit. Para citar de la especificación CLI:

La columna Mvid indexará un GUID [...] único que identifica esta instancia del módulo. [...] El Mvid debe generarse nuevamente para cada módulo [...] Mientras que el [tiempo de ejecución] no utiliza el Mvid, otras herramientas (como depuradores [...]) se basan en el hecho de que el Mvid casi siempre difiere de un módulo a otro.

Aunque es específico de una versión del compilador de C #, muchos puntos del artículo se pueden aplicar a cualquier compilador.

En primer lugar, suponemos que siempre obtenemos la misma lista de archivos cada vez, en el mismo orden. Pero eso en algunos casos depende del sistema operativo. Cuando dice "csc * .cs", el orden en que el sistema operativo ofrece la lista de archivos coincidentes es un detalle de implementación del sistema operativo; el compilador no clasifica esa lista en un orden canónico.

ta.speot.is
fuente
No debería ser difícil hacer que la construcción sea reproducible (aparte de algunos campos fácilmente descartados, como el tiempo de compilación y el GUID de ensamblaje). Por ejemplo, ordenar los archivos de entrada en un orden canónico es una línea. Incluso ese GUID podría ser un hash del resto del ensamblado en lugar de ser generado recientemente.
CodesInChaos
Supongo que te refieres al compilador de Microsoft C #, ¿o es un requisito de la especificación?
David
@David La especificación CLI lo requiere. El compilador C # de Mono tendría que hacer lo mismo. Lo mismo para cualquier compilador de VB .NET.
ta.speot.is
44
El estándar ECMA no tiene que tener marcas de tiempo o diferencias de MVID. Sin ellos, al menos es posible para binarios idénticos en C #. Por lo tanto, la razón principal es una decisión de diseño cuestionable y no una restricción técnica real.
Shiv
7
  • -frandom-seed=123controla algo de aleatoriedad interna del CCG. man gccdice:

    Esta opción proporciona una semilla que GCC usa en lugar de números aleatorios para generar ciertos nombres de símbolos que tienen que ser diferentes en cada archivo compilado. También se usa para colocar sellos únicos en los archivos de datos de cobertura y los archivos de objetos que los producen. Puede usar la opción -frandom-seed para producir archivos de objetos reproduciblemente idénticos.

  • __FILE__: coloca la fuente en una carpeta fija (por ejemplo /tmp/build)

  • para __DATE__, __TIME__, __TIMESTAMP__:
    • libfaketime: https://github.com/wolfcw/libfaketime
    • anular esas macros con -D
    • -Wdate-timeo -Werror=date-time: una advertencia o un error si cualquiera de los dos __TIME__, __DATE__o __TIMESTAMP__se está utilizado. El kernel 4.4 de Linux lo usa por defecto.
  • use la Dbandera con ar, o use https://github.com/nh2/ar-timestamp-wiper/tree/master para limpiar sellos
  • -fno-guess-branch-probability: las versiones manuales anteriores dicen que es una fuente de no determinismo, pero ya no . No estoy seguro si esto está cubierto -frandom-seedo no.

El proyecto de compilaciones de Debian Reproducible intenta estandarizar los paquetes de Debian byte a byte, y recientemente recibió una subvención de la Fundación Linux . Eso incluye más que solo compilación, pero debería ser de interés.

Buildroot tiene una BR2_REPRODUCIBLEopción que puede dar algunas ideas a nivel de paquete, pero está lejos de completarse en este momento.

Hilos relacionados:

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
fuente
3

El proyecto https://reproducible-builds.org/ tiene que ver con esto, y está tratando de hacer que la respuesta a su pregunta "no, no diferirán" en tantos lugares como sea posible. NixOS y Debian ahora tienen más del 90% de reproducibilidad para sus paquetes.

Si compila un binario, y yo compilo un binario, y son idénticos bit por bit, entonces puedo estar seguro de que el código fuente y las herramientas son los que determinan la salida, y que usted no se escabulló en algunos código troyano en el camino.

Si combinamos la reproducibilidad con bootstrappability de una fuente legible por humanos, como http://bootstrappable.org/ está trabajando en hacerlo, obtenemos un sistema determinado desde cero por una fuente legible por humanos, y solo entonces estamos en un punto donde Podemos confiar en que sabemos lo que está haciendo el sistema.

clacke
fuente
1
Enlaces geniales. Soy un fanático de Buildroot, pero si alguien me da una configuración de arco cruzado de Nix ARM que arranca en QEMU, estaré feliz :-)
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
No mencioné a Guix porque no sé dónde encontrar sus números, pero estaban antes de NixOS en el tren de reproducibilidad con herramientas de verificación y demás, así que estoy seguro de que están en igualdad de condiciones o mejor.
clacke
2

Yo diría que NO, no es 100% determinista. Anteriormente trabajé con una versión de GCC que genera binarios de destino para el procesador Hitachi H8.

No es un problema con la marca de tiempo. Incluso si se ignora el problema de la marca de tiempo, la arquitectura específica del procesador puede permitir que la misma instrucción se codifique de 2 maneras ligeramente diferentes donde algunos bits pueden ser 1 o 0. Mi experiencia previa muestra que los binarios generados fueron la misma la MAYORÍA del tiempo pero ocasionalmente el gcc generaría binarios con un tamaño idéntico pero algunos de los bytes son diferentes en solo 1 bit, por ejemplo, 0XE0 se convierte en 0XE1.

JavaMan
fuente
¿Y eso condujo a un comportamiento diferente o "problemas serios"?
Florian Straub
1

En general, no. Los compiladores más sofisticados incluirán el tiempo de compilación en el módulo objeto. Incluso si tuviera que reiniciar el reloj, tendría que ser muy preciso con respecto a cuándo inició la compilación (y luego esperar que los accesos al disco, etc., tengan la misma velocidad que antes).

Daniel R Hicks
fuente