viernes, 7 de diciembre de 2012

Amosando a traza dun programa cando da `Segmentation fault' [C][Gnu/Linux]

Uns días atras falaramos das sinais de Unix, unha de elas era SIGSEGV, que aparecía cando se producía un faio de segmento (é dicir, que o programa rompeu bastante), hoxe ímos ver como aproveita-la sinal para poder busca-lo erro.

O primeiro que precisamos é unha aplicación base cun faio de segmentación:
#include <stdio.h>
#include <stdlib.h>

// Produce segfault
void fail(){
    int *asd = NULL;
    *asd = 7;
}


int main(int argc, char **argv){
    fail();

    return 0;
}

E, claramente, faia...


Agora ímos cazar o sinal do erro, pero só amosaremos unha mensaxe e sairemos, para isto usamos a función signal:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void segfault_handler(int signal){
    printf("Faio de segmentación!\n");

    abort();
}


// Produce segfault
void fail(){
    int *asd = NULL;
    *asd = 7;
}


int main(int argc, char **argv){
    signal(SIGSEGV, segfault_handler);

    fail();

    return 0;
} 

Ímos entón a amosa-la traza, para isto tiraremos da familia de funcións backtrace:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

#include <execinfo.h>

#define BUFFER_SIZE 128

void show_backtrace(){
    void *buffer[128];
    char **symbols;

    int ptr_num = backtrace(buffer, BUFFER_SIZE);
    int i;

    symbols = backtrace_symbols(buffer, ptr_num);
    if (symbols == NULL){
        perror("backtrace_symbols");
        return;
    }

    for(i = 0; i < ptr_num; i++){
        printf("%i:\t %s\n", i, symbols[i]);
    }

    free(symbols);
}

void segfault_handler(int signal){
    printf("Faio de segmentación, amosando traza:\n\n");
    show_backtrace();

    abort();
}


// Produce segfault
void fail(){
    int *asd = NULL;
    *asd = 7;
}


int main(int argc, char **argv){
    signal(SIGSEGV, segfault_handler);

    fail();

    return 0;
}

Nota: sempre podemos obter máis información se compilamos cos flags -g -rdynamic.


Nomes de funcións... xa é algo máis, pero a opción sinxela é simplemente delegar nun debugger, se témo-lo PID do proceso podemos facerlle attach por exemplo dende GDB, e podemos facerlle o que sexa :
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void segfault_handler(int signal){
    printf("Faio de segmentación, preme [enter] para finalizar\n");
    printf("pid: %i\n", getpid());
    getchar();

    abort();
}


// Produce segfault
void fail(){
    int *asd = NULL;
    *asd = 7;
}


int main(int argc, char **argv){
    signal(SIGSEGV, segfault_handler);

    fail();

    return 0;
}


ps: Para quen lle interese, fío en StackOverflow sobre o backtrace co glib.

Espero que resulte útil, saúdos.

3 comentarios:

  1. Interesante! Ocurreseme algún uso para facer hooking grazas tamén a LD_Preload e con isto saber por onde veñen posibeis ataques. Sería viábel?

    Un saúdo!

    ResponderEliminar
  2. Oh, certamente sería algo moi práctico, o detalle sería a que función facerllo (pódese facer a todo o programa?).

    Ou seguindo con esa idea quizais se poda facer un programa "envoltorio" que prepare as sinais de despois faga execve e deixe paso o código final, pero non teño claro se esta accion limpará os sinais ou a función de trazado.

    Parece unha posibilidade que compre explorar, grazas polo alimento para o cerebro ;)

    ResponderEliminar
  3. Boas de novo, pois a min a primeira idea que me viñera á cabeza era a de hookear por exemplo strcpy para detectar posibles overflows, fopen para detectar determinadas intromisións e así con calqueira idea bizarra. O de programa envoltorio tampouco sei moi ben como funcionaría, eu con estes temas aínda son e serei un eterno newbie.

    Un saúdo!

    ResponderEliminar