¿Cómo forzar un locking en C, es una desviación de la referencia de un puntero nulo de manera (bastante) portátil?

Estoy escribiendo mi propio corredor de prueba para mi proyecto actual. Una característica (que probablemente sea bastante común con los corredores de prueba) es que cada caso de prueba se ejecuta en un proceso secundario, por lo que el corredor de prueba puede detectar e informar correctamente un caso de prueba que falla.

También quiero probar el propio corredor de pruebas, por lo tanto, un caso de prueba tiene que forzar un locking. Sé que “estrellarse” no está cubierto por el estándar C y solo puede suceder como resultado de un comportamiento indefinido. Entonces, esta pregunta es más sobre el comportamiento de las implementaciones del mundo real.

Mi primer bash fue simplemente desreferenciar un puntero nulo :

int c = *((int *)0); 

Esto funcionó en una comstackción de depuración en GNU / Linux y Windows, pero no se bloqueó en una comstackción de lanzamiento porque la variable c no utilizada se optimizó, así que agregué

 printf("%d", c); // to prevent optimizing away the crash 

Y pensé que estaba asentado. Sin embargo, probar mi código con clang lugar de gcc reveló una sorpresa durante la comstackción:

  [CC] obj/x86_64-pc-linux-gnu/release/src/test/test/test_s.o src/test/test/test.c:34:13: warning: indirection of non-volatile null pointer will be deleted, not trap [-Wnull-dereference] int c = *((int *)0); ^~~~~~~~~~~ src/test/test/test.c:34:13: note: consider using __builtin_trap() or qualifying pointer with 'volatile' 1 warning generated. 

Y de hecho, el testcase comstackdo no se estrelló.

Entonces, seguí el consejo de la advertencia y ahora mi caso de prueba se ve así:

 PT_TESTMETHOD(test_expected_crash) { PT_Test_expectCrash(); // crash intentionally int *volatile nptr = 0; int c = *nptr; printf("%d", c); // to prevent optimizing away the crash } 

Esto solucionó mi problema inmediato, el testcase “funciona” (también conocido como “crash”) con gcc y clang .

Supongo que debido a que la anulación de la referencia del puntero nulo es un comportamiento indefinido, clang es libre de comstackr mi primer código en algo que no falla. El calificador volatile elimina la capacidad de estar seguro en el momento de la comstackción de que esto realmente se eliminará como nulo.

Ahora mis preguntas son:

  • ¿Este código final garantiza que la anulación nula en realidad ocurre en el tiempo de ejecución?
  • ¿La anulación de referencia es, de hecho, una forma bastante portátil de bloquearse en la mayoría de las plataformas?

No confiaría en ese método como robusto si fuera tú.

¿No puede utilizar abort() , que forma parte del estándar C y está garantizado para provocar un evento de finalización anormal del progtwig?

La respuesta referente a abort() fue genial, realmente no pensé en eso y es una manera perfectamente portátil de forzar una terminación anormal del progtwig .

Al intentarlo con mi código, encontré que msvcrt (el tiempo de ejecución de C de Microsoft) implementa abort() de una manera especial de chat, da como resultado lo siguiente a stderr :

Esta aplicación ha solicitado al Runtime que termine de una manera inusual. Póngase en contacto con el equipo de asistencia de la aplicación para obtener más información.

Eso no es tan agradable, al menos desordena innecesariamente la salida de una ejecución de prueba completa. Así que eché un vistazo a __builtin_trap() que también se hace referencia en la advertencia de __builtin_trap() . Resulta que esto me da exactamente lo que estaba buscando:

El generador de códigos LLVM traduce __builtin_trap () a una instrucción de captura si es compatible con la ISA de destino. De lo contrario, el builtin se traduce en una llamada para abortar.

También está disponible en gcc partir de la versión 4.2.4:

Esta función hace que el progtwig salga de forma anormal. GCC implementa esta función mediante el uso de un mecanismo dependiente del objective (como ejecutar intencionalmente una instrucción ilegal) o llamando al abortar.

Como esto hace algo similar a un choque real, lo prefiero sobre un simple abort() . Para el respaldo, sigue siendo una opción tratar de realizar su propia operación ilegal como la anulación de puntero nulo, pero solo agregue una llamada a abort() en caso de que el progtwig de alguna manera llegue allí sin fallar.

Así que, en general, la solución se ve así, probando una versión mínima de GCC y usando la macro mucho más útil __has_builtin() proporcionada por Clang:

 #undef HAVE_BUILTIN_TRAP #ifdef __GNUC__ # define GCC_VERSION (__GNUC__ * 10000 \ + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) # if GCC_VERSION > 40203 # define HAVE_BUILTIN_TRAP # endif #else # ifdef __has_builtin # if __has_builtin(__builtin_trap) # define HAVE_BUILTIN_TRAP # endif # endif #endif #ifdef HAVE_BUILTIN_TRAP # define crashMe() __builtin_trap() #else # include  # define crashMe() do { \ int *volatile iptr = 0; \ int i = *iptr; \ printf("%d", i); \ abort(); } while (0) #endif // [...] PT_TESTMETHOD(test_expected_crash) { PT_Test_expectCrash(); // crash intentionally crashMe(); } 

Puedes escribir la memoria en lugar de leerla.

 *((int *)0) = 0; 

No, eliminar la referencia a un puntero NULO no es una forma portátil de bloquear un progtwig. Es un comportamiento indefinido, lo que significa que simplemente no tiene garantías de lo que sucederá.

A medida que sucede, en su mayor parte, en cualquiera de los tres sistemas operativos principales que se utilizan hoy en día en las computadoras de escritorio, el hecho de que MacOS, Linux y Windows NT (*) desreferan un puntero NULO provocará que su progtwig se bloquee de inmediato.

Dicho esto: “El peor resultado posible de un comportamiento indefinido es que haga lo que estaba esperando”.

Pongo una estrella a propósito junto a Windows NT, porque bajo Windows 95/98 / ME, puedo crear un progtwig que tenga la siguiente fuente:

 int main() { int *pointer = NULL; int i = *pointer; return 0; } 

que se ejecutará sin estrellarse. Compílalo como un archivo pequeño en modo .COM archivos bajo DOS de 16 bits, y estarás bien.

Ditto ejecutando la misma fuente con casi cualquier comstackdor de C en CP / M.

Ditto ejecutando eso en algunos sistemas embebidos. No lo he probado en un Arduino, pero no quisiera apostar de ninguna manera en el resultado. Sé con certeza que si hubiera un comstackdor de C disponible para los sistemas 8051 en los que me corté los dientes, ese progtwig funcionaría bien con esos.

El siguiente progtwig debería funcionar. Sin embargo, podría causar algún daño colateral.


 #include  void crashme( char *str) { char *omg; for(omg=strtok(str, "" ); omg ; omg=strtok(NULL, "") ) { strcat(omg , "wtf"); } *omg =0; // always NUL-terminate a NULL string !!! } int main(void) { char buff[20]; // crashme( "WTF" ); // works! // crashme( NULL ); // works, too crashme( buff ); // Maybe a bit too slow ... return 0; }