It’s kind of annoying to have to go back and run GDB after your program crashes. Here’s a signal handler I’ve been using which drops you into GDB straight away:
static void gdb_sighandler(int sig, siginfo_t *info) { char exe[256]; if (readlink("/proc/self/exe", exe, sizeof(exe)) < 0) { perror("readlink"); exit(EXIT_FAILURE); } char pid[16]; snprintf(pid, sizeof(pid), "%d", getpid()); pid_t p = fork(); if (p == 0) { execl("/usr/bin/gdb", "gdb", "-ex", "cont", exe, pid, NULL); perror("execl"); exit(EXIT_FAILURE); } else if (p < 0) { perror("fork"); exit(EXIT_FAILURE); } else { // Allow a little time for GDB to start before // dropping into the default signal handler sleep(1); signal(sig, SIG_DFL); } }
This probably breaks all sorts of rules on what you shouldn’t do in a signal handler but hey, it was going to crash anyway. Resetting the handler to SIG_DFL at the end is important or you’ll end up stuck in a loop when you try to quit GDB. Install it for all the unpleasant signals like this:
struct sigaction sa; sa.sa_sigaction = (void*)gdb_sighandler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART | SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGUSR1, &sa, NULL); sigaction(SIGFPE, &sa, NULL); sigaction(SIGBUS, &sa, NULL); sigaction(SIGILL, &sa, NULL); sigaction(SIGABRT, &sa, NULL);
It’s probably worth checking whether GDB is already running before you do this. On Linux you can check this by trying to ptrace yourself: if it succeeds then a debugger isn’t attached.
static int is_debugger_running(void) { pid_t pid = fork(); if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } else if (pid == 0) { int ppid = getppid(); if (ptrace(PTRACE_ATTACH, ppid, NULL, NULL) == 0) { waitpid(ppid, NULL, 0); ptrace(PTRACE_CONT, NULL, NULL); ptrace(PTRACE_DETACH, getppid(), NULL, NULL); exit(0); } else exit(1); } else { int status; waitpid(pid, &status, 0); return WEXITSTATUS(status); } }