Skip to content

Instantly share code, notes, and snippets.

@tony2001
Last active August 29, 2015 14:07
Show Gist options
  • Save tony2001/23508a8d08800974213a to your computer and use it in GitHub Desktop.
Save tony2001/23508a8d08800974213a to your computer and use it in GitHub Desktop.
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <execinfo.h>
#include <ucontext.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/limits.h>
#include <sys/syscall.h>
#include <errno.h>
#include <pthread.h>
#include <sys/wait.h>
#ifndef PR_SET_PTRACER
# define PR_SET_PTRACER 0x59616d61
# define PR_SET_PTRACER_ANY ((unsigned long)-1)
#endif
static char* homedir;
static int is_debugger_present(void) {
int fd = open("/proc/self/status", O_RDONLY);
if (fd == -1)
return 0;
char buf[1024];
int debugger_pid = 0;
ssize_t num_read = read(fd, buf, sizeof(buf));
if (num_read > 0) {
static const char TracerPid[] = "TracerPid:";
char *tracer = strstr(buf, TracerPid);
if (tracer)
debugger_pid = atoi(tracer + sizeof(TracerPid) - 1);
}
close(fd);
return debugger_pid != 0;
}
static volatile sig_atomic_t is_debugger_active;
static void trap_sigaction(int signum, siginfo_t *info, void* ptr) {
is_debugger_active = 0;
}
static void backtrace_sigaction(int signum, siginfo_t *info, void* ptr) {
void *array[42];
size_t size;
void * caller_address;
ucontext_t *uc = (ucontext_t *) ptr;
/* get all entries on the stack */
size = backtrace(array, 42);
/* Get the address at the time the signal was raised */
#if defined(__i386__)
caller_address = (void *) uc->uc_mcontext.gregs[REG_EIP]; // EIP: x86 specific
#elif defined(__x86_64__)
caller_address = (void *) uc->uc_mcontext.gregs[REG_RIP]; // RIP: x86_64 specific
#else
# error Unsupported architecture.
#endif
int should_die = 0;
switch(info->si_signo) {
case SIGABRT:
if (info->si_pid == getpid())
should_die = 1;
case SIGBUS:
case SIGFPE:
case SIGILL:
case SIGSEGV:
#ifndef SI_FROMKERNEL
# define SI_FROMKERNEL(info) (info->si_code > 0)
#endif
if (SI_FROMKERNEL(info))
should_die = 1;
}
char time_buf[64];
time_t t = time(NULL);
strftime(time_buf, sizeof(time_buf), "%F-%H%M%S", localtime(&t));
char name_buf[PATH_MAX];
int fd = -1;
if (snprintf(name_buf, sizeof(name_buf), "%s/lmdb-backtrace.%s-%i.log%c",
homedir ? homedir : ".", time_buf, getpid(), 0) > 0)
fd = open(name_buf, O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd < 0) {
if (homedir)
fd = open(strrchr(name_buf, '/') + 1, O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd < 0)
fd = STDERR_FILENO;
dprintf(fd, "\n\n*** Unable create \"%s\": %s!", name_buf, strerror(errno));
}
dprintf(fd, "\n\n*** Signal %d (%s), address is %p from %p\n", signum, strsignal(signum),
info->si_addr, (void *)caller_address);
int n = readlink("/proc/self/exe", name_buf, sizeof(name_buf) - 1);
if (n > 0) {
name_buf[n] = 0;
dprintf(fd, " Executable file %s\n", name_buf);
} else {
dprintf(fd, " Unable read executable name: %s\n", strerror(errno));
strcpy(name_buf, "unknown");
}
void** actual = array;
int frame = 0;
for(n = 0; n < size; ++n)
if (array[n] == caller_address) {
frame = n;
actual = array + frame;
size -= frame;
break;
}
dprintf(fd, "\n*** Backtrace by glibc:\n");
backtrace_symbols_fd(actual, size, fd);
int mem_map_fd = open("/proc/self/smaps", O_RDONLY, 0);
if (mem_map_fd >= 0) {
char buf[1024];
dprintf(fd, "\n*** Memory usage map (by /proc/self/smaps):\n");
while(1) {
int n = read(mem_map_fd, &buf, sizeof(buf));
if (n < 1)
break;
if (write(fd, &buf, n) != n)
break;
}
close(mem_map_fd);
}
signal(SIGCHLD, SIG_DFL);
if (! (signum == SIGABRT && should_die)) {
char **messages = backtrace_symbols(actual, size);
dprintf(fd, "\n*** Backtrace by addr2line:\n");
for(n = 0; n < size; ++n) {
int status, child_pid = fork();
if (! child_pid) {
char addr_buf[64];
close(STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
close(fd);
dprintf(STDOUT_FILENO, "(%d) %s: ", n, messages[n]);
sprintf(addr_buf, "%p", actual[n]);
execlp("addr2line", "addr2line", addr_buf, "-C", "-f", "-i",
/* "-p", LY: not available on RHEL6 */
"-e", name_buf, NULL);
exit(EXIT_FAILURE);
} else if (child_pid < 0 || waitpid(child_pid, &status, 0) < 0 || status != W_EXITCODE(EXIT_SUCCESS, 0)) {
dprintf(fd, "\n*** Unable complete backtrace by addr2line, sorry (%d, %d, 0x%x).\n", child_pid, errno, status);
break;
}
}
free(messages);
}
sigset_t mask, prev_mask;
if (pthread_sigmask(SIG_SETMASK, NULL, &prev_mask))
goto ballout;
struct sigaction sa, prev_sa;
sa.sa_sigaction = trap_sigaction;
sa.sa_flags = SA_RESTART | SA_NODEFER | SA_SIGINFO;
sa.sa_mask = prev_mask;
sigdelset(&sa.sa_mask, SIGTRAP);
if(sigaction(SIGTRAP, &sa, &prev_sa))
goto ballout;
sigemptyset(&mask);
sigaddset(&mask, SIGTRAP);
if(pthread_sigmask(SIG_UNBLOCK, &mask, NULL))
goto ballout;
if (is_debugger_present()) {
dprintf(fd, "*** debugger already present\n");
goto ballout;
}
is_debugger_active = 1;
if (pthread_kill(pthread_self(), SIGTRAP) < 0)
goto ballout;
if (is_debugger_active) {
dprintf(fd, "*** debugger already running\n");
goto ballout;
}
int pipe_fd[2];
if (pipe(pipe_fd)) {
pipe_fd[0] = pipe_fd[1] = -1;
goto ballout;
}
pid_t tid = syscall(SYS_gettid);
int child_pid = fork();
if (!child_pid) {
char pid_buf[16];
char tid_buf[32];
char frame_buf[16];
dup2(pipe_fd[0], STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(pipe_fd[0]);
close(pipe_fd[1]);
close(fd);
setpgid(0, 0);
setsid();
sprintf(pid_buf, "%d", getppid());
sprintf(frame_buf, "frame %d", frame);
sprintf(tid_buf, "thread find %d", tid);
dprintf(STDOUT_FILENO, "\n*** Backtrace by GDB (pid %s, tid %i frame #%d):\n", pid_buf, tid, frame);
execlp("gdb", "gdb", "-q", "-p", pid_buf, "-se", name_buf, "-n",
"-ex", "set confirm off",
"-ex", "handle SIG33 pass nostop noprint",
"-ex", "handle SIGTRAP nopass stop print",
"-ex", "continue",
NULL);
dprintf(STDOUT_FILENO, "\n*** Sorry, GDB launch failed: %s\n", strerror(errno));
fsync(STDOUT_FILENO);
kill(getppid(), SIGKILL);
} else if (child_pid > 0) {
if(prctl(PR_SET_PTRACER, child_pid /* PR_SET_PTRACER_ANY */, 0, 0, 0) < 0)
goto ballout;
close(pipe_fd[0]);
pipe_fd[0] = -1;
if(0 >= dprintf(pipe_fd[1],
"info threads\n"
"thread find %d\n"
"frame %d\n"
"thread\n"
"backtrace\n"
"info all-registers\n"
"disassemble\n"
"backtrace full\n"
"info sharedlibrary\n"
"info threads\n"
"thread apply all bt\n"
"thread apply all backtrace full\n"
"thread apply all disassemble\n"
"thread apply all info all-registers\n"
"shell uname -a\n"
"show environment\n"
"%s\n"
"quit\n",
tid, (should_die && SI_FROMKERNEL(info)) ? frame : frame + 1, should_die ? "kill" : "detach"))
goto ballout;
time_t timeout;
is_debugger_active = -1;
for(timeout = time(NULL) + 11; waitpid(child_pid, NULL, WNOHANG) == 0
&& time(NULL) < timeout; usleep(10 * 1000)) {
if (! is_debugger_present())
continue;
if (is_debugger_active < 0) {
dprintf(fd, "\n*** GDB 7.8 may hang here - this is just a bug, sorry.\n");
fsync(fd);
}
if (should_die && SI_FROMKERNEL(info)) {
/* expect kernel kill us again... */
dprintf(fd, "\n*** Expect kernel kill us again...\n");
return;
}
is_debugger_active = 1;
if (pthread_kill(pthread_self(), SIGTRAP) < 0)
break;
sched_yield();
if (is_debugger_active)
goto done;
}
}
ballout:
dprintf(fd, "\n*** Unable complete backtrace by GDB, sorry.\n");
done:
if (should_die) {
fsync(fd);
exit(EXIT_FAILURE);
}
if (child_pid > 0) {
dprintf(fd, "\n*** Waitfor GDB done.\n");
waitpid(child_pid, NULL, 0);
}
if (pipe_fd[0] >= 0)
close(pipe_fd[0]);
if (pipe_fd[1] >= 0)
close(pipe_fd[1]);
dprintf(fd, "\n*** No reason for die, continue running.\n");
fsync(fd);
close(fd);
sigaction(SIGTRAP, &prev_sa, NULL);
pthread_sigmask(SIG_SETMASK, &prev_mask, NULL);
}
static int enabled;
int backtrace_get_enable() {
return enabled;
}
void backtrace_set_dir( const char* path ) {
free(homedir);
homedir = path ? strdup(path) : NULL;
}
void backtrace_set_enable( int value )
{
if (enabled != (value != 0)) {
struct sigaction sa;
if (value) {
sa.sa_sigaction = backtrace_sigaction;
sa.sa_flags = SA_SIGINFO;
sigfillset(&sa.sa_mask);
sigdelset(&sa.sa_mask, SIGCONT);
sigdelset(&sa.sa_mask, SIGTRAP);
sigdelset(&sa.sa_mask, SIGTERM);
} else {
sa.sa_handler = SIG_DFL;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
}
sigaction(SIGSEGV, &sa, NULL);
sigaction(SIGBUS, &sa, NULL);
sigaction(SIGILL, &sa, NULL);
sigaction(SIGFPE, &sa, NULL);
sigaction(SIGABRT, &sa, NULL);
sa.sa_flags |= SA_RESTART;
sigaction(SIGTRAP, &sa, NULL);
enabled = ! enabled;
}
if (0 && enabled) {
// raise(SIGTRAP);
*(unsigned*)(0xDEADBEEF) = 0xFACEFEED;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment