Tuesday, April 30, 2013

Segmentation fault

Getting a segmentation fault out of no where after a long execution can be frustrating. One reason is that you usually do not get any information on the source of error. Another reason is that using valgrind or gdb to debug will make the program run even slower until you hit the error, if you hit it again. Frustrating indeed. But not all hope lost. You can modify your program to catch the segmentation fault and generate the needed debugging information for you (only for POSIX-compliant operating systems – that is like every operating system in the world, except Windows).

Now when my program generates a segmentation fault that's what I get as output:

Segmentation fault: Address not mapped to object 
Invalid read of the address 0x0

Stack trace: 
 1: 0x103432c37 <attack_dataset int="" x2b7=""> (/Users/malaggan/Desktop/mac-only-ws/SVDAttack/jni/./svdattack)
 2: 0x103498f8b <main x9b=""> (/Users/malaggan/Desktop/mac-only-ws/SVDAttack/jni/./svdattack)

Line number: 
got symbolicator for svdattack, base address 100000000
attack_dataset(int) (in svdattack) (attack_dataset.cpp:104)

How it is done is fairly simple. When a segmentation fault happens, the operating system sends a signal to the program. The program, in turn, can handle this signal and act based on it, via a signal handler. The signal handler is a function that is called by the operating system, to which it also provide useful information for debugging (and a lot more, like user-space threading, and copy-on-write, and other things). The entire state of the machine, including its general registers, and floating point registers, are recorded and sent to the signal handler. The one register we are interested about is the instruction pointer register, which contains the address of the instruction which caused the segmentation fault. Along with that also goes the address that the program was trying to access, and whether the program was trying to read it or write it. The operating system also gives information about that address; whether it is a totally wrong address (not mapped in the program's address space), or a valid address but the program has no permission to read (or write) it.

The code I wrote to generate the message above goes next (the portion about the stack trace was taken from stackoverflow). It is written in C++11 and the machine-specific part is for Mac.


#include <sys/ucontext.h>
#include <dlfcn.h>
#include <cxxabi.h>
#include <unistd.h>


void print(int sig, siginfo_t *info, void *c)
{
    ucontext_t *context = reinterpret_cast<ucontext_t*>(c);

    // defined in mach/i386/_structs.h
    __darwin_mcontext64* mc = context->uc_mcontext;
    __darwin_x86_thread_state64* ss = &mc->__ss;
    __darwin_x86_exception_state64* es = &mc->__es;
    __darwin_x86_float_state64* fs = &mc->__fs;

    bool write_fault = (es->__err & 2) ? true : false;

    fprintf(stderr,"\n%s: %s \n"
        "Invalid %s of the address %p\n",
        sys_siglist[sig],
        (info->si_code == SEGV_MAPERR) ? "Address not mapped to object" : "Invalid permissions for mapped object",
        (write_fault)?"write":"read",
        es->__faultvaddr
    );

    // http://stackoverflow.com/questions/5397041/getting-the-saved-instruction-pointer-address-from-a-signal-handler
    void **ip = reinterpret_cast<void**>(ss->__rip);
    void **bp = reinterpret_cast<void**>(ss->__rbp);

    fprintf(stderr,"\nStack trace: \n");

    Dl_info dlinfo;
    int f = 0;
    while(bp && ip) 
    {
        if(!dladdr(ip, &dlinfo))
            break; 

        const char *symname = dlinfo.dli_sname;
 
        int status;
        char * tmp = abi::__cxa_demangle(symname, nullptr, 0, &status);

        if (status == 0 && tmp)
            symname = tmp;

        fprintf(stderr,"% 2d: %p <%s+0x%lx> (%s)\n",
                 ++f,
                 ip,
                 symname,
                 reinterpret_cast<unsigned long>>ip) - reinterpret_cast<unsigned long>(dlinfo.dli_saddr),
                 dlinfo.dli_fname);

        if (tmp)
            free(tmp);

        if(dlinfo.dli_sname && std::string(dlinfo.dli_sname) == "main")
            break;

        ip = reinterpret_cast<void**>(bp[1]);
        bp = reinterpret_cast<void**>(bp[0]);
    }

    fprintf(stderr,"\nLine number: \n"); 
    
    auto load_addr = dlinfo.dli_fbase;

    char str_pid[100]={0},
         str_rip[100]={0}, 
         str_load_addr[100]={0};

    sprintf(str_pid, "%d", getpid());
    sprintf(str_rip, "%p", ss->__rip);
    sprintf(str_load_addr, "%p", load_addr);

    char *argv[]={"atos","-o", "./svdattack", "-l", str_load_addr, str_rip, nullptr};
    char *envp[] = { nullptr };
    execve("/usr/bin/atos", argv, envp);

    fprintf(stderr,"\n\n");

    std::exit(1);
}



/*this is how the signal was registered*/
struct sigaction sa;
sigemptyset (&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = print;

sigaction(SIGSEGV, &sa, nullptr);
sigaction(SIGBUS, &sa, nullptr);


No comments: