2009-02-16 17 views
9

Tengo un programa que segfaults de aritmética de puntero a veces. Sé que esto sucede, pero no puedo verificarlo con anticipación para ver si se segmenta o no, o puedo "preescanear" datos de entrada para ver si causará una segfault (que puede ser imposible de determinar), o puedo reajustarlo para que no use la aritmética del puntero, lo que requeriría una cantidad de trabajo significativamente mayor, o puedo intentar atrapar un segfault. Entonces mi pregunta:Captura de segfaults en C

1) ¿Cómo, en C, puedo atrapar un segfault? Sé algo en el sistema operativo provoca una segfault, pero ¿qué puede hacer un programa C en el caso de que segfaults a morir un poco más elegante que simplemente Segmentation fault?

2) ¿Qué tan portátil es esto?

Imagino que este es un comportamiento muy pocoportable, por lo que si publica un código para atrapar un segfault, dígame en qué funciona. Estoy en Mac OS X pero me gustaría que mi programa funcione en tantas plataformas como pueda y quiero ver cuáles son mis opciones.

Y no se preocupe: básicamente, todo lo que quiero hacer es imprimir un mensaje de error más fácil de usar y liberar algo de memoria malloc() ed, y luego morir. No estoy planeando ignorar todos los segfaults que obtengo y avanzo.

+1

Imprime el mensaje de error; no libere la memoria, ya que es probable que su sistema de memoria esté codificado cuando ocurre la falla seg. –

Respuesta

9

Tiene que definir un manejador de señal. Esto se hace en sistemas Unix que usan la función sigaction. He hecho esto con el mismo código en Fedora de 64 y 32 bits y en Sun Solaris.

21

Bueno, SIGSEGV es trampable, y esto es POSIX, por lo que es portátil en ese sentido.

Bu Me preocupa que parezca que desea manejar el segfault en lugar de solucionar el problema que causa el segfault. Si tuviera que elegir si era el sistema operativo defectuoso o mi propio código, sé cuál elegiría. Sugiero que busques ese error, lo arregles y luego escribas un caso de prueba para asegurarte de que nunca vuelva a morderte.

+0

Sin profundizar en los detalles, el modo predeterminado es seguro y evita que el usuario se quede sin memoria (utiliza una lista vinculada).Deben elegir usar explícitamente la versión "menos segura" y deben tener en cuenta las consecuencias de hacerlo. –

+0

La versión "menos segura" se proporciona para compatibilidad con otros programas que NO proporcionan una lista de enlaces infinitos como la mía. Además, si nada más, aprenderé algo nuevo. –

+1

Entendido - usted tiene buenas razones. Aunque tuve que señalarlo ... –

1

Creo que estás tratando de resolver un problema que no existe. Al menos estás trabajando en el lado equivocado. No podrá capturar una falla de segmentación, ya que este error/excepción es lanzado por el sistema operativo (es causado por su programa, el sistema operativo solo lo captura).

Te aconsejo que reconsideres tu estrategia con respecto a la entrada: ¿Por qué es imposible desinfectarla? Lo más importante es verificar el tamaño, para esto el C stdlib tiene las funciones apropiadas. Entonces, por supuesto, debe verificar las entradas válidas con respecto al contenido. Sí, esto probablemente resultará en mucho trabajo, pero es la única manera de escribir un programa sólido.

EDIT: No soy un experto en C, no sabía que incluso un error de segmentación podría ser manejado por un manejador de señal. Aún así, creo que no es la forma correcta de hacerlo por las razones mencionadas anteriormente.

+0

La comprobación de tamaño no es el problema: el usuario ingresa un programa (en brainf * ck) y mi programa lo ejecuta. La comprobación de una entrada válida se ejecuta contra el problema de detención. Además, algunos programas solo segmentarán a veces (es decir, con más datos ingresados), y dependerá o no de la entrada del usuario. –

+0

@Chris Lutz: Ahhh ... en este contexto su pregunta parece mucho más plausible. Ahora puedo ver por qué quieres, de hecho, por qué es tu única opción viable, atrapar SIGSEGV. – paprika

+0

No es la única opción "factible". Podría reescribirlo para usar una matriz y una variable que apunte a un elemento de esa matriz, y luego verifique fácilmente si el puntero está fuera de los límites (es decir, < 0 or > MAX), pero esto se vería aún más horrible (desde donde estoy parado). –

11

Puede utilizar la función señal de instalar un nuevo controlador de señal para la señal:

#include <signal.h> 
    void (*signal(int signum, void (*sighandler)(int)))(int); 

algo como el siguiente código:

signal(SIGINT , clean_exit_on_sig); 
signal(SIGABRT , clean_exit_on_sig); 
signal(SIGILL , clean_exit_on_sig); 
signal(SIGFPE , clean_exit_on_sig); 
signal(SIGSEGV, clean_exit_on_sig); // <-- this one is for segmentation fault 
signal(SIGTERM , clean_exit_on_sig); 

void 
clean_exit_on_sig(int sig_num) 
{ 
     printf ("\n Signal %d received",sig_num); 
} 
+0

... señalando que para cuando hayas llegado a un SEGV, donde un puntero de lectura/escritura solo * pasa * a la memoria inaccesible, es bastante probable que ya haya pisoteado la memoria asignada y accesible que contiene tus datos y la lista de bloque libre. Por lo tanto, no espere que alloc funcione, y no espere que ningún dato en la memoria esté cuerdo. – ijw

1

Hay un ejemplo de cómo atrapar SIGSEGV e imprimir una traza de pila usando la traza inversa de glibc() aquí:

how to generate a stacktrace when my C++ app crashes

Usted puede usar esto para recuperar el error de segmentación y limpiar, pero ten cuidado: no se debe hacer demasiadas cosas en un manejador de señales, especialmente cosas que involucran realización de llamadas como malloc(). Hay muchas llamadas que no son seguras para las señales y puede terminar disparándose en el pie si hace, por ejemplo, una llamada a malloc desde dentro de malloc.

+0

El enlace está muerto, considere actualizar o eliminar esta respuesta – slayton

1

manejo de señales es portátil (relativamente) a través de las máquinas UNIX (esto incluye Mac y Linux). Las grandes diferencias están en el detalle de excepción, que se pasa como argumento a la rutina de manejo de señal. Sorrty, pero es probable que necesite un montón de #ifdefs para eso, si desea imprimir mensajes de error más razonables (como dónde y por qué dirección ocurrió el error) ...

bien, aquí hay un código fragmento para que usted comience con:

#include <signal.h> 

/* reached when a segv occurrs */ 
void 
SEGVFunction(SIGARGS) 
{ 
    ... 
} 

... 
main(...) { 
    signal(SIGSEGV, SEGVFunction); /* tell the OS, where to go in case... */ 
    ... 
    ... do your work ... 
} 

Su tarea consiste en:

  • cheque lo SIGARGS es (depende del sistema operativo, por lo que utiliza un ifdef)
  • ver cómo extraer fallos dirección y el PC de la información de la excepción en sigArgs
  • impresión mensaje razonable
  • salida

en teoría, incluso se podría arreglar el PC en el manejador de la señal (para después de la instrucción con errores), y proceder. Sin embargo, los manejadores de señal típicos salen de (a) o a un longjmp() de regreso a un lugar de guardado en el principal.

respecto

5

Las acciones de seguridad en un manejador de señales son muy limitado. No es seguro para llamar a cualquier función de la biblioteca no se sabe que sean reentrantes, que excluye, por ejemplo, free() y printf(). La mejor práctica es establecer una variable y regresar, pero esto no te ayuda mucho. También es seguro usar llamadas al sistema como write().

Tenga en cuenta que en los dos ejemplos dados aquí backtrace, la función backtrace_symbols_fd() estará a salvo porque utiliza el fd prima directamente, pero la llamada a fprintf() es incorrecta, y debe ser reemplazado por un uso de write().

+0

Wow, eso es difícil. Mantendré estas reglas draconianas en mente. Quizás realmente NO DEBO intentar atrapar el segfault ... –

+1

Ciertamente estoy de acuerdo con los otros comentarios que sugieren que lo correcto es corregir el error que causa la segv. –