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


Quiero escribir un controlador de señal para capturar 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 pagesize de memoria 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 mi controlador será llamado. Hasta ahora 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 funcionamiento normal de mi código. No quiero salir de la función. En futuras escrituras en la misma memoria, quiero capturar la señal de nuevo y modificar los derechos de escritura y luego grabar ese evento.

Aquí está el código :

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

Author: Jeff, 2010-04-18

5 answers

Cuando su manejador de señales regresa (suponiendo que no llame a exit o longjmp o algo que impida que realmente regrese), 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 cambiado, solo lanzará la señal de nuevo, y volverá a su controlador de señal en un bucle infinito.

Así que para que funcione, tienes que llamar a mprotect en el manejador de señales. Desafortunadamente, como Steven Schansker señala, mprotect no es async-seguro, por lo que no se puede llamar de forma segura desde el controlador de señal. Así que, en lo que respecta a POSIX, estás jodido.

Afortunadamente en la mayoría de las implementaciones (todas las variantes modernas de UNIX y Linux que yo sepa), mprotect es una llamada al sistema, por lo que es seguro llamar desde un manejador de señal , por lo que puede hacer la mayoría de lo que quiera. El problema es que si desea cambiar las protecciones de nuevo después de la lectura, tendrá que hacerlo en la principal programa después de la lectura.

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

Editar

La estructura ucontext se define en <ucontext.h>. Dentro de ucontext el campo uc_mcontext contiene el contexto de la máquina, y dentro de ese, el array gregs contiene el contexto del registro general. Así que en su controlador de señal:

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

Le dará el pc donde ocurrió la excepción. Usted puede leerlo a la figura fuera de lo que la instrucción fue que falló, y hacer algo diferente.

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

 62
Author: Chris Dodd,
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-09-20 07:33:41

Has caído en la trampa que todas las personas hacen cuando intentan manejar las señales por primera vez. La trampa? Pensando que realmente puedes hacer cualquier cosa útil con manejadores de señal. Desde un manejador de señales, solo se le permite llamar a llamadas de biblioteca asíncronas y seguras para reentradas.

Vea este aviso de 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 es mprotect. No se le permite llamar desde un manejador de señales. podría funcionar, pero puedo prometer que se encontrará con problemas en el futuro. Tenga mucho cuidado con los controladores de señal, que son difíciles de hacer bien!

EDITAR

Ya que estoy siendo un idiota de la portabilidad en este momento, señalaré que tampoco debe escribir en variables compartidas (es decir, globales) sin tomar las precauciones adecuadas.

 21
Author: Steven Schlansker,
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
2010-04-19 02:04:43

Puede recuperarse de SIGSEGV en linux. También puede recuperarse de fallos 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 portable.

Echa un vistazo a libsigsegv.

 9
Author: Ben Voigt,
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-03-24 23:21:08

No debe volver desde el manejador de señales, ya que entonces el comportamiento es indefinido. Más bien, salte con longjmp.

Esto solo está bien si la señal se genera en una función async-signal-safe. De lo contrario, el comportamiento es indefinido si el programa alguna vez llama a otra función async-signal-unsafe. Por lo tanto, el manejador de señales solo debe establecerse inmediatamente antes de que sea necesario, y desactivarse tan pronto como sea posible.

De hecho, sé de muy pocos usos de un SIGSEGV controlador:

  • utilice una biblioteca de trazas traseras async-signal-safe para registrar una traza trasera y luego morir.
  • en una máquina virtual como la JVM o CLR: compruebe si el SIGSEGV se produjo en el código compilado por JIT. Si no, muere; si es así, entonces lanza una excepción específica del lenguaje (no una excepción de C++), que funciona porque el compilador JIT sabía que la trampa podría ocurrir y generó los datos de desenrollado de fotogramas apropiados.
  • clone () and exec () a debugger (do not use fork () - that calls callbacks registrado por pthread_atfork()).

Finalmente, tenga en cuenta que cualquier acción que desencadene SIGSEGV es probablemente UB, ya que esto es acceder a memoria no válida. Sin embargo, este no sería el caso si la señal fuera, digamos, SIGFPE.

 4
Author: Demi,
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-09-04 16:05:28

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

Http://www.mail-archive.com/[email protected]/msg13853.html

 0
Author: shreshtha,
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
2012-12-15 05:39:52