¿Cuál es la forma más simple de conformar un estándar para producir un Segfault en C?


Creo que la pregunta lo dice todo. Un ejemplo que cubra la mayoría de los estándares de C89 a C11 sería útil. Pensé en este, pero supongo que es un comportamiento indefinido:

#include <stdio.h>

int main( int argc, char* argv[] )
{
  const char *s = NULL;
  printf( "%c\n", s[0] );
  return 0;
}

EDITAR:

Como algunos votos pidieron aclaración: Quería tener un programa con un error de programación habitual (el más simple que se me ocurrió fue un error de segmento), que es garantizado (por estándar) para abortar. Esto es un poco diferente a la pregunta de segmentación mínima, a la que no le importa sobre este seguro.

Author: math, 2013-09-24

9 answers

Un fallo de segmentación es un comportamiento definido por la implementación . El estándar no define cómo la implementación debe lidiar con comportamiento indefinido y de hecho la implementación podría optimizar comportamiento indefinido y aún así ser compatible. Para ser claros, el comportamiento definido por la implementaciónes un comportamiento que no está especificado por el estándar, pero la implementación debe documentarse. Comportamiento indefinido {[5] } es código que no es portable o erróneo y cuyo comportamiento es impredecible y por lo tanto no se puede confiar en.

Si nos fijamos en el C99 borrador estándar §3.4.3 comportamiento indefinido que viene bajo la sección Términos, definiciones y símbolos en el párrafo 1 dice ( énfasis mío en el futuro):

Comportamiento, sobre el uso de una construcción de programa no portable o errónea o de datos erróneos, para lo cual este Estándar Internacional impone no requisitos

Y en el párrafo 2 dice:

NOTA El posible comportamiento indefinido abarca desde ignorar la situación completamente con resultados impredecibles, hasta comportarse durante la traducción o ejecución del programa de una manera documentada característica del entorno (con o sin la emisión de un mensaje de diagnóstico), hasta terminar una traducción o ejecución (con la emisión de un mensaje de diagnóstico).

Si, por otro lado, usted simplemente desea un método definido en el estándar que causará un error de segmentación en la mayoría de sistemas similares a Unix, entonces raise(SIGSEGV) debería lograr ese objetivo. Aunque, estrictamente hablando, SIGSEGV se define como sigue:

SIGSEGV un acceso no válido al almacenamiento

Y §7.14 manejo de Señales <signal.h> dice:

Una implementación no necesita generar ninguna de estas señales, excepto como resultado de llamadas explícitas a la función raise . Señales adicionales y punteros a funciones no declarables, con definiciones de macro que comienzan, respectivamente,con las letras SIG y una letra mayúscula o con SIG_ y una letra mayúscula, 219) también pueden ser especificados por la implementación. El conjunto completo de señales, su semántica y su manejo por defecto está definido por la implementación; todos los números de señales serán positivos.

 66
Author: Shafik Yaghmour,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-10 00:26:40

raise() se puede usar para subir un error de segmento:

raise(SIGSEGV);
 97
Author: msam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-24 16:25:43

El estándar solo menciona el comportamiento indefinido. No sabe nada sobre la segmentación de memoria. También tenga en cuenta que el código que produce el error no es conforme al estándar. Su código no puede invocar un comportamiento indefinido y ser conforme al estándar al mismo tiempo.

No obstante, la forma más corta de producir un fallo de segmentación en arquitecturas que generan tales fallos sería:

int main()
{
    *(int*)0 = 0;
}

¿Por qué es seguro que esto produzca un segfault? Porque el acceso a la dirección de memoria 0 es siempre atrapado por el sistema; nunca puede ser un acceso válido (al menos no por código de espacio de usuario.)

Tenga en cuenta, por supuesto, que no todas las arquitecturas funcionan de la misma manera. En algunos de ellos, lo anterior no podía bloquearse en absoluto, sino que producía otro tipo de errores. O la declaración podría estar perfectamente bien, incluso, y la ubicación de memoria 0 es accesible muy bien. Que es una de las razones por las que el estándar no define realmente lo que sucede.

 15
Author: Nikos C.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-24 20:09:14

Un programa correcto no produce una falla de segmento. Y no se puede describir el comportamiento determinista de un programa incorrecto.

Un "fallo de segmentación" es algo que hace una CPU x86. Se obtiene al intentar hacer referencia a la memoria de una manera incorrecta. También puede referirse a una situación en la que el acceso a la memoria causa un error de página (es decir, intentar acceder a la memoria que no está cargada en las tablas de páginas) y el sistema operativo decide que no tenía derecho a solicitar esa memoria. Para desencadenar esas condiciones, necesita programar directamente para su sistema operativo y su hardware. No es nada que esté especificado por el lenguaje C.

 12
Author: Kerrek SB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-25 09:11:31

Si asumimos que no estamos elevando una señal que llama a raise, es probable que el error de segmentación provenga de un comportamiento indefinido. Undefined behavior is undefined and a compiler is free to refuse to translate so no answer with undefined is guaranteed to fail on all implementations. Además, un programa que invoca un comportamiento indefinido es un programa erróneo.

Pero este es el más corto que puedo conseguir que segfault en mi sistema:

main(){main();}

(Compilo con gcc y -std=c89 -O0).

Y por cierto, ¿este programa realmente invoca a undefined bevahior?

 6
Author: ouah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-24 21:05:30

En algunas plataformas, un programa C conforme al estándar puede fallar con un fallo de segmentación si solicita demasiados recursos del sistema. Por ejemplo, asignar un objeto grande con malloc puede parecer que tiene éxito, pero más tarde, cuando se accede al objeto, se bloqueará.

Tenga en cuenta que tal programa no es estrictamente conforme; los programas que cumplen con esa definición tienen que permanecer dentro de cada uno de los límites mínimos de implementación.

Un programa C conforme al estándar no puede producir un error de segmentación de lo contrario, porque las únicas otras formas son a través de un comportamiento indefinido.

La señal SIGSEGV se puede elevar explícitamente, pero no hay un símbolo SIGSEGV en la biblioteca estándar de C.

(En esta respuesta, "conforme al estándar" significa: "Utiliza solo las características descritas en alguna versión del estándar ISO C, evitando comportamientos no especificados, definidos por la implementación o indefinidos, pero no necesariamente confinados a los límites mínimos de implementación.")

 2
Author: Kaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-24 19:08:46

La mayoría de las respuestas a esta pregunta están hablando en torno al punto clave, que es: El estándar C no incluye el concepto de falla de segmentación. (Desde C99 incluye el número de señal SIGSEGV, pero no define ninguna circunstancia donde se entrega esa señal, aparte de raise(SIGSEGV), que como se discutió en otras respuestas no cuenta.)

Por lo tanto, no hay un programa "estrictamente conforme" (es decir, un programa que usa solo construcciones cuyo comportamiento es completamente definido por el estándar C, solo) que se garantiza que causará un error de segmentación.

Las fallas de segmentación están definidas por un estándar diferente, POSIX. Este programa está garantizado para provocar un fallo de segmentación, o el funcionalmente equivalente "bus error" (SIGBUS), en cualquier sistema que es totalmente conforme con POSIX.1-2008 incluyendo la Protección de Memoria y opciones Avanzadas en Tiempo Real, siempre que las llamadas a sysconf, posix_memalign y mprotect tener éxito. Mi lectura de C99 es que este programa tiene definido por la implementación (¡no indefinido!) comportamiento considerando solo ese estándar, y por lo tanto es conformepero no estrictamente conforme.

#define _XOPEN_SOURCE 700
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

int main(void)
{
    size_t pagesize = sysconf(_SC_PAGESIZE);
    if (pagesize == (size_t)-1) {
        fprintf(stderr, "sysconf: %s\n", strerror(errno));
        return 1;
    }
    void *page;
    int err = posix_memalign(&page, pagesize, pagesize);
    if (err || !page) {
        fprintf(stderr, "posix_memalign: %s\n", strerror(err));
        return 1;
    }
    if (mprotect(page, pagesize, PROT_NONE)) {
        fprintf(stderr, "mprotect: %s\n", strerror(errno));
        return 1;
    }
    *(long *)page = 0xDEADBEEF;
    return 0;
}
 1
Author: zwol,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-04 15:29:46

Es difícil definir un método para segmentación falla un programa en plataformas indefinidas. Un error de segmentación es un término suelto que no se define para todas las plataformas (por ejemplo. computadoras pequeñas simples).

Considerando solo los sistemas operativos que soportan procesos, los procesos pueden recibir notificación de que se produjo un error de segmentación.

Además, limitar los sistemas operativos a sistemas operativos' unix like', un método confiable para que un proceso reciba un SIGSEGV la señal es kill(getpid(),SIGSEGV)

Como es el caso en la mayoría de los problemas multiplataforma, cada plataforma puede (generalmente lo hace) tener una definición diferente de seg-faulting.

Pero para ser prácticos, los sistemas operativos actuales de mac, lin y win se segmentarán en

*(int*)0 = 0;

Además, no es un mal comportamiento causar un segfault. Algunas implementaciones de assert() causan una señal SIGSEGV que podría producir un archivo core. Muy útil cuando necesitas autopsia.

Lo que es peor que causar un segfault es ocultar it:

try
{
     anyfunc();
}
catch (...) 
{
     printf("?\n");
}

Que oculta el origen de un error y todo lo que tienes que seguir es:

?

.

 1
Author: effbiae,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-29 14:46:41
 main;

Eso es todo.

Realmente.

Esencialmente, lo que esto hace es definir main como una variable . En C, variables y funciones son ambos símbolos poin punteros en memoria, por lo que el compilador no los distingue, y este código no arroja un error.

Sin embargo, el problema radica en cómo el sistema ejecuta ejecutables. En pocas palabras, el estándar C requiere que todos los ejecutables de C tengan un entrypoint de preparación de entorno integrado en ellos, que básicamente se reduce a " llamar main".

En este caso particular, sin embargo, main es una variable, por lo que se coloca en una no ejecutable sección de memoria llamada .bss, destinada a variables (en lugar de .text para el código). Intentar ejecutar código en .bss viola su segmentación específica, por lo que el sistema lanza un fallo de segmentación.

Para ilustrar, aquí está (parte de) un objdump del archivo resultante:

# (unimportant)

Disassembly of section .text:

0000000000001020 <_start>:
    1020:   f3 0f 1e fa             endbr64 
    1024:   31 ed                   xor    %ebp,%ebp
    1026:   49 89 d1                mov    %rdx,%r9
    1029:   5e                      pop    %rsi
    102a:   48 89 e2                mov    %rsp,%rdx
    102d:   48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
    1031:   50                      push   %rax
    1032:   54                      push   %rsp
    1033:   4c 8d 05 56 01 00 00    lea    0x156(%rip),%r8        # 1190 <__libc_csu_fini>
    103a:   48 8d 0d df 00 00 00    lea    0xdf(%rip),%rcx        # 1120 <__libc_csu_init>

    # This is where the program should call main
    1041:   48 8d 3d e4 2f 00 00    lea    0x2fe4(%rip),%rdi      # 402c <main> 
    1048:   ff 15 92 2f 00 00       callq  *0x2f92(%rip)          # 3fe0 <__libc_start_main@GLIBC_2.2.5>
    104e:   f4                      hlt    
    104f:   90                      nop

# (nice things we still don't care about)

Disassembly of section .data:

0000000000004018 <__data_start>:
    ...

0000000000004020 <__dso_handle>:
    4020:   20 40 00                and    %al,0x0(%rax)
    4023:   00 00                   add    %al,(%rax)
    4025:   00 00                   add    %al,(%rax)
    ...

Disassembly of section .bss:

0000000000004028 <__bss_start>:
    4028:   00 00                   add    %al,(%rax)
    ...

# main is in .bss (variables) instead of .text (code)

000000000000402c <main>:
    402c:   00 00                   add    %al,(%rax)
    ...

# aaand that's it! 

PD: Esto no funcionará si compilas a un ejecutable plano. En su lugar, causará un comportamiento indefinido.

 1
Author: Thesola10,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-05 15:22:13