Last active
August 29, 2015 14:07
-
-
Save tony2001/23508a8d08800974213a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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