2011-01-28 10 views
11

Me acabo de enterar de que alguien está llamando, desde un manejador de señal, una función definitivamente no asincrónica de seguridad de señal que escribí. Y, por supuesto, me culpan (a pesar de las advertencias en mi documentación). (El mismo codificador llama a todo tipo de funciones no asíncronas con seguridad de señal desde su controlador de señal. Suspiro.)¿CÓMO determinar si el código se está ejecutando en el contexto del manejador de señal?

Entonces, ahora tengo curiosidad: ¿cómo evitar que vuelva a ocurrir esta situación? Me gustaría ser capaz de determinar fácilmente si el código se ejecuta en el contexto manejador de la señal (el lenguaje es C, pero no volvería a la solución aplicable a cualquier idioma?):

int myfunc(void) { 
    if(in_signal_handler_context()) { return(-1) } 
    // rest of function goes here 
    return(0); 
} 

Ésta es bajo Linux. Espero que esta no sea una respuesta fácil, o de lo contrario me sentiré como un idiota.

+0

Entendido. Sin embargo, cuando un programador encuentra por primera vez el concepto de señales, este tipo de cosas siempre sucede. ¿Adivina qué sucede cuando un destructor de objetos C++ es llamado a la fuerza desde un manejador de señal? – smcdow

Respuesta

7

Aparentemente, las nuevas versiones de Linux/x86 (probablemente desde algunos kernel 2.6.x) llaman a controladores de señal del vdso. Puede utilizar este hecho para infligir el siguiente hack horrible en el mundo desprevenido:

#include <stdio.h> 
#include <stdlib.h> 
#include <stdint.h> 
#include <string.h> 
#include <signal.h> 

#include <unistd.h> 

uintmax_t vdso_start = 0; 
uintmax_t vdso_end = 0;    /* actually, next byte */ 

int check_stack_for_vdso(uint32_t *esp, size_t len) 
{ 
    size_t i; 

    for (i = 0; i < len; i++, esp++) 
      if (*esp >= vdso_start && *esp < vdso_end) 
        return 1; 

    return 0; 
} 

void handler(int signo) 
{ 
    uint32_t *esp; 

    __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp)); 
    /* XXX only for demonstration, don't call printf from a signal handler */ 
    printf("handler: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20)); 
} 

void parse_maps() 
{ 
    FILE *maps; 
    char buf[256]; 
    char path[7]; 
    uintmax_t start, end, offset, inode; 
    char r, w, x, p; 
    unsigned major, minor; 

    maps = fopen("/proc/self/maps", "rt"); 
    if (maps == NULL) 
      return; 

    while (!feof(maps) && !ferror(maps)) { 
      if (fgets(buf, 256, maps) != NULL) { 
        if (sscanf(buf, "%jx-%jx %c%c%c%c %jx %u:%u %ju %6s", 
            &start, &end, &r, &w, &x, &p, &offset, 
            &major, &minor, &inode, path) == 11) { 
          if (!strcmp(path, "[vdso]")) { 
            vdso_start = start; 
            vdso_end = end; 
            break; 
          } 
        } 
      } 
    } 

    fclose(maps); 

    printf("[vdso] at %jx-%jx\n", vdso_start, vdso_end); 
} 

int main() 
{ 
    struct sigaction sa; 
    uint32_t *esp; 

    parse_maps(); 
    memset(&sa, 0, sizeof(struct sigaction)); 
    sa.sa_handler = handler; 
    sa.sa_flags = SA_RESTART; 

    if (sigaction(SIGUSR1, &sa, NULL) < 0) { 
      perror("sigaction"); 
      exit(1); 
    } 

    __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp)); 
    printf("before kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20)); 

    kill(getpid(), SIGUSR1); 

    __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp)); 
    printf("after kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20)); 

    return 0; 
} 

SCNR.

+0

No sabía que los manejadores de señal fueran llamados desde vdso. ¿Podría señalar una referencia? En cualquier caso, me gusta este truco. Mucho. Sería bastante fácil enrollar esto en una biblioteca opaca. El truco sería asegurarse de que se llamara a parse_maps() antes de cualquier manejador de señal. – smcdow

+0

La mejor referencia que puedo encontrar es http://lxr.free-electrons.com/source/arch/x86/kernel/signal.c?v=2.6.37#L310 – ninjalj

+0

Pero el comentario en la línea 320 parece interesante: http : //lxr.free-electrons.com/source/arch/x86/kernel/signal.c? v = 2.6.37 # L320 – ninjalj

0

Hay dos formas adecuadas para hacer frente a este:

  • en tus compañeros de trabajo dejan de hacer las cosas mal. Buena suerte al sacar esto adelante con el jefe, aunque ...

  • Haga que su función vuelva a entrar y sea asincrona. Si es necesario, proporcione una función con una firma diferente (por ejemplo, utilizando la ampliamente utilizada convención de nomenclatura *_r) con los argumentos adicionales que son necesarios para la preservación del estado.

En cuanto a la no apropiado manera de hacer esto, en Linux con GNU libc puede utilizar backtrace() y amigos para pasar por la lista de llamadas de su función. Es no fácil de hacerlo bien, seguro o portátil, pero podría hacer por un tiempo:

/* 
* *** Warning *** 
* 
* Black, fragile and unportable magic ahead 
* 
* Do not use this, lest the daemons of hell be unleashed upon you 
*/ 
int in_signal_handler_context() { 
     int i, n; 
     void *bt[1000]; 
     char **bts = NULL; 

     n = backtrace(bt, 1000); 
     bts = backtrace_symbols(bt, n); 

     for (i = 0; i < n; ++i) 
       printf("%i - %s\n", i, bts[i]); 

     /* Have a look at the caller chain */ 
     for (i = 0; i < n; ++i) { 
       /* Far more checks are needed here to avoid misfires */ 
       if (strstr(bts[i], "(__libc_start_main+") != NULL) 
         return 0; 
       if (strstr(bts[i], "libc.so.6(+") != NULL) 
         return 1; 
     } 

     return 0; 
} 


void unsafe() { 
     if (in_signal_handler_context()) 
       printf("John, you know you are an idiot, right?\n"); 
} 

En mi opinión, que sólo podría ser mejor dejar de fumar en lugar de ser obligado a escribir código como esto.

+0

Acabo de probar 'backtrace()', y simplemente no funciona: '__libc_start_main' está en el rastro tanto dentro como fuera del contexto de manejo de señal. –

+0

Como mencioné, no es fácil hacerlo bien. Tienes que encontrar una diferencia en la traza inversa entre los dos casos y usar eso. Por ejemplo, para mi prueba, asumí que ninguna función de libc estaría en el backtrace antes de llegar a 'main()', a menos que sea el código de manejo de señal. ¿Cómo se ve tu traza inversa en cada caso? – thkala

+0

Dos comentarios: (1) Temía que pudiera ser algo como esto. (2) No debe ser un moco, pero printf (3) no es una función de seguridad de señal asíncrona. Tendría que usar escribir (2). - Una lista de funciones de seguridad de señal asíncrona (al menos, la lista a la que suelo hacer referencia) se puede encontrar aquí: http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html – smcdow

0

Puede encontrar algo usando sigaltstack. Configure una pila de señal alternativa, obtenga el puntero de pila de alguna manera asíncrona, si dentro de la pila alternativa continúa, de lo contrario, aborte().

+0

Pensé en algo así, pero no creo que pueda garantizar que tendría configurada la pila alternativa antes de que mi función fuera llamada desde un manejador de señal. También debo reiterar que NUNCA se supone que mi función sea llamada desde un manejador de señal, y la documentación lo dice. – smcdow

+0

Hay una manera más fácil. 'sigaltstack' es necesario para devolver un error si ya se está ejecutando en la pila alternativa e intenta realizar cambios en ella, por lo que puede intentar llamar y ver si la llamada falla. –

0

Supongo que debe hacer lo siguiente. ¡Esta es una solución compleja que combina las mejores prácticas no solo de la codificación, sino también de la ingeniería de software!

  1. Convenza a su jefe de que la convención de nomenclatura sobre controladores de señal es algo bueno. Proponer, por ejemplo, un Hungarian notation, y decir que fue utilizado en Microsoft con gran éxito. Por lo tanto, todos los controladores de señal comenzarán con sighnd, como sighndInterrupt.
  2. Su función que detecta contexto manejo de señales que hacer lo siguiente:
    1. Obtener el backtrace().
    2. Mira si alguna de las funciones en ella comienza con sighnd.... Si lo hace, felicitaciones, ¡estás dentro de un manejador de señal!
    3. De lo contrario, no es así.
  3. Trate de evitar trabajar con Jimmy en la misma empresa. "Puede haber solo uno", ya sabes.
+0

Vamos a hacer que un conserje pase por encima de la base de código e informe de todas y cada una de las funciones a las que llaman desde controladores de señal. Ya estoy encogido. – smcdow

+0

@smcdow, dicho sea de paso, podría emplear una herramienta de análisis estático para eso. Pero, una vez más, tendría que anotar cada manejador de señal, lo que hace que la solución propuesta no sea más compleja. :-) –

+0

hace un tiempo había una herramienta Valgrind para buscar problemas con el manejador de señales, se llamaba crocus. – ninjalj

0

de código optimizado en -O2 o mejor (ISTR) han encontrado necesidad de añadir -fno-omitir-frame-pointer

demás gcc optimizará la información de contexto pila

3

Si podemos asumir que su la aplicación no bloquea manualmente las señales usando sigprocmask() o pthread_sigmask(), entonces esto es bastante simple: obtenga su ID de hilo actual (tid). Abra /proc/tid/status y obtenga los valores para SigBlk y SigCgt. AND esos dos valores. Si el resultado de ese AND es distinto de cero, entonces ese hilo se está ejecutando actualmente desde dentro de un manejador de señal. Lo he probado yo mismo y funciona.

+0

Necesitará la ID del proceso (PID), no la ID del hilo. Y hacer esto implicará llamar a funciones que no sean de señal asíncrona, salvo que usted escriba. – mgarey

Cuestiones relacionadas