2011-02-25 12 views
6

Estoy interesado en un manejador de señal que pueda identificar la dirección de la instrucción que causó el problema.Obtener la dirección de error que generó una señal UNIX

que sé sobre siginfo_t y __builtin_return_address y ninguno parece funcionar:

#include <iostream> 
#include <signal.h> 

void handler (int, siginfo_t *, void *); 

int main() 
{ 
begin: 
    std :: cerr << &&begin << " ~ " << &&before << " ~ " << &&after << "\n"; 

    struct sigaction s; 
    s .sa_flags = SA_SIGINFO; 
    sigemptyset (& s .sa_mask); 
    s .sa_sigaction = handler; 
    sigaction (SIGSEGV, &s, NULL); 

    int * i = NULL; 
before: 
    *i = 0; 
after: 
    std :: cout << "End.\n"; 
} 

void handler (int, siginfo_t *si, void *) 
{ 
    std :: cerr << "si:" << si -> si_addr << "\n"; 
    std :: cerr << "At: " << __builtin_return_address (0) << "\n"; 
    std :: cerr << "At: " << __builtin_return_address (1) << "\n"; 
    std :: cerr << "At: " << __builtin_return_address (2) << "\n"; 
    std :: cerr << "At: " << __builtin_return_address (3) << "\n"; 
    std :: cerr << "At: " << __builtin_return_address (4) << "\n"; 
    std :: cerr << "At: " << __builtin_return_address (5) << "\n"; 
} 

Esto da salida a algo como:

0x10978 ~ 0x10a4c ~ 0x10a54 
si:0 
At: 0xfb945364 
At: 0xfb939e64 
At: 0x10a40 
At: 0x10740 
At: 0 
At: Segmentation Fault 

Así siginfo_t es nulo y __builtin_return_address está dando valores en algún lugar entre la etiquetas nombradas.

me esperaba tanto de estos para devolver el valor de &&before. ¿Estoy usando estas funciones correctamente?

probado en Linux y SunOS 2.6.9-89.0.9.Elsmp.

+0

Esta [respuesta] (http://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes/1925461#1925461) a [Cómo generar una stacktrace cuando mi aplicación gcc C++ se cuelga] (http://stackoverflow.com/q/77005/203667) puede ser útil. – jschmier

+0

Recuerde que otro proceso puede enviar una señal a este proceso, por lo que la 'dirección de falla' puede no decirle nada significativo. OTOH, ese es un evento bastante improbable. –

+0

@Jonathan: con señales en tiempo real ('sigaction' con' SA_SIGINFO' y 'siginfo_t'), se requiere que el kernel proteja contra la falsificación. Hay un campo en 'siginfo_t' que le dice a la fuente de la señal, y' sigqueue' no puede falsificar la fuente como kernel. –

Respuesta

7

El tercer argumento a un controlador instalado con SA_SIGINFO (a la declarada como void *) es un puntero a una estructura ucontext_t. Los contenidos de esta estructura son específicos de la arquitectura y del sistema operativo y no forman parte de ningún estándar, pero incluyen la información que necesita. He aquí una versión de su programa adaptado para usarlo (Linux/x86-64 específica; que se necesidad #ifdef s para todas las arquitecturas y OS de interés):

#define _GNU_SOURCE 1 
#include <iostream> 
#include <iomanip> 
#include <signal.h> 
#include <ucontext.h> 

using std::cout; 

static volatile int *causecrash; 

static void handler(int, siginfo_t *si, void *ptr) 
{ 
    ucontext_t *uc = (ucontext_t *)ptr; 

    cout << "si:" << si->si_addr << '\n'; 
    cout << "ip:" << std::hex << uc->uc_mcontext.gregs[REG_RIP] << '\n'; 
} 

int main() 
{ 
begin: 
    cout.setf(std::ios::unitbuf); 
    cout << &&begin << " ~ " << &&before << " ~ " << &&after << '\n'; 

    struct sigaction s; 
    s.sa_flags = SA_SIGINFO|SA_RESETHAND; 
    s.sa_sigaction = handler; 
    sigemptyset(&s.sa_mask); 
    sigaction(SIGSEGV, &s, 0); 

before: 
    *causecrash = 0; 
after: 
    cout << "End.\n"; 
} 

Por cierto, GCC tiene esta mala costumbre de mover etiquetas cuya dirección se toma pero no se usa en una operación de transferencia de control (hasta donde puede ver). Compare:

$ g++ -O0 -W -Wall test.cc && ./a.out 
0x400a30 ~ 0x400acd ~ 0x400ada 
si:0 
ip:400ad4 
Segmentation fault 
$ g++ -O2 -W -Wall test.cc && ./a.out 
0x4009f0 ~ 0x4009f0 ~ 0x4009f0 
si:0 
ip:400ab4 
Segmentation fault 

Vea cómo todas las etiquetas están en la misma dirección en la versión optimizada? Eso va a frustrar cualquier intento, por ejemplo, de recuperarse de la falla ajustando la PC. IIRC hay una manera de hacer que GCC no haga eso, pero no sé qué es y no pude encontrarlo en el manual.

+2

Tratando de recuperarse de la falla mediante el ajuste de la PC se está entrando en un terreno de comportamiento peligroso e indefinido. Puede intentar 'longjmp' con una pieza conocida de código, pero incluso eso podría estar lleno de peligros; la mejor opción realmente es tirar el núcleo y morir. –

+0

@Adam: puede 'mmap' algo nuevo en la parte superior de la dirección del acceso fallido, luego regresar. Esta podría ser una solución viable para 'SIGSEGV' o' SIGBUS' de archivos truncados, overcommit, etc. –

+0

Es cierto, pero al mismo tiempo, es algo que la gente realmente hace y que a menudo puede hacer un trabajo lo suficientemente confiable para sus propósitos. Por ejemplo, las bases de datos persistentes de objetos y las barreras de escritura GC a menudo extraen tales trucos. (Sin embargo, no conozco a nadie que confíe en las extensiones de dirección de etiqueta de GCC.) – zwol

1

siginfo_t no va a funcionar porque it contains the memory address which was accessed, no la dirección de la instrucción que lo hizo.

Ahora, el __builtin_return_address es interesante. En mi máquina, devuelve algunas tonterías:

0x40089f ~ 0x400935 ~ 0x40093f 
si:0 
At: 0x7fe22916fc20 
At: 0x7fe22915ad8e 

No tengo ni idea de por qué. Pero luego examiné el vaciado de memoria:

(gdb) bt 
#0 0x00000000004009ff in handler(int, siginfo*, void*)() 
#1 <signal handler called> 
#2 0x0000000000400939 in main() 

Como se puede ver, al igual que en su caso, la dirección de la delincuencia está en algún lugar entre ubicaciones de las etiquetas. Sin embargo, esto se explica fácilmente. Basta con mirar el desmontaje de main():

(gdb) disas 
Dump of assembler code for function main: 
    ... 
    ; the label is here: 
    0x0000000000400935 <+161>: mov -0x8(%rbp),%rax 
=> 0x0000000000400939 <+165>: movl $0x0,(%rax) 
    0x000000000040093f <+171>: mov $0x400c32,%esi 

La instrucción etiquetada consta de varias instrucciones. El primero carga la dirección en el registro RAX. Se completa con éxito porque no tiene nada de malo. Es el segundo que accede a la dirección y se rompe. Esto explica por qué la dirección en su trazado es un poco diferente de la dirección de la etiqueta, aunque el código probablemente será diferente de mi ejemplo. Todo esto no explica por qué el __builtin_return_address no tiene sentido en mi caso, sin embargo.

+0

'__builtin_return_address', cuando se llama con cualquier argumento distinto de cero, asume la existencia de una cadena frame-pointer. Esto no es requerido por x86-64, e incluso si fuerza al compilador a generar uno, el marco especial de "llamador de manejador de señal" que el kernel fabrica romperá la cadena. – zwol

Cuestiones relacionadas