2009-09-15 9 views
16

Primero le daré un poco de antecedentes sobre por qué estoy haciendo esta pregunta:¿Cómo siempre producir byte por byte .exe idéntico en la reconstrucción de la aplicación C#?

Actualmente estoy trabajando en una industria estrictamente regulada y, como tal, nuestro código es cuidadosamente revisado por oficiales. casas de prueba. Estas casas de prueba esperan poder construir el código y generar un .exe o .dll que sea EXACTAMENTE el mismo todas y cada una de las veces (¡sin cambiar ningún código obviamente!). Revisan el MD5 y el SHA1 de los ejecutables que crean para garantizar esto.

Hasta este momento, he estado codificando predominantemente en C++, donde (después de algunas modificaciones en la configuración de proyectos) conseguí que los proyectos se reconstruyeran consistentemente con el mismo MD5/SHA1. Ahora estoy usando C# en un proyecto y estoy teniendo grandes dificultades para hacer que los MD5 coincidan después de una reconstrucción. Soy consciente de que hay "Sellos de tiempo" en el encabezado PE del archivo, y se han borrado a 0. También sé que hay un GUID para el .exe, que de nuevo se ha borrado a 00 00 00 ... etc. Sin embargo, los archivos aún no coinciden.

Estoy usando CFF Explorer para ver y editar el encabezado PE para eliminar las marcas de fecha y hora. Después de usar una herramienta de comparación binaria, solo hay 2 bloques de bytes en el .exe que son diferentes (ambos muy pequeños).

Uno de los bloques inconsistentes aparece solo antes de algún código binario, que en ASCII detalla la ruta del archivo *Project*\obj\Release\xxx.pdb.

EDIT: Ahora se sabe que es el GUID del archivo * .pdb, sin embargo, ¿todavía no sé si puedo modificarlo sin causar ningún error?

El otro bloque aparece en medio de lo que parecen ser nombres de funciones, es decir. (Una sección típica) AssemblyName.GetName.Version.get_Version.System.IO.Ports.SerialPort.Parity.Byte.<PrivateImplementationDetails>{

entonces el diferente bloque de código:

4A134ACE-D6A0-461B-A47C-3A4232D90816

seguido por:.

"} .ValueType .__ StaticArrayInitTypeSize = 7 $$ method0x60000ab-1.RuntimeFieldHandle.InitializeArray `... etc.

¡Cualquier idea o sugerencia sería bienvenida!

Respuesta

5

Actualización: Roslyn parece tener un indicador de compilador /feature:deterministic para compilaciones reproducibles, aunque it's not 100% working yet.


Debería poder deshacerse del GUID de depuración deshabilitando la generación de PDB. De lo contrario, establecer el GUID en cero está bien; solo los depuradores miran esa sección (ya no podrá depurar el ensamblado, pero aún así debería funcionar bien).

Los detalles de PrivateImplementation son un poco más difíciles: son clases internas de ayuda generadas por el compilador para ciertas construcciones de lenguaje (inicializadores de matriz, instrucciones de conmutación que utilizan cadenas, etc.). Debido a que solo se usan internamente, el nombre de la clase realmente no importa, por lo que podría asignarles un número en ejecución.

Me gustaría hacer esto yendo a través de la corriente de metadatos #STRINGS y la sustitución de todas las cadenas de la forma "< PrivateImplementationDetails> {GUID}" con "< PrivateImplementationDetails> {número consecutivo, acolchado de misma longitud que un GUID}".

La secuencia de metadatos #Strings es simplemente la lista de cadenas utilizadas por los metadatos, codificadas en UTF-8 y separadas por \ 0; por lo que encontrar y reemplazar los nombres debería ser fácil una vez que sepa dónde está la secuencia #Strings dentro del archivo ejecutable.

Lamentablemente, los "encabezados de flujo de metadatos" que contienen esta información están bastante enterrados dentro del formato de archivo. Tendrá que comenzar en el Encabezado opcional de NT, encontrar el puntero al Encabezado de tiempo de ejecución de CLI, resolverlo en una posición de archivo usando la tabla de sección PE (es un RVA, pero necesita una posición dentro del archivo), luego vaya a la raíz de metadatos y leer los encabezados de la secuencia.

+0

Bien, bien si el GUID se puede deshacer deshabilitando la generación de PDB (o borrándola a todos los 0) esa es la diferencia número 1 resuelto. La diferencia número 2 parece ser mucho más difícil de resolver; ¿Estás diciendo que tengo que pasar por la IL y cambiar el valor allí? ¿O accede al archivo compliled * .exe de forma manual y manual para configurar los bytes? – Siyfion

+0

Bueno, debido a mi trabajo en la herramienta de inyección de recursos, habría elegido la solución de parche * .exe. También se debe hacer una ida y vuelta de ILDASM/ILASM para reemplazar el nombre de la clase. – Daniel

+0

¿Alguna posibilidad de que liberes esta herramienta de parche en el futuro cercano? ;) – Siyfion

2

No estoy seguro de esto, pero solo un pensamiento: ¿está utilizando algún tipo anónimo para el cual el compilador podría generar nombres detrás de escena, que podrían ser diferentes cada vez que se ejecuta el compilador? Solo una posibilidad que se me ocurrió. Probablemente uno para Jon Skeet ;-)

Actualización: También podría utilizar el Reflector addins para comparar y desmontar.

+0

No, no usa ningún tipo anónimo en la aplicación, ¡aunque fue una buena idea! ;) – Siyfion

+0

En cuanto a Reflector para comparación, desafortunadamente no soy yo quien elige la herramienta que usan para comparar, tiene que ser una coincidencia MD5 exacta :( – Siyfion

1

Eche un vistazo a las respuestas de la pregunta this. Especialmente en el enlace externo provisto en el 3er.

EDIT:

De hecho, me wantetd para enlazar a this artículo.

+0

Ese es un enlace a una herramienta diff para comparar archivos binarios. –

+0

No puedo encuentre una versión de Dumpbin.exe para usar en cualquier lugar, pero aparte de eso, parece que las únicas diferencias deberían ser la fecha y la hora (que he borrado a 0), el GUID (que he borrado a 00 00). .etc), la versión de ensamblado (¿cuál debería ser el mismo?) y un hash fuerte (que debería ser el mismo si todo lo demás es así). Por lo tanto, creo que el siguiente paso es usar Ildasm.exe para intentar figurar ¡Fuera si el código de MSIL difiere !? – Siyfion

+0

Disculpa la confusión. Edité mi publicación para señalar el artículo correcto. Por favor, mira allí para obtener más información. –

2

En relación con el problema de GUI PDB, si especifica que no se generará un PDB en la compilación para compilaciones de Release, ¿el binario aún contiene el GUID del sistema de archivos de PDB?

Para desactivar la generación de AP:

  1. Haga clic con el proyecto en el Explorador de soluciones y seleccione Propiedades.
  2. En el menú de la izquierda, selecciona Build.
  3. Asegúrese de que la selección de Configuración sea Liberar (aún querrá un PDB para la depuración).
  4. Haga clic en el botón Avanzado en la parte inferior derecha.
  5. En Salida/Información de depuración, seleccione Ninguna.

Si está creando desde la consola, use/debug- para obtener el mismo resultado.

+0

Voy a dar una oportunidad mañana ... – Siyfion

+0

Solo estoy usando Visual C# Express en este momento para fines de evaluación, ¿sabes si puedo desconectar la generación * .pdb en esta versión? – Siyfion

+0

Puedes. Agregaré instrucciones, ya que la opción está como enterrada. –

0

Dijiste que después de algunos retoques de proyectos conseguías que las aplicaciones de C++ se compilaran repetidamente con los mismos valores SHA1/MD5. Estoy en el mismo barco que tú en una industria con un laboratorio de pruebas de terceros que necesita reconstruir exactamente los mismos ejecutables repetidamente.

Al investigar cómo hacer que esto suceda en VS2005, encontré su publicación aquí. ¿Podría compartir los ajustes del proyecto que realizó para que las aplicaciones de C++ crezcan con los mismos valores SHA1/MD5 de forma consistente? Sería de gran ayuda para mí y quizás para otros que compartan este requisito.

+1

Por supuesto, ¡aunque esto está fuera de mi cabeza! En el modo de liberación haga lo siguiente: - Desactivar la generación del archivo de manifiesto (Solución Properties-> Linker-> archivo de manifiesto) O - Cambiar la configuración de manifiesto (Solución Properties-> Manifiesto Herramientas-> Entrada y Salida) para que "Embed Manifest" esté configurado a "No". También asegúrese de que toda la información de depuración esté desactivada para la versión de lanzamiento. Luego, solo necesita eliminar TimeAndDateStamp del encabezado del archivo PE. (Intente buscar en Google "CFF Explorer") – Siyfion

+0

Ack ... ¿manipulación manual del encabezado del archivo? Habla sobre un desastre de error humano esperando a suceder. ¿Conoce una utilidad de línea de comandos que pueda hacer esto para que sea automatizada, confiable y repetible? – Tom

0

Utilice ildasm.exe para desmontar completamente ambos programas y comparar el IL.Luego puede "limpiar" el código utilizando métodos basados ​​en texto y (de forma predecible) volver a compilarlo.