/* * CINELERRA * Copyright (C) 1997-2014 Adam Williams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "bcsignals.h" #include "bcwindowbase.h" #include "bckeyboard.h" #include #include #include #include #include #include #include #include #include #include #include #include BC_Signals* BC_Signals::global_signals = 0; static int signal_done = 0; static int table_id = 0; static bc_locktrace_t* new_bc_locktrace(void *ptr, const char *title, const char *location) { bc_locktrace_t *result = (bc_locktrace_t*)malloc(sizeof(bc_locktrace_t)); result->ptr = ptr; result->title = title; result->location = location; result->is_owner = 0; result->id = table_id++; result->tid = pthread_self(); return result; } static struct sigaction old_segv = {0, }, old_intr = {0, }; static void handle_dump(int n, siginfo_t * info, void *sc); const char *BC_Signals::trap_path = 0; void *BC_Signals::trap_data = 0; void (*BC_Signals::trap_hook)(FILE *fp, void *data) = 0; bool BC_Signals::trap_sigsegv = false; bool BC_Signals::trap_sigintr = false; static void uncatch_sig(int sig, struct sigaction &old) { struct sigaction act; sigaction(sig, &old, &act); old.sa_handler = 0; } static void catch_sig(int sig, struct sigaction &old) { struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_sigaction = handle_dump; act.sa_flags = SA_SIGINFO; sigaction(sig, &act, (!old.sa_handler ? &old : 0)); } static void uncatch_intr() { uncatch_sig(SIGINT, old_intr); } static void catch_intr() { catch_sig(SIGINT, old_intr); } static void uncatch_segv() { uncatch_sig(SIGSEGV, old_segv); } static void catch_segv() { catch_sig(SIGSEGV, old_segv); } void BC_Signals::set_trap_path(const char *path) { trap_path = path; } void BC_Signals::set_trap_hook(void (*hook)(FILE *fp, void *vp), void *data) { trap_data = data; trap_hook = hook; } void BC_Signals::set_catch_segv(bool v) { if( v == trap_sigsegv ) return; if( v ) catch_segv(); else uncatch_segv(); v = trap_sigsegv; } void BC_Signals::set_catch_intr(bool v) { if( v == trap_sigintr ) return; if( v ) catch_intr(); else uncatch_intr(); v = trap_sigintr; } typedef struct { int size; void *ptr; const char *location; } bc_buffertrace_t; static bc_buffertrace_t* new_bc_buffertrace(int size, void *ptr, const char *location) { bc_buffertrace_t *result = (bc_buffertrace_t*)malloc(sizeof(bc_buffertrace_t)); result->size = size; result->ptr = ptr; result->location = location; return result; } static void bc_copy_textfile(FILE *ofp, const char *fmt,...) { va_list ap; va_start(ap, fmt); char bfr[BCTEXTLEN]; vsnprintf(bfr, sizeof(bfr), fmt, ap); va_end(ap); FILE *ifp = fopen(bfr,"r"); if( !ifp ) return; while( fgets(bfr,sizeof(bfr),ifp) ) fputs(bfr,ofp); fclose(ifp); } // Need our own table to avoid recursion with the memory manager typedef struct { void **values; int size; int allocation; // This points to the next value to replace if the table wraps around int current_value; } bc_table_t; static void* append_table(bc_table_t *table, void *ptr) { if(table->allocation <= table->size) { if(table->allocation) { int new_allocation = table->allocation * 2; void **new_values = (void**)calloc(new_allocation, sizeof(void*)); memcpy(new_values, table->values, sizeof(void*) * table->size); free(table->values); table->values = new_values; table->allocation = new_allocation; } else { table->allocation = 4096; table->values = (void**)calloc(table->allocation, sizeof(void*)); } } table->values[table->size++] = ptr; return ptr; } // Replace item in table pointed to by current_value and advance // current_value static void* overwrite_table(bc_table_t *table, void *ptr) { free(table->values[table->current_value]); table->values[table->current_value++] = ptr; if(table->current_value >= table->size) table->current_value = 0; return 0; } static void clear_table(bc_table_t *table, int delete_objects) { if(delete_objects) { for(int i = 0; i < table->size; i++) { free(table->values[i]); } } table->size = 0; } static void clear_table_entry(bc_table_t *table, int number, int delete_object) { if(delete_object) free(table->values[number]); for(int i = number; i < table->size - 1; i++) { table->values[i] = table->values[i + 1]; } table->size--; } // Table of functions currently running. static bc_table_t execution_table = { 0, 0, 0, 0 }; // Table of locked positions static bc_table_t lock_table = { 0, 0, 0, 0 }; // Table of buffers static bc_table_t memory_table = { 0, 0, 0, 0 }; static bc_table_t temp_files = { 0, 0, 0, 0 }; // Can't use Mutex because it would be recursive static pthread_mutex_t *lock = 0; static pthread_mutex_t *handler_lock = 0; // incase lock set after task ends static pthread_t last_lock_thread = 0; static const char *last_lock_title = 0; static const char *last_lock_location = 0; // Don't trace memory until this is true to avoid initialization static int trace_memory = 0; static const char* signal_titles[] = { "NULL", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM" }; void BC_Signals::dump_stack(FILE *fp) { void *buffer[256]; int total = backtrace (buffer, 256); char **result = backtrace_symbols (buffer, total); fprintf(fp, "BC_Signals::dump_stack\n"); for(int i = 0; i < total; i++) { fprintf(fp, "%s\n", result[i]); } } // Kill subprocesses void BC_Signals::kill_subs() { // List /proc directory DIR *dirstream; struct dirent64 *new_filename; struct stat ostat; char path[BCTEXTLEN]; char string[BCTEXTLEN]; dirstream = opendir("/proc"); if(!dirstream) return; while( (new_filename = readdir64(dirstream)) != 0 ) { // All digits are numbers char *ptr = new_filename->d_name; int got_alpha = 0; while(*ptr) { if(*ptr == '.' || isalpha(*ptr++)) { got_alpha = 1; break; } } if(got_alpha) continue; // Must be a directory sprintf(path, "/proc/%s", new_filename->d_name); if(!stat(path, &ostat)) { if(S_ISDIR(ostat.st_mode)) { // Read process stat strcat(path, "/stat"); //printf("kill_subs %d %s\n", __LINE__, path); FILE *fd = fopen(path, "r"); // Must search forwards because the file is 0 length if(fd) { while(!feof(fd)) { char c = fgetc(fd); //printf("kill_subs %d %d\n", __LINE__, c); if(c == ')') { // Search for 2 spaces int spaces = 0; while(!feof(fd) && spaces < 2) { c = fgetc(fd); if(c == ' ') spaces++; } // Read in parent process ptr = string; while(!feof(fd)) { *ptr = fgetc(fd); if(*ptr == ' ') { *ptr = 0; break; } ptr++; } // printf("kill_subs %d process=%d getpid=%d parent_process=%d\n", // __LINE__, // atoi(new_filename->d_name), // getpid(), // atoi(string)); int parent_process = atoi(string); int child_process = atoi(new_filename->d_name); // Kill if we're the parent if(getpid() == parent_process) { //printf("kill_subs %d: process=%d\n", __LINE__, atoi(new_filename->d_name)); kill(child_process, SIGKILL); } } } fclose(fd); } } } } } static void signal_entry(int signum) { signal(signum, SIG_DFL); pthread_mutex_lock(handler_lock); if(signal_done) { pthread_mutex_unlock(handler_lock); exit(0); } signal_done = 1; pthread_mutex_unlock(handler_lock); printf("signal_entry: got %s my pid=%d execution table size=%d:\n", signal_titles[signum], getpid(), execution_table.size); BC_Signals::kill_subs(); BC_Signals::dump_traces(); BC_Signals::dump_locks(); BC_Signals::dump_buffers(); BC_Signals::delete_temps(); // Call user defined signal handler BC_Signals::global_signals->signal_handler(signum); abort(); } static void signal_entry_recoverable(int signum) { printf("signal_entry_recoverable: got %s my pid=%d\n", signal_titles[signum], getpid()); } // used to terminate child processes when program terminates static void handle_exit(int signum) { //printf("child %d exit\n", getpid()); exit(0); } void BC_Signals::set_sighup_exit(int enable) { if( enable ) { // causes SIGHUP to be generated when parent dies signal(SIGHUP, handle_exit); prctl(PR_SET_PDEATHSIG, SIGHUP, 0,0,0); // prevents ^C from signalling child when attached to gdb setpgid(0, 0); if( isatty(0) ) ioctl(0, TIOCNOTTY, 0); } else { signal(SIGHUP, signal_entry); prctl(PR_SET_PDEATHSIG, 0,0,0,0); } } BC_Signals::BC_Signals() { } void BC_Signals::dump_traces(FILE *fp) { // Dump trace table if(execution_table.size) { for(int i = execution_table.current_value; i < execution_table.size; i++) fprintf(fp," %s\n", (char*)execution_table.values[i]); for(int i = 0; i < execution_table.current_value; i++) fprintf(fp," %s\n", (char*)execution_table.values[i]); } } void BC_Signals::dump_locks(FILE *fp) { // Dump lock table #ifdef TRACE_LOCKS fprintf(fp,"signal_entry: lock table size=%d\n", lock_table.size); for(int i = 0; i < lock_table.size; i++) { bc_locktrace_t *table = (bc_locktrace_t*)lock_table.values[i]; fprintf(fp," %p %s %s %p%s\n", table->ptr, table->title, table->location, (void*)table->tid, table->is_owner ? " *" : ""); } #endif } void BC_Signals::dump_buffers(FILE *fp) { #ifdef TRACE_MEMORY pthread_mutex_lock(lock); // Dump buffer table fprintf(fp,"BC_Signals::dump_buffers: buffer table size=%d\n", memory_table.size); for(int i = 0; i < memory_table.size; i++) { bc_buffertrace_t *entry = (bc_buffertrace_t*)memory_table.values[i]; fprintf(fp," %d %p %s\n", entry->size, entry->ptr, entry->location); } pthread_mutex_unlock(lock); #endif } void BC_Signals::delete_temps() { pthread_mutex_lock(lock); if(temp_files.size) printf("BC_Signals::delete_temps: deleting %d temp files\n", temp_files.size); for(int i = 0; i < temp_files.size; i++) { printf(" %s\n", (char*)temp_files.values[i]); remove((char*)temp_files.values[i]); } pthread_mutex_unlock(lock); } void BC_Signals::reset_locks() { pthread_mutex_unlock(lock); } void BC_Signals::set_temp(char *string) { char *new_string = strdup(string); append_table(&temp_files, new_string); } void BC_Signals::unset_temp(char *string) { for(int i = 0; i < temp_files.size; i++) { if(!strcmp((char*)temp_files.values[i], string)) { clear_table_entry(&temp_files, i, 1); break; } } } int BC_Signals::x_error_handler(Display *display, XErrorEvent *event) { char string[1024]; XGetErrorText(event->display, event->error_code, string, 1024); fprintf(stderr, "BC_Signals::x_error_handler: error_code=%d opcode=%d,%d %s\n", event->error_code, event->request_code, event->minor_code, string); return 0; } void BC_Signals::initialize() { BC_Signals::global_signals = this; lock = (pthread_mutex_t*)calloc(1, sizeof(pthread_mutex_t)); handler_lock = (pthread_mutex_t*)calloc(1, sizeof(pthread_mutex_t)); pthread_mutex_init(lock, 0); pthread_mutex_init(handler_lock, 0); old_err_handler = XSetErrorHandler(x_error_handler); initialize2(); } void BC_Signals::terminate() { BC_Signals::global_signals = 0; uncatch_segv(); uncatch_intr(); signal(SIGHUP, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGFPE, SIG_DFL); signal(SIGPIPE, SIG_DFL); signal(SIGUSR2, SIG_DFL); XSetErrorHandler(old_err_handler); } // callable from debugger extern "C" void dump() { BC_Signals::dump_traces(); BC_Signals::dump_locks(); BC_Signals::dump_buffers(); } // kill SIGUSR2 void BC_Signals::signal_dump(int signum) { BC_KeyboardHandler::kill_grabs(); dump(); signal(SIGUSR2, signal_dump); } void BC_Signals::initialize2() { signal(SIGHUP, signal_entry); signal(SIGINT, signal_entry); signal(SIGQUIT, signal_entry); // SIGKILL cannot be stopped // signal(SIGKILL, signal_entry); catch_segv(); signal(SIGTERM, signal_entry); signal(SIGFPE, signal_entry); signal(SIGPIPE, signal_entry_recoverable); signal(SIGUSR2, signal_dump); } void BC_Signals::signal_handler(int signum) { printf("BC_Signals::signal_handler\n"); // exit(0); } const char* BC_Signals::sig_to_str(int number) { return signal_titles[number]; } #define TOTAL_TRACES 16 void BC_Signals::new_trace(const char *text) { if(!global_signals) return; pthread_mutex_lock(lock); // Wrap around if(execution_table.size >= TOTAL_TRACES) { overwrite_table(&execution_table, strdup(text)); // clear_table(&execution_table, 1); } else { append_table(&execution_table, strdup(text)); } pthread_mutex_unlock(lock); } void BC_Signals::new_trace(const char *file, const char *function, int line) { char string[BCTEXTLEN]; snprintf(string, BCTEXTLEN, "%s: %s: %d", file, function, line); new_trace(string); } void BC_Signals::delete_traces() { if(!global_signals) return; pthread_mutex_lock(lock); clear_table(&execution_table, 0); pthread_mutex_unlock(lock); } // no canceling with lock held void BC_Signals::lock_locks(const char *s) { pthread_mutex_lock(lock); last_lock_thread = pthread_self(); last_lock_title = s; last_lock_location = 0; } void BC_Signals::unlock_locks() { pthread_mutex_unlock(lock); } #define TOTAL_LOCKS 256 int BC_Signals::set_lock(void *ptr, const char *title, const char *location) { if(!global_signals) return 0; bc_locktrace_t *table = 0; int id_return = 0; pthread_mutex_lock(lock); last_lock_thread = pthread_self(); last_lock_title = title; last_lock_location = location; if(lock_table.size >= TOTAL_LOCKS) clear_table(&lock_table, 0); // Put new lock entry table = new_bc_locktrace(ptr, title, location); append_table(&lock_table, table); id_return = table->id; pthread_mutex_unlock(lock); return id_return; } void BC_Signals::set_lock2(int table_id) { if(!global_signals) return; bc_locktrace_t *table = 0; pthread_mutex_lock(lock); for(int i = lock_table.size - 1; i >= 0; i--) { table = (bc_locktrace_t*)lock_table.values[i]; // Got it. Hasn't been unlocked/deleted yet. if(table->id == table_id) { table->is_owner = 1; table->tid = pthread_self(); pthread_mutex_unlock(lock); return; } } pthread_mutex_unlock(lock); } void BC_Signals::unset_lock2(int table_id) { if(!global_signals) return; bc_locktrace_t *table = 0; pthread_mutex_lock(lock); for(int i = lock_table.size - 1; i >= 0; i--) { table = (bc_locktrace_t*)lock_table.values[i]; if(table->id == table_id) { clear_table_entry(&lock_table, i, 1); break; } } pthread_mutex_unlock(lock); } void BC_Signals::unset_lock(void *ptr) { if(!global_signals) return; bc_locktrace_t *table = 0; pthread_mutex_lock(lock); // Take off currently held entry for(int i = 0; i < lock_table.size; i++) { table = (bc_locktrace_t*)lock_table.values[i]; if(table->ptr == ptr) { if(table->is_owner) { clear_table_entry(&lock_table, i, 1); break; } } } pthread_mutex_unlock(lock); } void BC_Signals::unset_all_locks(void *ptr) { if(!global_signals) return; pthread_mutex_lock(lock); // Take off previous lock entry for(int i = 0; i < lock_table.size; ) { bc_locktrace_t *table = (bc_locktrace_t*)lock_table.values[i]; if(table->ptr == ptr) { clear_table_entry(&lock_table, i, 1); continue; } ++i; } pthread_mutex_unlock(lock); } void BC_Signals::clear_locks_tid(pthread_t tid) { if(!global_signals) return; pthread_mutex_lock(lock); // Take off previous lock entry for(int i = 0; i < lock_table.size; ) { bc_locktrace_t *table = (bc_locktrace_t*)lock_table.values[i]; if(table->tid == tid) { clear_table_entry(&lock_table, i, 1); continue; } ++i; } pthread_mutex_unlock(lock); } void BC_Signals::enable_memory() { trace_memory = 1; } void BC_Signals::disable_memory() { trace_memory = 0; } void BC_Signals::set_buffer(int size, void *ptr, const char* location) { if(!global_signals) return; if(!trace_memory) return; //printf("BC_Signals::set_buffer %p %s\n", ptr, location); pthread_mutex_lock(lock); append_table(&memory_table, new_bc_buffertrace(size, ptr, location)); pthread_mutex_unlock(lock); } int BC_Signals::unset_buffer(void *ptr) { if(!global_signals) return 0; if(!trace_memory) return 0; int ret = 1; pthread_mutex_lock(lock); for(int i = 0; i < memory_table.size; i++) { if(((bc_buffertrace_t*)memory_table.values[i])->ptr == ptr) { //printf("BC_Signals::unset_buffer %p\n", ptr); clear_table_entry(&memory_table, i, 1); ret = 0; break; } } pthread_mutex_unlock(lock); // fprintf(stderr, "BC_Signals::unset_buffer buffer %p not found.\n", ptr); return ret; } #include #include #include "thread.h" #if __i386__ #define IP eip #endif #if __x86_64__ #define IP rip #endif #ifndef IP #error gotta have IP #endif static void handle_dump(int n, siginfo_t * info, void *sc) { uncatch_segv(); uncatch_intr(); signal(SIGSEGV, SIG_DFL); signal(SIGINT, SIG_DFL); // gotta be root, or the dump is worthless int uid = getuid(); if( uid != 0 ) return; ucontext_t *uc = (ucontext_t *)sc; int pid = getpid(), tid = gettid(); struct sigcontext *c = (struct sigcontext *)&uc->uc_mcontext; fprintf(stderr,"** %s at %p in pid %d, tid %d\n", n==SIGSEGV? "segv" : n==SIGINT? "intr" : "trap", (void*)c->IP, pid, tid); FILE *fp = 0; char fn[PATH_MAX]; if( BC_Signals::trap_path ) { snprintf(fn, sizeof(fn), BC_Signals::trap_path, pid); fp = fopen(fn,"w"); } if( fp ) { fprintf(stderr,"writing debug data to %s\n", fn); fprintf(fp,"** %s at %p in pid %d, tid %d\n", n==SIGSEGV? "segv" : n==SIGINT? "intr" : "trap", (void*)c->IP, pid, tid); } else { strcpy(fn, "stdout"); fp = stdout; } time_t t; time(&t); fprintf(fp,"created on %s", ctime(&t)); struct passwd *pw = getpwuid(uid); if( pw ) { fprintf(fp," by %d:%d %s(%s)\n", pw->pw_uid, pw->pw_gid, pw->pw_name, pw->pw_gecos); } fprintf(fp,"\nTHREADS:\n"); Thread::dump_threads(fp); fprintf(fp,"\nTRACES:\n"); BC_Signals::dump_traces(fp); fprintf(fp,"\nLOCKS:\n"); BC_Signals::dump_locks(fp); fprintf(fp,"\nBUFFERS:\n"); BC_Signals::dump_buffers(fp); if( BC_Signals::trap_hook ) { fprintf(fp,"\nMAIN HOOK:\n"); BC_Signals::trap_hook(fp, BC_Signals::trap_data); } fprintf(fp,"\nVERSION:\n"); bc_copy_textfile(fp,"/proc/version"); fprintf(fp,"\nMEMINFO:\n"); bc_copy_textfile(fp,"/proc/meminfo"); fprintf(fp,"\nMAPS:\n"); bc_copy_textfile(fp,"/proc/%d/maps",pid); fprintf(fp,"\n\n"); if( fp != stdout ) fclose(fp); char cmd[1024], *cp = cmd; cp += sprintf(cp, "exec gdb /proc/%d/exe -p %d --batch --quiet " "-ex \"thread apply all info registers\" " "-ex \"thread apply all bt full\" " "-ex \"quit\"", pid, pid); if( fp != stdout ) cp += sprintf(cp," >> \"%s\"", fn); cp += sprintf(cp," 2>&1"); //printf("handle_dump:: pid=%d, cmd='%s' fn='%s'\n",pid,cmd,fn); pid = vfork(); if( pid < 0 ) { fprintf(stderr,"** can't start gdb, dump abondoned\n"); return; } if( pid > 0 ) { waitpid(pid,0,0); fprintf(stderr,"** dump complete\n"); return; } char *const argv[4] = { (char*) "/bin/sh", (char*) "-c", cmd, 0 }; execvp(argv[0], &argv[0]); } #ifdef TRACE_MEMORY // void* operator new(size_t size) // { // //printf("new 1 %d\n", size); // void *result = malloc(size); // BUFFER(size, result, "new"); // //printf("new 2 %d\n", size); // return result; // } // // void* operator new[](size_t size) // { // //printf("new [] 1 %d\n", size); // void *result = malloc(size); // BUFFER(size, result, "new []"); // //printf("new [] 2 %d\n", size); // return result; // } // // void operator delete(void *ptr) // { // //printf("delete 1 %p\n", ptr); // UNBUFFER(ptr); // //printf("delete 2 %p\n", ptr); // free(ptr); // } // // void operator delete[](void *ptr) // { // //printf("delete [] 1 %p\n", ptr); // UNBUFFER(ptr); // free(ptr); // //printf("delete [] 2 %p\n", ptr); // } #endif