Cómo escribir un controlador de señal para atrapar SIGSEGV?

Quiero escribir un manejador de señal para atrapar SIGSEGV. Protejo un bloque de memoria para leer o escribir usando

char *buffer; char *p; char a; int pagesize = 4096; mprotect(buffer,pagesize,PROT_NONE) 

Esto protege los bytes de memoria de tamaño de página que comienzan en el búfer contra cualquier lectura o escritura.

Segundo, trato de leer la memoria:

 p = buffer; a = *p 

Esto generará un SIGSEGV y se llamará a mi controlador. Hasta aquí todo bien. Mi problema es que, una vez que se llama al controlador, quiero cambiar la escritura de acceso de la memoria haciendo

 mprotect(buffer,pagesize,PROT_READ); 

y continuar el funcionamiento normal de mi código. No quiero salir de la función. En futuras escrituras en la misma memoria, quiero volver a captar la señal y modificar los derechos de escritura y luego grabar ese evento.

Aquí está el código :

 #include  #include  #include  #include  #include  #include  #define handle_error(msg) \ do { perror(msg); exit(EXIT_FAILURE); } while (0) char *buffer; int flag=0; static void handler(int sig, siginfo_t *si, void *unused) { printf("Got SIGSEGV at address: 0x%lx\n",(long) si->si_addr); printf("Implements the handler only\n"); flag=1; //exit(EXIT_FAILURE); } int main(int argc, char *argv[]) { char *p; char a; int pagesize; struct sigaction sa; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); sa.sa_sigaction = handler; if (sigaction(SIGSEGV, &sa, NULL) == -1) handle_error("sigaction"); pagesize=4096; /* Allocate a buffer aligned on a page boundary; initial protection is PROT_READ | PROT_WRITE */ buffer = memalign(pagesize, 4 * pagesize); if (buffer == NULL) handle_error("memalign"); printf("Start of region: 0x%lx\n", (long) buffer); printf("Start of region: 0x%lx\n", (long) buffer+pagesize); printf("Start of region: 0x%lx\n", (long) buffer+2*pagesize); printf("Start of region: 0x%lx\n", (long) buffer+3*pagesize); //if (mprotect(buffer + pagesize * 0, pagesize,PROT_NONE) == -1) if (mprotect(buffer + pagesize * 0, pagesize,PROT_NONE) == -1) handle_error("mprotect"); //for (p = buffer ; ; ) if(flag==0) { p = buffer+pagesize/2; printf("It comes here before reading memory\n"); a = *p; //trying to read the memory printf("It comes here after reading memory\n"); } else { if (mprotect(buffer + pagesize * 0, pagesize,PROT_READ) == -1) handle_error("mprotect"); a = *p; printf("Now i can read the memory\n"); } /* for (p = buffer;p<=buffer+4*pagesize ;p++ ) { //a = *(p); *(p) = 'a'; printf("Writing at address %p\n",p); }*/ printf("Loop completed\n"); /* Should never happen */ exit(EXIT_SUCCESS); } 

El problema es que solo funciona el manejador de señal y no puedo regresar a la función principal después de atrapar la señal.

Cuando regrese su manejador de señal (suponiendo que no llame a exit o longjmp o algo que le impida regresar), el código continuará en el punto en que se produjo la señal, volviendo a ejecutar la misma instrucción. Dado que en este punto, la protección de la memoria no se ha modificado, solo arrojará la señal nuevamente, y usted estará de regreso en su controlador de señal en un bucle infinito.

Entonces, para que funcione, debes llamar a mprotect en el controlador de señal. Desafortunadamente, como señala Steven Schansker, mprotect no es asíncrono, por lo que no puede llamarlo de manera segura desde el manejador de señal. Entonces, en lo que se refiere a POSIX, estás jodido.

Afortunadamente en la mayoría de las implementaciones (todas las variantes modernas de UNIX y Linux, hasta donde yo sé), mprotect es una llamada al sistema, por lo que es seguro llamar desde un manejador de señal , para que pueda hacer la mayor parte de lo que desee. El problema es que si desea volver a cambiar las protecciones después de la lectura, tendrá que hacerlo en el progtwig principal después de la lectura.

Otra posibilidad es hacer algo con el tercer argumento para el controlador de señal, que apunta a una estructura específica de sistema operativo y arco que contiene información sobre dónde se produjo la señal. En Linux, esta es una estructura ucontext , que contiene información específica de la máquina sobre la dirección de $ PC y otros contenidos de registro donde se produjo la señal. Si modifica esto, cambia el lugar al que volverá el controlador de señal, por lo que puede cambiar el PC $ justo después de la instrucción de fallas para que no se vuelva a ejecutar después de que el controlador regrese. Es muy complicado hacerlo bien (y no portátil también).

editar

La estructura de ucontext se define en . Dentro de ucontext el campo uc_mcontext contiene el contexto de la máquina, y dentro de eso , la matriz gregs contiene el contexto de registro general. Entonces en tu controlador de señal:

 ucontext *u = (ucontext *)unused; unsigned char *pc = (unsigned char *)u->uc_mcontext.gregs[REG_RIP]; 

le dará la pc donde ocurrió la excepción. Puede leerlo para descubrir qué instrucción fue la que falló y hacer algo diferente.

En cuanto a la portabilidad de llamar a mprotect en el controlador de señal, cualquier sistema que siga la especificación SVID o la especificación BSD4 debería ser seguro: permiten llamar a cualquier llamada al sistema (cualquier cosa en la sección 2 del manual) en una señal entrenador de animales.

Has caído en la trampa que todas las personas hacen cuando intentan manejar las señales por primera vez. ¿La trampa? Pensando que en realidad puedes hacer algo útil con manejadores de señal. Desde un manejador de señal, solo se le permite llamar a bibliotecas asincrónicas y reentrantes seguras.

Consulte este aviso CERT sobre por qué y una lista de las funciones POSIX que son seguras.

Tenga en cuenta que printf (), que ya está llamando, no está en esa lista.

Tampoco está mprotect. No puedes llamar desde un manejador de señal. Podría funcionar, pero puedo prometer que se encontrará con problemas en el futuro. Ten mucho cuidado con los manejadores de señal, ¡es complicado hacerlo bien!

EDITAR

Dado que ya estoy siendo un portavasos de portabilidad, señalaré que tampoco debería escribir en variables compartidas (es decir, globales) sin tomar las precauciones adecuadas.

Puede recuperar desde SIGSEGV en Linux. También puede recuperarse de las fallas de segmentación en Windows (verá una excepción estructurada en lugar de una señal). Pero el estándar POSIX no garantiza la recuperación , por lo que su código será muy no portátil.

Eche un vistazo a libsigsegv .

No debe regresar del manejador de señal, ya que el comportamiento no está definido. Por el contrario, salta con longjmp.

Esto solo está bien si la señal se genera en una función de seguridad de señal asíncrona. De lo contrario, el comportamiento no está definido si el progtwig alguna vez llama a otra función de señal asíncrona insegura. Por lo tanto, el manejador de señal solo debe establecerse inmediatamente antes de que sea necesario y se debe restablecer tan pronto como sea posible.

De hecho, conozco muy pocos usos de un controlador SIGSEGV:

  • use una biblioteca de rastreo de señal asíncrona segura para registrar una traza inversa, luego muera.
  • en una VM como JVM o CLR: compruebe si el SIGSEGV se produjo en el código comstackdo JIT. Si no, muere; si es así, ejecute una excepción específica de lenguaje ( no una excepción de C ++), que funciona porque el comstackdor de JIT sabía que la trampa podría suceder y generó datos de desenrollado de ttwig apropiados.
  • clone () y exec () un depurador ( no use fork () – que llama retrollamadas registradas por pthread_atfork ()).

Finalmente, tenga en cuenta que cualquier acción que desencadena SIGSEGV probablemente sea UB, ya que está accediendo a la memoria no válida. Sin embargo, este no sería el caso si la señal fuera, por ejemplo, SIGFPE.

Hay un problema de comstackción usando ucontext_t o struct ucontext (presente en /usr/include/sys/ucontext.h )

http://www.mail-archive.com/arch-general@archlinux.org/msg13853.html