Necesito detectar fallas de segmentación en operaciones de limpieza de bibliotecas de terceros. Esto sucede a veces justo antes de que salga mi programa y no puedo solucionar la verdadera razón de esto. En la programación de Windows, podría hacer esto con __try - __catch. ¿Existe una forma multiplataforma o específica de plataforma para hacer lo mismo? Necesito esto en Linux, gcc.
c++
segmentation-fault
try-catch
Alex F
fuente
fuente
Respuestas:
En Linux también podemos tener estos como excepciones.
Normalmente, cuando su programa realiza una falla de segmentación, se envía una
SIGSEGV
señal. Puede configurar su propio controlador para esta señal y mitigar las consecuencias. Por supuesto, debe estar realmente seguro de que puede recuperarse de la situación. En su caso, creo que debería depurar su código.Volvamos al tema. Recientemente encontré una biblioteca ( manual breve ) que transforma tales señales en excepciones, por lo que puede escribir código como este:
try { *(int*) 0 = 0; } catch (std::exception& e) { std::cerr << "Exception caught : " << e.what() << std::endl; }
Sin embargo, no lo comprobé.Funciona en mi caja Gentoo x86-64. Tiene un backend específico de la plataforma (tomado de la implementación de java de gcc), por lo que puede funcionar en muchas plataformas. Solo es compatible con x86 y x86-64 de fábrica, pero puede obtener backends de libjava, que reside en gcc sources.fuente
-fnon-call-exceptions
asegurarse de que funcione, y eso tiene un costo de rendimiento. También existe el peligro de que se lance desde una función sin soporte de excepción (como una función C) y se produzca una fuga / falla más tarde../build_gcc_linux_release
da varios errores.Aquí hay un ejemplo de cómo hacerlo en C.
#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> void segfault_sigaction(int signal, siginfo_t *si, void *arg) { printf("Caught segfault at address %p\n", si->si_addr); exit(0); } int main(void) { int *foo = NULL; struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); sigemptyset(&sa.sa_mask); sa.sa_sigaction = segfault_sigaction; sa.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); /* Cause a seg fault */ *foo = 1; return 0; }
fuente
signal(7)
enumera todas las funciones seguras para señales asíncronas que se pueden utilizar con relativamente poco cuidado. En el ejemplo anterior también es completamente seguro porque nada más en el programa está tocandostdout
excepto laprintf
llamada en el controlador.Solución C ++ que se encuentra aquí ( http://www.cplusplus.com/forum/unices/16430/ )
#include <signal.h> #include <stdio.h> #include <unistd.h> void ouch(int sig) { printf("OUCH! - I got signal %d\n", sig); } int main() { struct sigaction act; act.sa_handler = ouch; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGINT, &act, 0); while(1) { printf("Hello World!\n"); sleep(1); } }
fuente
Para la portabilidad, probablemente se debería usar
std::signal
de la biblioteca estándar de C ++, pero hay muchas restricciones sobre lo que puede hacer un manejador de señales. Desafortunadamente, no es posible capturar un SIGSEGV desde un programa C ++ sin introducir un comportamiento indefinido porque la especificación dice:abort
,exit
, algunas funciones atómicas, vuelva a instalar actual manejador de señales,memcpy
,memmove
, caracteres de tipo, `std :: movimiento, std::forward
, y algunos más ).throw
expresión.Esto prueba que es imposible capturar SIGSEGV desde un programa que utilice C ++ estrictamente estándar y portátil. SIGSEGV todavía es detectado por el sistema operativo y normalmente se informa al proceso padre cuando se llama a una función de familia de espera .
Probablemente se encontrará con el mismo tipo de problema al usar la señal POSIX porque hay una cláusula que dice en 2.4.3 Acciones de señal :
Unas palabras sobre el
longjump
s. Suponiendo que estamos usando señales POSIX, usarlongjump
para simular el desenrollado de la pila no ayudará:Esto significa que la continuación invocado por la llamada a longjump no se puede llamar de forma fiable por lo general la función de utilidad de la biblioteca como
printf
,malloc
oexit
o devolución del principal sin inducir un comportamiento indefinido. Como tal, la continuación solo puede realizar operaciones restringidas y solo puede salir a través de algún mecanismo de terminación anormal.En pocas palabras, capturar un SIGSEGV y reanudar la ejecución del programa en un portátil probablemente no sea factible sin introducir UB. Incluso si está trabajando en una plataforma Windows para la que tiene acceso al manejo estructurado de excepciones, vale la pena mencionar que MSDN sugiere no intentar nunca manejar las excepciones de hardware: Excepciones de hardware
fuente
A veces queremos capturar a
SIGSEGV
para averiguar si un puntero es válido, es decir, si hace referencia a una dirección de memoria válida. (O incluso verifique si algún valor arbitrario puede ser un puntero).Una opción es verificarlo con
isValidPtr()
(funcionó en Android):int isValidPtr(const void*p, int len) { if (!p) { return 0; } int ret = 1; int nullfd = open("/dev/random", O_WRONLY); if (write(nullfd, p, len) < 0) { ret = 0; /* Not OK */ } close(nullfd); return ret; } int isValidOrNullPtr(const void*p, int len) { return !p||isValidPtr(p, len); }
Otra opción es leer los atributos de protección de la memoria, que es un poco más complicado (funcionó en Android):
re_mprot.c:
#include <errno.h> #include <malloc.h> //#define PAGE_SIZE 4096 #include "dlog.h" #include "stdlib.h" #include "re_mprot.h" struct buffer { int pos; int size; char* mem; }; char* _buf_reset(struct buffer*b) { b->mem[b->pos] = 0; b->pos = 0; return b->mem; } struct buffer* _new_buffer(int length) { struct buffer* res = malloc(sizeof(struct buffer)+length+4); res->pos = 0; res->size = length; res->mem = (void*)(res+1); return res; } int _buf_putchar(struct buffer*b, int c) { b->mem[b->pos++] = c; return b->pos >= b->size; } void show_mappings(void) { DLOG("-----------------------------------------------\n"); int a; FILE *f = fopen("/proc/self/maps", "r"); struct buffer* b = _new_buffer(1024); while ((a = fgetc(f)) >= 0) { if (_buf_putchar(b,a) || a == '\n') { DLOG("/proc/self/maps: %s",_buf_reset(b)); } } if (b->pos) { DLOG("/proc/self/maps: %s",_buf_reset(b)); } free(b); fclose(f); DLOG("-----------------------------------------------\n"); } unsigned int read_mprotection(void* addr) { int a; unsigned int res = MPROT_0; FILE *f = fopen("/proc/self/maps", "r"); struct buffer* b = _new_buffer(1024); while ((a = fgetc(f)) >= 0) { if (_buf_putchar(b,a) || a == '\n') { char*end0 = (void*)0; unsigned long addr0 = strtoul(b->mem, &end0, 0x10); char*end1 = (void*)0; unsigned long addr1 = strtoul(end0+1, &end1, 0x10); if ((void*)addr0 < addr && addr < (void*)addr1) { res |= (end1+1)[0] == 'r' ? MPROT_R : 0; res |= (end1+1)[1] == 'w' ? MPROT_W : 0; res |= (end1+1)[2] == 'x' ? MPROT_X : 0; res |= (end1+1)[3] == 'p' ? MPROT_P : (end1+1)[3] == 's' ? MPROT_S : 0; break; } _buf_reset(b); } } free(b); fclose(f); return res; } int has_mprotection(void* addr, unsigned int prot, unsigned int prot_mask) { unsigned prot1 = read_mprotection(addr); return (prot1 & prot_mask) == prot; } char* _mprot_tostring_(char*buf, unsigned int prot) { buf[0] = prot & MPROT_R ? 'r' : '-'; buf[1] = prot & MPROT_W ? 'w' : '-'; buf[2] = prot & MPROT_X ? 'x' : '-'; buf[3] = prot & MPROT_S ? 's' : prot & MPROT_P ? 'p' : '-'; buf[4] = 0; return buf; }
re_mprot.h:
#include <alloca.h> #include "re_bits.h" #include <sys/mman.h> void show_mappings(void); enum { MPROT_0 = 0, // not found at all MPROT_R = PROT_READ, // readable MPROT_W = PROT_WRITE, // writable MPROT_X = PROT_EXEC, // executable MPROT_S = FIRST_UNUSED_BIT(MPROT_R|MPROT_W|MPROT_X), // shared MPROT_P = MPROT_S<<1, // private }; // returns a non-zero value if the address is mapped (because either MPROT_P or MPROT_S will be set for valid addresses) unsigned int read_mprotection(void* addr); // check memory protection against the mask // returns true if all bits corresponding to non-zero bits in the mask // are the same in prot and read_mprotection(addr) int has_mprotection(void* addr, unsigned int prot, unsigned int prot_mask); // convert the protection mask into a string. Uses alloca(), no need to free() the memory! #define mprot_tostring(x) ( _mprot_tostring_( (char*)alloca(8) , (x) ) ) char* _mprot_tostring_(char*buf, unsigned int prot);
PD
DLOG()
esprintf()
el registro de Android.FIRST_UNUSED_BIT()
se define aquí .PPS Puede que no sea una buena idea llamar a alloca () en un bucle; es posible que la memoria no se libere hasta que la función regrese.
fuente