c compiler-construction deterministic embedded

C(o cualquier) compilador de rendimiento determinista



compiler-construction deterministic (22)

Mientras trabajaba en un proyecto reciente, un cliente QA representitive me visitó y me hizo una pregunta que antes no había considerado realmente:

¿Cómo sabe que el compilador que está utilizando genera un código de máquina que coincide exactamente con la funcionalidad del código c y que el compilador es completamente determinista?

A esta pregunta no tuve absolutamente ninguna respuesta ya que siempre he tomado el compilador por sentado. Toma código y arroja código de máquina. ¿Cómo puedo ir y probar que el compilador no está realmente agregando funcionalidades que no le he pedido? o incluso más peligrosamente la implementación del código de una manera ligeramente diferente a la que espero?

Soy consciente de que esto no es realmente un problema para todos, y de hecho la respuesta podría ser ... "estás por el barril y lidiar con eso". Sin embargo, cuando trabaja en un entorno incrustado, confía en su compilador implícitamente. ¿Cómo puedo demostrarme a mí mismo y a la garantía de calidad que estoy en lo cierto al hacerlo?


Prueba la prueba unitaria.

Si eso no es suficiente, use diferentes compiladores y compare los resultados de las pruebas de su unidad. Compare las salidas de Strace, ejecute sus pruebas en una VM, mantenga un registro de las E / S de red y de disco, y luego compárelas.

O proponga escribir su propio compilador y decirles cuánto costará.


  1. Cambiar el nivel de optimización del compilador cambiará la salida.
  2. Pequeños cambios en una función pueden hacer que el compilador quede en línea o deje de alinear una función.
  3. Los cambios en el compilador (versiones de gcc por ejemplo) pueden cambiar la salida
  4. Ciertas funciones de biblioteca pueden ser intrínsecas (es decir, emitir un ensamblaje optimizado) mientras que otras no lo son.

La buena noticia es que para la mayoría de las cosas realmente no importa tanto. Donde lo haga, es posible que desee considerar el ensamblaje si realmente importa (por ejemplo, en un ISR).


A veces obtienes cambios de comportamiento cuando solicitas niveles agresivos de optimización.

¿Y la optimización y los números de coma flotante? ¡Olvídalo!


Creo que es posible reducir este problema al problema de detención de alguna manera.

El problema más obvio es que si utiliza algún tipo de programa para analizar el compilador y su determinismo, ¿cómo sabe que su programa se compila correctamente y produce el resultado correcto?

Sin embargo, si está usando otro compilador "seguro", no estoy seguro. Lo que estoy seguro es que escribir un compilador desde cero probablemente sea un trabajo más fácil.


Lo máximo que puede certificar fácilmente es que está utilizando un compilador no protegido del proveedor X. Si no confían en el proveedor X, es su problema (si X es razonablemente confiable). Si no confían en ningún proveedor de compiladores, entonces son totalmente irrazonables.

Respondiendo a su pregunta: me aseguro de estar usando un compilador no protegido de X a través de estos medios. X tiene buena reputación, además tengo un buen conjunto de pruebas que muestran que nuestra aplicación se comporta como se esperaba.

Todo lo demás está comenzando a abrir la lata de gusanos. Tienes que parar en algún lado, como dice Rob.


Para la seguridad, las agencias de certificación de aplicaciones integradas obligatorias deben satisfacer el requisito de "probado en uso" para el compilador. Normalmente hay ciertos requisitos (como "horas de operación") que deben cumplirse y probarse mediante documentación detallada. Sin embargo, la mayoría de la gente no puede o no quiere cumplir con estos requisitos porque puede ser muy difícil, especialmente en su primer proyecto con un nuevo objetivo / compilador.

Otro enfoque es, básicamente, NO confiar en absoluto en la salida del compilador. Cualquier compilador e incluso las deficiencias dependientes del idioma (Apéndice G de la norma C-90, ¿alguien?) Deben estar cubiertas por un conjunto estricto de análisis estático, pruebas de unidad y cobertura, además de las pruebas funcionales posteriores.

Un estándar como MISRA-C puede ayudar a restringir la entrada al compilador a un subconjunto "seguro" del lenguaje C. Otro enfoque es restringir la entrada a un compilador a un subconjunto de un idioma y probar cuál es la salida para todo el subconjunto. Si nuestra aplicación solo está compuesta de componentes del subconjunto, se supone que se conocerá cuál será la salida del compilador. Lo general pasa por "calificación del compilador".

El objetivo de todo esto es poder responder a la pregunta del representante de control de calidad con "No confiamos únicamente en el determinismo del compilador, pero esta es la forma en que lo demostramos ...".


Puede aplicar ese argumento en cualquier nivel: ¿confía en las bibliotecas de terceros? ¿Confías en el sistema operativo? ¿Confías en el procesador?

Un buen ejemplo de por qué esto puede ser una preocupación válida, por supuesto, es cómo Ken Thompson puso una puerta trasera en el programa original de "inicio de sesión" ... y modificó el compilador de C, de modo que incluso si recompilaba el inicio de sesión todavía tenía la puerta trasera. Vea esta publicación para más detalles.

Se han planteado preguntas similares sobre los algoritmos de encriptación: ¿cómo sabemos que no hay una puerta trasera en DES para que la NSA pueda husmear?

Al final, debes decidir si confías en la infraestructura que estás creando lo suficiente como para no preocuparte por ella, de lo contrario, ¡tendrás que empezar a desarrollar tus propios chips de silicio!


Si le preocupa el código de máquina inesperado que no produce resultados visibles, la única forma es, probablemente, ponerse en contacto con el proveedor del compilador para obtener algún tipo de certificación que satisfaga a su cliente.

De lo contrario, lo sabrá de la misma manera que usted sabe acerca de los errores en su código: las pruebas.

El código de máquina de los compiladores modernos puede ser muy diferente y totalmente incomprensible para los humanos insignificantes.


Todo se reduce a la confianza. ¿Confía su cliente en cualquier compilador? Úselo, o al menos compare el código de salida entre el suyo y el de ellos.

Si no confían en ninguno, ¿hay una implementación de referencia para el idioma? ¿Podrías convencerlos de que confíen en eso? Luego compare el suyo con la referencia o use la referencia.

Todo esto suponiendo que realmente verifique el código real que obtiene del proveedor / proveedor y que verifique que el compilador no haya sido manipulado, lo que debería ser el primer paso.

De todos modos, esto deja la pregunta sobre cómo verificarías, sin tener referencias, un compilador, desde cero. Eso ciertamente parece mucho trabajo y requiere una definición del lenguaje, que no siempre está disponible, a veces la definición es el compilador.


Usted sabe por las pruebas. Cuando pruebas, estás probando el código y el compilador.

Descubrirá que las probabilidades de que usted o el compilador hayan cometido un error son mucho menores que las probabilidades de que cometiera un error si escribía el programa en cuestión en algún lenguaje ensamblador.


Hay trajes de validación de compiladores disponibles.
El que recuerdo es "Perenne" .

Cuando trabajé en un compilador de C para un procesador SOC incrustado, tuvimos que validar el compilador frente a este y otros dos trajes de validación (que me olvidé del nombre). La validación del compilador a un cierto nivel de conformidad con estos trajes de prueba fue parte del contrato.


Para la mayoría del desarrollo de software (piense en aplicaciones de escritorio) la respuesta es probablemente que usted no sabe y no le importa.

En sistemas críticos para la seguridad (piense en plantas de energía nuclear y aviónica comercial) le importa y las agencias reguladoras le exigirán que lo demuestre. En mi experiencia, puedes hacer esto de dos maneras:

  1. Use un compilador calificado, donde "calificado" significa que ha sido verificado de acuerdo con las normas establecidas por la agencia reguladora.
  2. Realizar análisis de código de objeto. Básicamente, compila una pieza de código de referencia y luego analiza manualmente la salida para demostrar que el compilador no ha insertado ninguna instrucción que no pueda rastrearse hasta su código fuente.

Incluso un compilador calificado o certificado puede producir resultados no deseados. Mantenga su código simple y pruebe, pruebe, pruebe. Eso o caminar a través del código de máquina a mano sin permitir ningún error humano. Más el sistema operativo o cualquier entorno en el que se esté ejecutando (preferiblemente ningún sistema operativo, solo su programa).

Este problema se ha resuelto en entornos de misión crítica desde que comenzaron el software y los compiladores. Como muchos de los otros que han respondido también saben. Cada industria tiene sus propias reglas, desde los compiladores certificados hasta el estilo de programación (siempre debe programar de esta manera, nunca use esto o aquello u otro), muchas pruebas y revisión por pares. Verificando cada ruta de ejecución, etc.

Si no estás en una de esas industrias, obtienes lo que obtienes. Un programa comercial en un sistema operativo COTS en hardware COTS. Fallará, eso es una garantía.


... confías en tu compilador implícitamente

Dejarás de hacerlo la primera vez que te encuentres con un error del compilador. ;-)

Pero en definitiva, esto es para lo que son las pruebas. En primer lugar, no importa para su régimen de prueba cómo ingresó el error en su producto, lo único que importa es que no pasó su extenso régimen de prueba.


¿Cómo sabe que el compilador que está utilizando genera un código de máquina que coincide exactamente con la funcionalidad del código c y que el compilador es completamente determinista?

No, es por eso que prueba el binario resultante, y por qué se asegura de enviar el mismo binario con el que realizó la prueba. Y por qué cuando realiza cambios de software ''menores'', realiza la prueba de regresión para asegurarse de que no se rompió ninguna de las funciones anteriores.

El único software que he certificado es aviónica. La certificación FAA no es lo suficientemente rigurosa como para demostrar que el software funciona correctamente, mientras que al mismo tiempo te obliga a saltar una cierta cantidad de aros. El truco es estructurar su ''proceso'' para que mejore la calidad tanto como sea posible, con tan poco salto de aro como sea posible. Entonces, cualquier cosa que sepa que no tiene valor y que en realidad no encontrará errores, probablemente pueda evitarla. Y cualquier cosa que sepa que debe hacer porque encontrará errores que la FAA no pide explícitamente, su mejor opción es retuercer las palabras hasta que parezca que le está dando a la FAA / a su personal de control de calidad lo que pidieron.

Esto en realidad no es tan deshonesto como lo he hecho sonar, en general, la FAA se preocupa más por usted que es concienzudo y seguro de que está tratando de hacer un buen trabajo, que por lo que hace exactamente.


Algunas municiones intelectuales pueden encontrarse en Crosstalk, una revista para ingenieros de software de defensa. Esta pregunta es el tipo de cosas en las que pasan muchas horas despiertas. http://www.stsc.hill.af.mil/crosstalk/2006/08/index.html (Si puedo encontrar mis notas antiguas de un proyecto anterior, volveré aquí ...)


Nunca se puede confiar plenamente en el compilador, incluso en los altamente recomendados. Podrían lanzar una actualización que tenga un error, y tu código compilará lo mismo. Este problema se agrava cuando se actualiza el código anterior con el compilador erróneo, se realizan pruebas y se envían las mercancías solo para que el cliente le llame 3 meses después con un problema.

Todo vuelve a las pruebas, y si hay algo que he aprendido es probar a fondo después de cualquier cambio no trivial. Si el problema parece imposible de encontrar, eche un vistazo al ensamblador compilado y compruebe que está haciendo lo que debería estar haciendo.

En varias ocasiones he encontrado errores en el compilador. Una vez hubo un error donde las variables de 16 bits se incrementarían pero sin arrastre y solo si la variable de 16 bits formaba parte de una estructura externa definida en un archivo de encabezado.


Obtienes el que escribió Dijkstra.


Seleccione un compilador verificado formalmente, como el compilador Compcert C.


Si le preocupan los errores maliciosos en el compilador, una recomendación (IIRC, un requisito de la NSA para algunos proyectos) es que el binario del compilador sea anterior a la escritura del código. Al menos entonces sabes que nadie ha agregado errores dirigidos a tu programa.


Bueno ... no puedes decir simplemente que confías en la salida de tu compilador, especialmente si trabajas con código incrustado. No es difícil encontrar discrepancias entre el código generado al compilar el mismo código con diferentes compiladores. Este es el caso porque el estándar C en sí mismo es demasiado flojo. Muchos detalles pueden ser implementados de manera diferente por diferentes compiladores sin romper el estándar. ¿Cómo lidiamos con esto? Evitamos construcciones dependientes del compilador siempre que sea posible. Podemos manejarlo eligiendo un subconjunto más seguro de C como Misra-C, tal como lo mencionó anteriormente el usuario cschol. Pocas veces tengo que inspeccionar el código generado por el compilador, pero a veces también me ha pasado. Pero, en última instancia, confía en sus pruebas para asegurarse de que el código se comporte como debe.

¿Hay una mejor opción por ahí? Algunas personas dicen que sí. La otra opción es escribir tu código en SPARK / Ada . Nunca escribí el código en SPARK, pero tengo entendido que aún tendrías que vincularlo con las rutinas escritas en C que tratarían las cosas del "metal desnudo". La belleza de SPARK / Ada es que está absolutamente garantizado de que el código generado por cualquier compilador siempre será el mismo. Sin ambigüedad alguna. Además de eso, el lenguaje le permite anotar el código con explicaciones sobre cómo se debe comportar el código. El conjunto de herramientas SPARK usará estas anotaciones para probar formalmente que el código escrito sí hace lo que las anotaciones han descrito. Entonces me dijeron que para sistemas críticos, SPARK / Ada es una apuesta bastante buena. Aunque nunca lo intenté yo mismo.


No está seguro de que el compilador haga exactamente lo que espera. La razón es, por supuesto, que un compilador es una pieza de software y, por lo tanto, susceptible a errores.

Los escritores de compiladores tienen la ventaja de trabajar desde una especificación de alta calidad, mientras que el resto de nosotros tenemos que descubrir qué hacemos a medida que avanzamos. Sin embargo, las especificaciones del compilador también tienen errores y partes complejas con interacciones sutiles. Por lo tanto, no es exactamente trivial averiguar qué debe hacer el compilador.

Aún así, una vez que decida qué cree que significa la especificación del idioma, puede escribir una prueba buena, rápida y automatizada para cada matiz. Aquí es donde la escritura del compilador tiene una gran ventaja sobre la escritura de otros tipos de software: en las pruebas. Cada error se convierte en un caso de prueba automatizado, y el conjunto de pruebas puede ser muy minucioso. Los proveedores de compiladores tienen mucho más presupuesto para invertir en verificar la corrección del compilador que usted (ya tiene un trabajo diario, ¿no?).

¿Qué significa esto para ti? Significa que debes estar abierto a las posibilidades de errores en tu compilador, pero es probable que no encuentres ninguno.

Escogería un proveedor de compiladores que probablemente no cerraría en el corto plazo, que tiene un historial de alta calidad en sus compiladores, y que ha demostrado su capacidad para reparar (parchar) sus productos. Los compiladores parecen ser más correctos con el tiempo, por lo que elegiría uno que haya durado una década o dos.

Enfoca tu atención en obtener tu código correcto. Si es claro y simple , cuando aciertes una falla del compilador, no tendrás que pensar mucho para decidir dónde está el problema. Escriba buenas pruebas unitarias , lo que garantizará que su código haga lo que espera que haga.