/* EINA - EFL data type library * Copyright (C) 2015 Carsten Haitzler * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; * if not, see . */ # ifdef HAVE_CONFIG_H # include "config.h" # endif #ifdef HAVE_DLADDR # ifdef _WIN32 # include /* dladdr */ # else # include # endif #endif #ifdef HAVE_UNWIND #include #endif #include "eina_debug.h" #include "eina_debug_private.h" #ifndef _WIN32 # include // realtime signals guarantee a minimum of 8, so SIGRTMIN + 7 would be valid // at a minimum, so let's choose + 6 ... second last of the minimum set. // SIGRTMAX of course is defined too... note the manual pages for sigation say // that it calls rt_sigaction transparently for us so... no need for anything // else special # ifdef SIGRTMIN # define SIG (SIGRTMIN + 6) # else // OSX seems to not support posix RT signals... too old a kernel. so be partly // broken on OSX in that a HUP signal will maybe cause a crash... but compiling // with -pg would have guaranteed always caused a crash before anyway. // given OSX only supports "old-style" signals like: // // #define SIGHUP 1 /* hangup */ // #define SIGINT 2 /* interrupt */ // #define SIGQUIT 3 /* quit */ // #define SIGILL 4 /* illegal instruction (not reset when caught) */ // #define SIGTRAP 5 /* trace trap (not reset when caught) */ // #define SIGABRT 6 /* abort() */ // #if (defined(_POSIX_C_SOURCE) && !defined(_DARWIN_C_SOURCE)) // # define SIGPOLL 7 /* pollable event ([XSR] generated, not supported) */ // #else /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */ // # define SIGIOT SIGABRT /* compatibility */ // # define SIGEMT 7 /* EMT instruction */ // #endif /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */ // #define SIGFPE 8 /* floating point exception */ // #define SIGKILL 9 /* kill (cannot be caught or ignored) */ // #define SIGBUS 10 /* bus error */ // #define SIGSEGV 11 /* segmentation violation */ // #define SIGSYS 12 /* bad argument to system call */ // #define SIGPIPE 13 /* write on a pipe with no one to read it */ // #define SIGALRM 14 /* alarm clock */ // #define SIGTERM 15 /* software termination signal from kill */ // #define SIGURG 16 /* urgent condition on IO channel */ // #define SIGSTOP 17 /* sendable stop signal not from tty */ // #define SIGTSTP 18 /* stop signal from tty */ // #define SIGCONT 19 /* continue a stopped process */ // #define SIGCHLD 20 /* to parent on child stop or exit */ // #define SIGTTIN 21 /* to readers pgrp upon background tty read */ // #define SIGTTOU 22 /* like TTIN for output if (tp->t_local<OSTOP) */ // #if (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)) // # define SIGIO 23 /* input/output possible signal */ // #endif // #define SIGXCPU 24 /* exceeded CPU time limit */ // #define SIGXFSZ 25 /* exceeded file size limit */ // #define SIGVTALRM 26 /* virtual time alarm */ // #define SIGPROF 27 /* profiling time alarm */ // #if (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)) // # define SIGWINCH 28 /* window size changes */ // # define SIGINFO 29 /* information request */ // #endif // #define SIGUSR1 30 /* user defined signal 1 */ // #define SIGUSR2 31 /* user defined signal 2 */ // // (excerpt from OSX's signal.h - found at: // http://github.com/st3fan/osx-10.9/blob/master/xnu-2422.1.72/bsd/sys/signal.h // pasting here due to how difficult it was to find a signal list for OSX) # define SIG SIGHUP # endif #endif static Eina_Semaphore _wait_for_bts_sem; // _bt_buf[0] is always for mainloop, 1 + is for extra threads static void ***_bt_buf; static int *_bt_buf_len; static struct timespec *_bt_ts; static int *_bt_cpu; /* Used by trace timer */ static double _trace_t0 = 0.0; static Eina_Debug_Timer *_timer = NULL; #ifndef _WIN32 static struct sigaction old_sigprof_action; #endif void _eina_debug_dump_fhandle_bt(FILE *f, void **bt, int btlen) { #ifndef _WIN32 int i; Dl_info info; const char *file; unsigned long long offset, base; for (i = 0; i < btlen; i++) { file = NULL; offset = base = 0; // we have little choice but to hope/assume dladdr() doesn't alloc // anything here if ((dladdr(bt[i], &info)) && (info.dli_fname) && (info.dli_fname[0])) { offset = (unsigned long long)(uintptr_t)bt[i]; base = (unsigned long long)(uintptr_t)info.dli_fbase; file = _eina_debug_file_get(info.dli_fname); } // rely on normal libc buffering for file ops to avoid syscalls. // may or may not be a good idea. good enough for now. if (file) fprintf(f, "%s\t 0x%llx 0x%llx\n", file, offset, base); else fprintf(f, "??\t -\n"); } #else (void)f; (void)bt; (void)btlen; #endif } // a backtracer that uses libunwind to do the job static inline int _eina_debug_unwind_bt(void **bt, int max) { #ifdef HAVE_UNWIND unw_cursor_t cursor; unw_context_t uc; unw_word_t p; int total; // create a context for unwinding unw_getcontext(&uc); // begin our work unw_init_local(&cursor, &uc); // walk up each stack frame until there is no more, storing it for (total = 0; (unw_step(&cursor) > 0) && (total < max); total++) { unw_get_reg(&cursor, UNW_REG_IP, &p); bt[total] = (void *)p; } // return our total backtrace stack size return total; #else (void)bt; (void)max; return 0; #endif } // a quick and dirty local time point getter func - not portable static inline double get_time(void) { #if defined(__clockid_t_defined) struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return (double)t.tv_sec + (((double)t.tv_nsec) / 1000000000.0); #else struct timeval timev; gettimeofday(&timev, NULL); return (double)timev.tv_sec + (((double)timev.tv_usec) / 1000000.0); #endif } #ifndef _WIN32 static void _signal_handler(int sig EINA_UNUSED, siginfo_t *si EINA_UNUSED, void *foo EINA_UNUSED) { int i, slot = 0; pthread_t self = pthread_self(); #if defined(HAVE_CLOCK_GETTIME) && defined(HAVE_SCHED_GETCPU) && defined(__clockid_t_defined) clockid_t cid; #endif // find which slot in the array of threads we have so we store info // in the correct slot for us for (i = 0; i < _eina_debug_thread_active_num; i++) { if (self == _eina_debug_thread_active[i].thread) { slot = i; goto found; } } // we couldn't find out thread reference! help! e_debug("EINA DEBUG ERROR: can't find thread slot!"); eina_semaphore_release(&_wait_for_bts_sem, 1); return; found: /* * Below is very non-portable code! * * - clock_gettime() is not implemented on macOS < 10.12 * - sched_getcpu() is not implemented on macOS * - pthread_getcpuclockid() is not implemented on macOS * - CLOCK_THREAD_CPUTIME_ID should be identical to pthread_getcpuclockid(), * but it requires POSIX thingies to be defined. */ #if defined(HAVE_CLOCK_GETTIME) && defined(HAVE_SCHED_GETCPU) && defined(__clockid_t_defined) // store thread info like what cpu core we are on now (not reliable // but hey - better than nothing), the amount of cpu time total // we have consumed (it's cumulative so subtracing deltas can give // you an average amount of cpu time consumed between now and the // previous time we looked) and also a full backtrace _bt_cpu[slot] = sched_getcpu(); # ifdef HAVE_PTHREAD_GETCPUCLOCKID /* Try pthread_getcpuclockid() first */ pthread_getcpuclockid(self, &cid); # elif defined(_POSIX_THREAD_CPUTIME) /* Fallback to POSIX clock id. */ cid = CLOCK_THREAD_CPUTIME_ID; # else /* Boom, we lost */ # error Cannot determine the clock id for clock_gettime() # endif clock_gettime(cid, &(_bt_ts[slot])); _bt_buf_len[slot] = _eina_debug_unwind_bt(_bt_buf[slot], EINA_MAX_BT); #endif /* HAVE_CLOCK_GETTIME && HAVE_SCHED_GETCPU */ // now wake up the monitor to let them know we are done collecting our // backtrace info eina_semaphore_release(&_wait_for_bts_sem, 1); } #endif static void _signal_init(void) { #ifndef _WIN32 struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = SIG_DFL; sa.sa_sigaction = NULL; sa.sa_flags = SA_RESTART | SA_SIGINFO; sigemptyset(&sa.sa_mask); sigaction(SIG, &sa, &old_sigprof_action); memset(&sa, 0, sizeof(struct sigaction)); // set up signal handler for our profiling signal - eevery thread should // obey this (this is the case on linux - other OSs may vary) sa.sa_sigaction = _signal_handler; sa.sa_flags = SA_RESTART | SA_SIGINFO; sigemptyset(&sa.sa_mask); if (sigaction(SIG, &sa, NULL) != 0) e_debug("EINA DEBUG ERROR: Can't set up sig %i handler!", SIG); sa.sa_sigaction = NULL; sa.sa_handler = SIG_IGN; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; if (sigaction(SIGPIPE, &sa, 0) == -1) perror(0); #endif } static void _signal_shutdown(void) { #ifndef _WIN32 sigaction(SIG, &old_sigprof_action, NULL); #endif } static void _collect_bt(pthread_t pth) { // this async signals the thread to switch to the deebug signal handler // and collect a backtrace and other info from inside the thread #ifndef _WIN32 pthread_kill(pth, SIG); #endif } static Eina_Bool _trace_cb(void *data EINA_UNUSED) { static int bts = 0; int i; if (!_trace_t0) _trace_t0 = get_time(); // take a lock on grabbing thread debug info like backtraces eina_spinlock_take(&_eina_debug_thread_lock); // too many threads (over 1 million) !!!! if (_eina_debug_thread_active_num > (1024 * 1024)) goto err; // reset our "stack" of memory se use to dump thread info into _eina_debug_chunk_tmp_reset(); // get an array of pointers for the backtrace array for main + th _bt_buf = _eina_debug_chunk_tmp_push ((_eina_debug_thread_active_num) * sizeof(void *)); if (!_bt_buf) goto err; // get an array of pointers for the timespec array for mainloop + th _bt_ts = _eina_debug_chunk_tmp_push ((_eina_debug_thread_active_num) * sizeof(struct timespec)); if (!_bt_ts) goto err; // get an array of pointers for the cpuid array for mainloop + th _bt_cpu = _eina_debug_chunk_tmp_push ((_eina_debug_thread_active_num) * sizeof(int)); if (!_bt_cpu) goto err; // get an array of void ptrs for each thread we know about for bt for (i = 0; i < _eina_debug_thread_active_num; i++) { _bt_buf[i] = _eina_debug_chunk_tmp_push(EINA_MAX_BT * sizeof(void *)); if (!_bt_buf[i]) goto err; } // get an array of ints to stor the bt len for mainloop + threads _bt_buf_len = _eina_debug_chunk_tmp_push ((_eina_debug_thread_active_num) * sizeof(int)); // now collect per thread for (i = 0; i < _eina_debug_thread_active_num; i++) _collect_bt(_eina_debug_thread_active[i].thread); // we're done probing. now collec all the "i'm done" msgs on the // semaphore for every thread + mainloop for (i = 0; i < (_eina_debug_thread_active_num); i++) eina_semaphore_lock(&_wait_for_bts_sem); // we now have gotten all the data from all threads // we can process it now as we see fit, so release thread lock for (i = 0; i < _eina_debug_thread_active_num; i++) { _eina_debug_dump_fhandle_bt(stderr, _bt_buf[i], _bt_buf_len[i]); } err: eina_spinlock_release(&_eina_debug_thread_lock); //// XXX: some debug just to see how well we perform - will go bts++; if (bts >= 10000) { double t; t = get_time(); e_debug("%1.5f bt's per sec", (double)bts / (t - _trace_t0)); _trace_t0 = t; bts = 0; } return EINA_TRUE; } // profiling on with poll time gap as uint payload static Eina_Bool _prof_on_cb(Eina_Debug_Session *session, int cid EINA_UNUSED, void *buffer, int size) { unsigned int time; _signal_init(); if (size >= 4) { memcpy(&time, buffer, 4); _trace_t0 = 0.0; if (_timer) eina_debug_timer_del(_timer); _timer = eina_debug_timer_add(time, _trace_cb, session); } return EINA_TRUE; } static Eina_Bool _prof_off_cb(Eina_Debug_Session *session EINA_UNUSED, int cid EINA_UNUSED, void *buffer EINA_UNUSED, int size EINA_UNUSED) { eina_debug_timer_del(_timer); _timer = NULL; _signal_shutdown(); return EINA_TRUE; } EINA_DEBUG_OPCODES_ARRAY_DEFINE(_OPS, {"Profiler/on", NULL, &_prof_on_cb}, {"Profiler/off", NULL, &_prof_off_cb}, {NULL, NULL, NULL} ); Eina_Bool _eina_debug_bt_init(void) { eina_semaphore_new(&_wait_for_bts_sem, 0); eina_debug_opcodes_register(NULL, _OPS(), NULL, NULL); return EINA_TRUE; } Eina_Bool _eina_debug_bt_shutdown(void) { eina_semaphore_free(&_wait_for_bts_sem); return EINA_TRUE; }