2010-04-18 20 views
55

Quiero escribir un manejador de señal para capturar SIGSEGV. puedo proteger a un bloque de memoria para leer o escribir utilizandoCómo escribir un manejador de señal para atrapar SIGSEGV?

char *buffer; 
char *p; 
char a; 
int pagesize = 4096; 

mprotect(buffer,pagesize,PROT_NONE) 

Esto protege bytes PAGESIZE de memoria a partir de amortiguador contra cualquier lee o escribe.

En segundo lugar, trato de leer la memoria:

p = buffer; 
a = *p 

Esto generará un SIGSEGV, y mi manejador será llamado. Hasta ahora todo bien. Mi problema es que, una vez que el controlador se llama, yo quiero cambiar el acceso de escritura de la memoria haciendo

mprotect(buffer,pagesize,PROT_READ); 

y continuar con el funcionamiento normal de mi código. No quiero salir de la función. En el futuro escribe en la misma memoria, quiero capturar la señal nuevamente y modificar los derechos de escritura y luego grabar ese evento.

Aquí es the code:

#include <signal.h> 
#include <stdio.h> 
#include <malloc.h> 
#include <stdlib.h> 
#include <errno.h> 
#include <sys/mman.h> 

#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 sólo se ejecuta el manejador de la señal y no puedo volver a la función principal después de la captura de la señal.

+2

gracias por editar. Lo agradezco. Necesito dedicar algo de tiempo para aprender a editar mis preguntas ... – Adi

+0

al compilar, active siempre todas las advertencias y luego corrija esas advertencias. (para 'gcc', como mínimo uso:' -Wall -Wextra -pedantic' También uso: '-Wconversion -std = gnu99') El compilador te dirá: 1) parámetro' argc' no utilizado 2) parámetro ' argv' no utilizado (sugiera utilizar la firma main() de: 'int main (void)' 3) variable local 'p' utilizada en el bloque de código' else' sin inicializarse. 4) parámetro 'unused' no utilizado, sugerir: add statement:' (void) no utilizado; 'como primera línea en esa función. 5) conjunto de variables locales 'a' pero no utilizado. – user3629249

+0

¡NUNCA use 'printf()' en un manejador de señal! La función 'write()' estaría bien para usar, pero lo mejor es no hacer ninguna E/S en un manejador de señal, simplemente establezca un indicador y deje que la línea principal de código verifique ese indicador – user3629249

Respuesta

57

Cuando regrese su manejador de señal (suponiendo que no llame a exit o longjmp o algo que impida que regrese realmente), el código continuará en el punto donde ocurrió 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.

Para que funcione, debe 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 safe to call from within a signal handler, por lo que puede 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 programa principal después de la lectura.

Otra posibilidad es hacer algo con el tercer argumento para el manejador 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 $ PC y otros contenidos del 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 ucontext se define en <ucontext.h>. Dentro del campo ucontext, el campo uc_mcontext contiene el contexto de la máquina, y dentro de ese, la matriz gregs contiene el contexto general del registro. Por lo tanto, en su controlador de señal:

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

le dará la pc donde se produjo la excepción. Puede leerlo para descubrir qué instrucción fue el 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 BSD4 debe ser seguro - permite llamar a cualquier llamada del sistema (cualquier cosa en la sección 2 del manual) en un controlador de señal.

+0

Derecho, puede realizar el acceso a la memoria en nombre del programa (como una VM) y luego actualizar el puntero de instrucción. Llamar a 'mprotect' es definitivamente más fácil. –

+0

hi chris, Me ha dado información útil. Gracias por eso ... ¿Pueden decirme cómo puedo leer la información en la estructura ucontext (tercer argumento y cambiar la $ PC)? Tengo curiosidad por saberlo. – Adi

+0

@ Ben Voigt, no entendí claramente lo que está diciendo, le pido que sea un poco más elaborado. – Adi

20

Ha 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 puede hacer cualquier cosa útil con controladores de señal. Desde un manejador de señal, solo se le permite llamar a bibliotecas asincrónicas y reentrantes seguras.

Consulte this CERT advisory 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 es mprotect. No puedes llamar desde un manejador de señal. Es podría funcionar, pero puedo prometer que se encontrará con problemas en el camino. Ten mucho cuidado con los manejadores de señal, ¡es complicado hacerlo bien!

EDITAR

Desde que estoy siendo un gilipollas portabilidad en el momento ya se, voy a señalar que also shouldn't write to shared (i.e. global) variables sin tomar las precauciones adecuadas.

+0

Hola steven, Si no puedo hacer nada útil dentro del manejador de señales, estaré bien si puedo actualizar algunos contadores dentro de él y regresar a main y normalmente ejecutar mi código, ¿es posible? – Adi

+0

citando el aviso de CERT, "pueden llamar a otras funciones siempre que todas las implementaciones a las que se transporta el código garanticen que estas funciones son asíncronas-seguras". En Linux que incluye muchas más funciones. –

+0

¡Claro, pero solo tiene que ser consciente del problema! No puedo nombrar la parte superior de mi cabeza qué funciones son y no son señales seguras, ¡y dudo que muchos lo puedan hacer! –

8

Puede recuperar de 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 the POSIX standard doesn't guarantee recovery, por lo que su código será muy no portátil.

Eche un vistazo a libsigsegv.

4

No debe volver desde el manejador de la señal, ya que entonces 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 programa 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, no conozco muy pocos usos de un manejador de SIGSEGV:

  • utilizar una biblioteca de traza inversa asíncrona entre señal y seguro para registrar un trazado inverso, luego mueren.
  • en una VM como JVM o CLR: compruebe si el SIGSEGV se produjo en el código compilado 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 compilador JIT sabía que la trampa podría suceder y generó datos de desenrollado de trama apropiados.
  • clone() y exec() un depurador (do not use fork() - que llama retrollamadas registradas por pthread_atfork()).

Por último, tenga en cuenta que cualquier acción que active 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.

Cuestiones relacionadas