diff --git a/configure.ac b/configure.ac index 63cc54ddce..ab7661560a 100644 --- a/configure.ac +++ b/configure.ac @@ -849,6 +849,12 @@ case "${build_profile}" in ;; esac +PKG_CHECK_MODULES(UNWIND, [libunwind libunwind-generic], + [have_unwind=yes], [have_unwind=no]) +AS_IF([test "x$have_unwind" = "xyes"], + [AC_DEFINE([HAVE_UNWIND], [1], [Have libunwind])]) +AM_CONDITIONAL(HAVE_UNWIND, test "x$have_unwind" = "xyes") + EINA_CONFIG([HAVE_ALLOCA_H], [test "x${ac_cv_working_alloca_h}" = "xyes"]) EINA_CONFIG([SAFETY_CHECKS], [test "x${have_safety_checks}" = "xyes"]) EINA_CONFIG([DEFAULT_MEMPOOL], [test "x${want_default_mempool}" = "xyes"]) @@ -4801,7 +4807,7 @@ echo " Cryptography..: ${build_crypto}" echo " X11...........: ${with_x11}" echo " OpenGL........: ${with_opengl}" echo " C++11.........: ${have_cxx11}" -echo "Eina............: yes (${features_eina})" +echo "Eina............: yes (${features_eina} unwind=$have_unwind)" echo "Eo..............: yes (${features_eo})" echo "Eolian..........: yes (${features_eolian})" echo "Emile...........: yes (${features_emile})" diff --git a/src/Makefile_Efl.am b/src/Makefile_Efl.am index cf6310a00c..b80a46c210 100644 --- a/src/Makefile_Efl.am +++ b/src/Makefile_Efl.am @@ -67,3 +67,20 @@ installed_eflluadir = $(datadir)/elua/modules/efl nodist_installed_efllua_DATA = $(generated_efl_lua_all) endif + +### Binary + +bin_PROGRAMS += \ +bin/efl/efl_debugd \ +bin/efl/efl_debug + +bin_efl_efl_debugd_SOURCES = bin/efl/efl_debugd.c +bin_efl_efl_debugd_CPPFLAGS = -I$(top_builddir)/src/bin/efl @EINA_CFLAGS@ @ECORE_CFLAGS@ @ECORE_CON_CFLAGS@ +bin_efl_efl_debugd_LDADD = @USE_EINA_INTERNAL_LIBS@ @USE_ECORE_INTERNAL_LIBS@ @USE_ECORE_CON_INTERNAL_LIBS@ +bin_efl_efl_debugd_DEPENDENCIES = @USE_EINA_INTERNAL_LIBS@ @USE_ECORE_INTERNAL_LIBS@ @USE_ECORE_CON_INTERNAL_LIBS@ + +bin_efl_efl_debug_SOURCES = bin/efl/efl_debug.c +bin_efl_efl_debug_CPPFLAGS = -I$(top_builddir)/src/bin/efl @EINA_CFLAGS@ @ECORE_CFLAGS@ @ECORE_CON_CFLAGS@ +bin_efl_efl_debug_LDADD = @USE_EINA_INTERNAL_LIBS@ @USE_ECORE_INTERNAL_LIBS@ @USE_ECORE_CON_INTERNAL_LIBS@ +bin_efl_efl_debug_DEPENDENCIES = @USE_EINA_INTERNAL_LIBS@ @USE_ECORE_INTERNAL_LIBS@ @USE_ECORE_CON_INTERNAL_LIBS@ + diff --git a/src/Makefile_Eina.am b/src/Makefile_Eina.am index 3591b10de1..a92b758de1 100644 --- a/src/Makefile_Eina.am +++ b/src/Makefile_Eina.am @@ -101,6 +101,14 @@ lib/eina/eina_convert.c \ lib/eina/eina_counter.c \ lib/eina/eina_cow.c \ lib/eina/eina_cpu.c \ +lib/eina/eina_debug.h \ +lib/eina/eina_debug.c \ +lib/eina/eina_debug_chunk.c \ +lib/eina/eina_debug_bt.c \ +lib/eina/eina_debug_bt_file.c \ +lib/eina/eina_debug_thread.c \ +lib/eina/eina_debug_monitor.c \ +lib/eina/eina_debug_proto.c \ lib/eina/eina_error.c \ lib/eina/eina_file_common.h \ lib/eina/eina_file_common.c \ @@ -221,15 +229,30 @@ endif lib_eina_libeina_la_CPPFLAGS = -I$(top_builddir)/src/lib/efl \ @EINA_CFLAGS@ \ +@UNWIND_CFLAGS@ \ -DPACKAGE_BIN_DIR=\"$(bindir)\" \ -DPACKAGE_LIB_DIR=\"$(libdir)\" \ -DPACKAGE_DATA_DIR=\"$(datadir)/eina\" \ @VALGRIND_CFLAGS@ -lib_eina_libeina_la_LIBADD = @EINA_LIBS@ @DL_LIBS@ +lib_eina_libeina_la_LIBADD = @EINA_LIBS@ @DL_LIBS@ @UNWIND_LIBS@ lib_eina_libeina_la_DEPENDENCIES = @EINA_INTERNAL_LIBS@ @DL_INTERNAL_LIBS@ lib_eina_libeina_la_LDFLAGS = @EFL_LTLIBRARY_FLAGS@ +### Binaries + +bin_PROGRAMS += bin/eina/eina_btlog + +bin_eina_eina_btlog_SOURCES = bin/eina/eina_btlog.c +bin_eina_eina_btlog_CPPFLAGS = -I$(top_builddir)/src/lib/efl \ +-DPACKAGE_BIN_DIR=\"$(bindir)\" \ +-DPACKAGE_LIB_DIR=\"$(libdir)\" \ +-DPACKAGE_DATA_DIR=\"$(datadir)/eina\" \ +@EINA_CFLAGS@ + +bin_eina_eina_btlog_LDADD = @USE_EINA_LIBS@ +bin_eina_eina_btlog_DEPENDENCIES = @USE_EINA_INTERNAL_LIBS@ + ### Script bin_SCRIPTS += scripts/eina/eina-bench-cmp diff --git a/src/bin/efl/.gitignore b/src/bin/efl/.gitignore new file mode 100644 index 0000000000..b38a2ee5ea --- /dev/null +++ b/src/bin/efl/.gitignore @@ -0,0 +1,2 @@ +efl_debugd +efl_debug diff --git a/src/bin/efl/efl_debug.c b/src/bin/efl/efl_debug.c new file mode 100644 index 0000000000..b1cd730ea4 --- /dev/null +++ b/src/bin/efl/efl_debug.c @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +#include +#include + +static unsigned char *buf; +static unsigned int buf_size; + +static int my_argc; +static char **my_argv; +static const char *expect = NULL; + +static Ecore_Con_Server *svr; + +static void +_proto(unsigned char *d, unsigned int size) +{ + if (size >= 4) + { + char *cmd = (char *)d; + + if (!strncmp(cmd, "CLST", 4)) + { + int i, n; + + n = (size - 4) / sizeof(int); + if (n < 10000) + { + int *pids = malloc(n * sizeof(int)); + memcpy(pids, d + 4, n * sizeof(int)); + for (i = 0; i < n; i++) + { + if (pids[i] > 0) printf("%i\n", pids[i]); + } + free(pids); + } + } + if ((expect) && (!strncmp(cmd, expect, 4))) + ecore_main_loop_quit(); + } +} + + +static Eina_Bool +_server_proto(void) +{ + unsigned int size, newsize; + unsigned char *b; + if (!buf) return EINA_FALSE; + if (buf_size < 4) return EINA_FALSE; + memcpy(&size, buf, 4); + if (buf_size < (size + 4)) return EINA_FALSE; + _proto(buf + 4, size); + newsize = buf_size - (size + 4); + if (buf_size == newsize) + { + free(buf); + buf = NULL; + buf_size = 0; + } + else + { + b = malloc(newsize); + memcpy(b, buf + size + 4, newsize); + free(buf); + buf = b; + buf_size = newsize; + } + return EINA_TRUE; +} + +Eina_Bool +_server_add(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_Con_Event_Server_Add *ev) +{ + int i; + for (i = 1; i < my_argc; i++) + { + if (!strcmp(my_argv[i], "list")) + { + unsigned int size = 4; + char *head = "LIST"; + expect = "CLST"; + ecore_con_server_send(svr, &size, 4); + ecore_con_server_send(svr, head, 4); + } + else if ((!strcmp(my_argv[i], "pon")) && + (i < (my_argc - 2))) + { + unsigned int size = 12; + char *head = "PLON"; + int pid = atoi(my_argv[i + 1]); + unsigned int freq = atoi(my_argv[i + 2]); + i++; + ecore_con_server_send(svr, &size, 4); + ecore_con_server_send(svr, head, 4); + ecore_con_server_send(svr, &pid, 4); + ecore_con_server_send(svr, &freq, 4); + ecore_main_loop_quit(); + } + else if ((!strcmp(my_argv[i], "poff")) && + (i < (my_argc - 1))) + { + unsigned int size = 8; + char *head = "PLOFF"; + int pid = atoi(my_argv[i + 1]); + i++; + ecore_con_server_send(svr, &size, 4); + ecore_con_server_send(svr, head, 4); + ecore_con_server_send(svr, &pid, 4); + ecore_main_loop_quit(); + } + } + return ECORE_CALLBACK_RENEW; +} + +Eina_Bool +_server_del(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_Con_Event_Server_Del *ev) +{ + ecore_main_loop_quit(); + return ECORE_CALLBACK_RENEW; +} + +static Eina_Bool +_server_data(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_Con_Event_Server_Data *ev) +{ + if (!buf) + { + buf = malloc(ev->size); + if (buf) + { + buf_size = ev->size; + memcpy(buf, ev->data, ev->size); + } + } + else + { + unsigned char *b = realloc(buf, buf_size + ev->size); + if (b) + { + buf = b; + memcpy(buf + buf_size, ev->data, ev->size); + buf_size += ev->size; + } + } + while (_server_proto()); + return ECORE_CALLBACK_RENEW; +} + +int +main(int argc, char **argv) +{ + eina_init(); + ecore_init(); + ecore_con_init(); + + my_argc = argc; + my_argv = argv; + + svr = ecore_con_server_connect(ECORE_CON_LOCAL_USER, "efl_debug", 0, NULL); + if (!svr) + { + fprintf(stderr, "ERROR: Cannot connetc to debug daemon.\n"); + return -1; + } + + ecore_event_handler_add(ECORE_CON_EVENT_SERVER_ADD, (Ecore_Event_Handler_Cb)_server_add, NULL); + ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DEL, (Ecore_Event_Handler_Cb)_server_del, NULL); + ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DATA, (Ecore_Event_Handler_Cb)_server_data, NULL); + + ecore_main_loop_begin(); + ecore_con_server_flush(svr); + + ecore_con_shutdown(); + ecore_shutdown(); + eina_shutdown(); +} diff --git a/src/bin/efl/efl_debugd.c b/src/bin/efl/efl_debugd.c new file mode 100644 index 0000000000..7f369ac636 --- /dev/null +++ b/src/bin/efl/efl_debugd.c @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include +#include +#include + +typedef struct _Client Client; + +struct _Client +{ + Ecore_Con_Client *client; + int version; + pid_t pid; + unsigned char *buf; + unsigned int buf_size; +}; + +static Ecore_Con_Server *svr = NULL; +static Eina_List *clients = NULL; + +static void +_proto(Client *c, unsigned char *d, unsigned int size) +{ + if (size >= 4) + { + char *cmd = (char *)d; + + if (!strncmp(cmd, "HELO", 4)) + { + int version; + int pid; + + memcpy(&version, d + 4, 4); + memcpy(&pid, d + 8, 4); + c->version = version; + c->pid = pid; + } + else if (!strncmp(cmd, "LIST", 4)) + { + int n = eina_list_count(clients), i; + unsigned int *pids, size2; + Client *c2; + Eina_List *l; + char *head = "CLST"; + + pids = malloc(n * sizeof(int)); + i = 0; + size2 = 4 + (n * sizeof(int)); + EINA_LIST_FOREACH(clients, l, c2) + { + pids[i] = c2->pid; + i++; + } + ecore_con_client_send(c->client, &size2, 4); + ecore_con_client_send(c->client, head, 4); + ecore_con_client_send(c->client, pids, n * sizeof(int)); + free(pids); + } + else if (!strncmp(cmd, "PLON", 4)) + { + int pid; + unsigned int freq = 1000; + Client *c2; + Eina_List *l; + + memcpy(&pid, d + 4, 4); + memcpy(&freq, d + 8, 4); + if (pid > 0) + { + EINA_LIST_FOREACH(clients, l, c2) + { + if (c2->pid == pid) + { + unsigned int size2 = 8; + + ecore_con_client_send(c2->client, &size2, 4); + ecore_con_client_send(c2->client, d, 4); + ecore_con_client_send(c2->client, &freq, 4); + break; + } + } + } + } + else if (!strncmp(cmd, "PLOF", 4)) + { + int pid; + Client *c2; + Eina_List *l; + + memcpy(&pid, d + 4, 4); + if (pid > 0) + { + EINA_LIST_FOREACH(clients, l, c2) + { + if (c2->pid == pid) + { + unsigned int size2 = 4; + + ecore_con_client_send(c2->client, &size2, 4); + ecore_con_client_send(c2->client, d, 4); + break; + } + } + } + } + } +} + +static Eina_Bool +_client_proto(Client *c) +{ + unsigned int size, newsize; + unsigned char *b; + if (!c->buf) return EINA_FALSE; + if (c->buf_size < 4) return EINA_FALSE; + memcpy(&size, c->buf, 4); + if (c->buf_size < (size + 4)) return EINA_FALSE; + _proto(c, c->buf + 4, size); + newsize = c->buf_size - (size + 4); + if (c->buf_size == newsize) + { + free(c->buf); + c->buf = NULL; + c->buf_size = 0; + } + else + { + b = malloc(newsize); + memcpy(b, c->buf + size + 4, newsize); + free(c->buf); + c->buf = b; + c->buf_size = newsize; + } + return EINA_TRUE; +} + +static Eina_Bool +_client_add(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_Con_Event_Client_Add *ev) +{ + Client *c = calloc(1, sizeof(Client)); + if (c) + { + c->client = ev->client; + clients = eina_list_append(clients, c); + ecore_con_client_data_set(c->client, c); + } + return ECORE_CALLBACK_RENEW; +} + +static Eina_Bool +_client_del(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_Con_Event_Client_Del *ev) +{ + Client *c = ecore_con_client_data_get(ev->client); + if (c) + { + clients = eina_list_remove(clients, c); + free(c); + } + return ECORE_CALLBACK_RENEW; +} + +static Eina_Bool +_client_data(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_Con_Event_Client_Data *ev) +{ + Client *c = ecore_con_client_data_get(ev->client); + if (c) + { + if (!c->buf) + { + c->buf = malloc(ev->size); + if (c->buf) + { + c->buf_size = ev->size; + memcpy(c->buf, ev->data, ev->size); + } + } + else + { + unsigned char *b = realloc(c->buf, c->buf_size + ev->size); + if (b) + { + c->buf = b; + memcpy(c->buf + c->buf_size, ev->data, ev->size); + c->buf_size += ev->size; + } + } + while (_client_proto(c)); + } + return ECORE_CALLBACK_RENEW; +} + +int +main(int argc EINA_UNUSED, char **argv EINA_UNUSED) +{ + eina_init(); + ecore_init(); + ecore_con_init(); + + svr = ecore_con_server_add(ECORE_CON_LOCAL_USER, "efl_debug", 0, NULL); + if (!svr) + { + fprintf(stderr, "ERROR: Cannot create debug daemon.\n"); + return -1; + } + + ecore_event_handler_add(ECORE_CON_EVENT_CLIENT_ADD, (Ecore_Event_Handler_Cb)_client_add, NULL); + ecore_event_handler_add(ECORE_CON_EVENT_CLIENT_DEL, (Ecore_Event_Handler_Cb)_client_del, NULL); + ecore_event_handler_add(ECORE_CON_EVENT_CLIENT_DATA, (Ecore_Event_Handler_Cb)_client_data, NULL); + + ecore_main_loop_begin(); + + ecore_con_server_del(svr); + + ecore_con_shutdown(); + ecore_shutdown(); + eina_shutdown(); +} diff --git a/src/bin/eina/.gitignore b/src/bin/eina/.gitignore new file mode 100644 index 0000000000..ec4ebaa080 --- /dev/null +++ b/src/bin/eina/.gitignore @@ -0,0 +1 @@ +eina_btlog diff --git a/src/bin/eina/eina_btlog.c b/src/bin/eina/eina_btlog.c new file mode 100644 index 0000000000..2b4d5303ab --- /dev/null +++ b/src/bin/eina/eina_btlog.c @@ -0,0 +1,198 @@ +#include +#include +#include +#include + +// right now this is quick and dirty and may have some parsing ... frailty, +// so don't put malicious data through it... :) but cat in eina bt's through +// this to get a nicely clean and readable bt with filenames of binaries, +// shared objects, source files, and line numbers. even nicely colored and +// columnated. this is more the start of a bunch of debug tools for efl to make +// it easier to identify issues. +// +// how to use: +// +// cat mybacktrace.txt | eina_btlog +// +// (or just run it and copy & paste in on stdin - what i do mostly, and out +// pops a nice backtrace, hit ctrl+d to end) + +typedef struct _Bt Bt; + +struct _Bt +{ + char *bin_dir; + char *bin_name; + char *file_dir; + char *file_name; + char *func_name; + int line; +}; + +static void +path_split(const char *path, char **dir, char **file) +{ + const char *p = strrchr(path, '/'); + + if (!path) + { + *dir = NULL; + *file = NULL; + return; + } + if (!p) + { + *dir = NULL; + *file = strdup(path); + return; + } + *dir = malloc(p - path + 1); + if (!dir) + { + *dir = NULL; + *file = NULL; + return; + } + strncpy(*dir, path, p - path); + (*dir)[p - path] = 0; + *file = strdup(p + 1); +} + +static Eina_Bool +_addr2line(const char *bin_dir, const char *bin_name, unsigned long long addr, + char **file_dir, char **file_name, char **func_name, int *file_line) +{ + char buf[4096], func[4096], *f1 = NULL, *f2 = NULL; + Eina_Bool ok = EINA_FALSE; + int line; + FILE *p; + + snprintf(buf, sizeof(buf), "addr2line -f -e %s/%s -C -a 0x%llx", + bin_dir, bin_name, addr); + p = popen(buf, "r"); + if (!p) return EINA_FALSE; + fscanf(p, "%s\n", buf); + if (fscanf(p, "%s\n", func) == 1) + { + if (fscanf(p, "%[^:]:%i\n", buf, &line) == 2) + { + path_split(buf, &(f1), &(f2)); + if ((!f1) || (!f2)) + { + free(f1); + free(f2); + pclose(p); + return EINA_FALSE; + } + } + else + { + f1 = strdup("??"); + f2 = strdup("??"); + } + *file_dir = f1; + *file_name = f2; + *func_name = strdup(func); + *file_line = line; + ok = EINA_TRUE; + } + pclose(p); + return ok; +} + +static Eina_List * +bt_append(Eina_List *btl, const char *btline) +{ + Bt *bt = calloc(1, sizeof(Bt)); + if (!bt) return btl; + char *bin = strdup(btline); + unsigned long long offset = 0, base = 0; + + // parse: + // /usr/local/lib/libeina.so.1 0x1ec88 + // /usr/local/lib/libelementary.so.1 0x10f695 + // /usr/local/lib/libeo.so.1 0xa474 + // /usr/local/lib/libelementary.so.1 0x139bd6 + // /usr/local/bin/elementary_test 0x8196d + // /usr/local/bin/elementary_test 0x81b6a + if (sscanf(btline, "%s %llx %llx", bin, &offset, &base) == 3) + { + path_split(bin, &(bt->bin_dir), &(bt->bin_name)); + if (!bt->bin_dir) bt->bin_dir = strdup(""); + if (!bt->bin_name) bt->bin_name = strdup(""); + if (!_addr2line(bt->bin_dir, bt->bin_name, offset - base, + &(bt->file_dir), &(bt->file_name), + &(bt->func_name), &(bt->line))) + { + if (!_addr2line(bt->bin_dir, bt->bin_name, offset, + &(bt->file_dir), &(bt->file_name), + &(bt->func_name), &(bt->line))) + { + bt->file_dir = strdup(""); + bt->file_name = strdup(""); + bt->func_name = strdup(""); + } + } + btl = eina_list_append(btl, bt); + } + free(bin); + return btl; +} + +int +main(int argc, char **argv) +{ + Eina_List *btl = NULL, *l; + char buf[4096]; + Bt *bt; + int cols[6] = { 0 }, len, i; + + eina_init(); + while (fgets(buf, sizeof(buf) - 1, stdin)) + { + btl = bt_append(btl, buf); + } + EINA_LIST_FOREACH(btl, l, bt) + { + len = strlen(bt->bin_dir); + if (len > cols[0]) cols[0] = len; + len = strlen(bt->bin_name); + if (len > cols[1]) cols[1] = len; + + len = strlen(bt->file_dir); + if (len > cols[2]) cols[2] = len; + len = strlen(bt->file_name); + if (len > cols[3]) cols[3] = len; + + snprintf(buf, sizeof(buf), "%i", bt->line); + len = strlen(buf); + if (len > cols[4]) cols[4] = len; + + len = strlen(bt->func_name); + if (len > cols[5]) cols[5] = len; + } + EINA_LIST_FOREACH(btl, l, bt) + { + len = strlen(bt->bin_dir); + for (i = 0; i < (cols[0] - len); i++) printf(" "); + printf("\033[34m%s\033[01m\033[36m/\033[37m%s\033[0m", + bt->bin_dir, bt->bin_name); + len = strlen(bt->bin_name); + for (i = 0; i < (cols[1] - len); i++) printf(" "); + printf(" | "); + len = strlen(bt->file_dir); + for (i = 0; i < (cols[2] - len); i++) printf(" "); + printf("\033[34m%s\033[01m\033[36m/\033[37m%s\033[0m", + bt->file_dir, bt->file_name); + len = strlen(bt->file_name); + for (i = 0; i < (cols[3] - len); i++) printf(" "); + + printf(" : "); + snprintf(buf, sizeof(buf), "%i", bt->line); + len = strlen(buf); + for (i = 0; i < (cols[4] - len); i++) printf(" "); + printf("\033[01m\033[33m%s\033[0m @ \033[32m%s\033[36m()", buf, bt->func_name); + printf("\033[0m\n"); + } + return 0; +} diff --git a/src/lib/eina/eina_debug.c b/src/lib/eina/eina_debug.c new file mode 100644 index 0000000000..766ec7ac3c --- /dev/null +++ b/src/lib/eina/eina_debug.c @@ -0,0 +1,69 @@ +#include "eina_debug.h" + +#ifdef EINA_HAVE_DEBUG + +extern pthread_t _eina_debug_thread_mainloop; +extern pthread_t *_eina_debug_thread_active; +extern int _eina_debug_thread_active_num; + + +// yes - a global debug spinlock. i expect contention to be low for now, and +// when needed we can split this up into mroe locks to reduce contention when +// and if that day comes +Eina_Spinlock _eina_debug_lock; + +static Eina_Bool _inited = EINA_FALSE; + +Eina_Bool +eina_debug_init(void) +{ + pthread_t self; + + if (_inited) + { + eina_spinlock_release(&_eina_debug_thread_lock); + return EINA_TRUE; + } + _inited = EINA_TRUE; + eina_spinlock_new(&_eina_debug_lock); + eina_spinlock_new(&_eina_debug_thread_lock); + eina_semaphore_new(&_eina_debug_monitor_return_sem, 0); + self = pthread_self(); + _eina_debug_thread_mainloop_set(&self); +#if defined(HAVE_GETUID) && defined(HAVE_GETEUID) + // if we are setuid - don't debug! + if (getuid() != geteuid()) return EINA_TRUE; +#endif + if (getenv("EFL_NODEBUG")) return EINA_TRUE; + _eina_debug_monitor_service_connect(); + if (_eina_debug_monitor_service_fd >= 0) + { + _eina_debug_monitor_service_greet(); + _eina_debug_monitor_signal_init(); + _eina_debug_monitor_thread_start(); + } + return EINA_TRUE; +} + +Eina_Bool +eina_debug_shutdown(void) +{ + eina_spinlock_take(&_eina_debug_thread_lock); + // yes - we never free on shutdown - this is because the monitor thread + // never exits. this is not a leak - we intend to never free up any + // resources here because they are allocated once only ever. + return EINA_TRUE; +} +#else +Eina_Bool +eina_debug_init(void) +{ + return EINA_TRUE; +} + +Eina_Bool +eina_debug_shutdown(void) +{ + return EINA_TRUE; +} +#endif diff --git a/src/lib/eina/eina_debug.h b/src/lib/eina/eina_debug.h new file mode 100644 index 0000000000..f4494af741 --- /dev/null +++ b/src/lib/eina/eina_debug.h @@ -0,0 +1,86 @@ +#ifndef EINA_DEBUG_H_ +# define EINA_DEBUG_H_ + +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif + +# include +# include +# include +# include +# if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && defined(HAVE_DLADDR) && defined(HAVE_UNWIND) +# include +# ifndef _GNU_SOURCE +# define _GNU_SOURCE 1 +# endif +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +# include "eina_config.h" +# include "eina_private.h" +# include "eina_inlist.h" +# include "eina_lock.h" +# include "eina_thread.h" +# include "eina_convert.h" +# include "eina_strbuf.h" +# include "eina_safety_checks.h" +# include "eina_log.h" +# include "eina_inline_private.h" + +# define EINA_HAVE_DEBUG 1 + +# define EINA_MAX_BT 256 + +extern Eina_Spinlock _eina_debug_lock; +extern Eina_Spinlock _eina_debug_thread_lock; +extern Eina_Semaphore _eina_debug_monitor_return_sem; +extern int _eina_debug_monitor_service_fd; + +void _eina_debug_thread_add(void *th); +void _eina_debug_thread_del(void *th); +void _eina_debug_thread_mainloop_set(void *th); + +void *_eina_debug_chunk_push(int size); +void *_eina_debug_chunk_realloc(int size); +char *_eina_debug_chunk_strdup(const char *str); +void *_eina_debug_chunk_tmp_push(int size); +void _eina_debug_chunk_tmp_reset(void); + +const char *_eina_debug_file_get(const char *fname); + +void _eina_debug_dump_fhandle_bt(FILE *f, void **bt, int btlen); + +void _eina_debug_monitor_thread_start(void); +void _eina_debug_monitor_signal_init(void); +void _eina_debug_monitor_service_connect(void); + +void _eina_debug_monitor_service_greet(void); + +# define EINA_BT(file) \ + do { \ + void *bt[EINA_MAX_BT]; \ + int btlen = backtrace((void **)bt, EINA_MAX_BT); \ + _eina_debug_dump_fhandle_bt(file, bt, btlen); \ + } while (0) +# else +# define EINA_BT(file) do { } while (0) +# endif + +#endif diff --git a/src/lib/eina/eina_debug_bt.c b/src/lib/eina/eina_debug_bt.c new file mode 100644 index 0000000000..7f1beb1a61 --- /dev/null +++ b/src/lib/eina/eina_debug_bt.c @@ -0,0 +1,31 @@ +#include "eina_debug.h" + +#ifdef EINA_HAVE_DEBUG + +void +_eina_debug_dump_fhandle_bt(FILE *f, void **bt, int btlen) +{ + 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 hgope/assume dladdr() doesn't alloc + // anything here + if ((dladdr(bt[i], &info)) && (info.dli_fname[0])) + { + offset = (unsigned long long)bt[i]; + base = (unsigned long long)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"); + } +} +#endif diff --git a/src/lib/eina/eina_debug_bt_file.c b/src/lib/eina/eina_debug_bt_file.c new file mode 100644 index 0000000000..9ed5a7cf8a --- /dev/null +++ b/src/lib/eina/eina_debug_bt_file.c @@ -0,0 +1,145 @@ +#include "eina_debug.h" + +#ifdef EINA_HAVE_DEBUG + +static unsigned int _table_num = 0; +static unsigned int _table_size = 0; +static const char **_table = NULL; + +// a very simple "fast lookup" of a filename to a path. we expect this table +// of lookups to remain very small as it most likely includes just the +// application executable itself as this was run from $PATH or using +// a relative path relative to cwd of shell at time of exec. this is really +// the only likely content, but just in case, handle more. it is much faster +// than going through systemcalls like realpath() every time (well this libc +// function will at least be a system call or use system calls to do its work) +static const char * +_eina_debug_file_lookup(const char *fname) +{ + unsigned int n; + + if (!_table) return NULL; + for (n = 0; _table[n]; n += 2) + { + if (!strcmp(_table[n], fname)) return _table[n + 1]; + } + return NULL; +} + +// record a new filename -> path entry in our table. the table really is just +// odd/even strings like fname, path, fname2, path2, fnamr3, path3, ... +// and we are unlikely to have much more than 1 entry here. see above +static const char * +_eina_debug_file_store(const char *fname, const char *file) +{ + static const char **table2; + + _table_num += 2; + if (_table_num >= _table_size) + { + _table_size += 32; + table2 = _eina_debug_chunk_realloc(_table_size * sizeof(const char *)); + if (!table2) return NULL; + _table = table2; + } + _table[_table_num - 2] = _eina_debug_chunk_strdup(fname); + _table[_table_num - 1] = _eina_debug_chunk_strdup(file); + return _table[_table_num - 1]; +} + +// do a "fast lookup" of a filename to a file path for debug output. this +// relies on caching to avoid system calls and assumes that once we know +// the full path of a given filename, we can know its full path reliably. +// if we can't we'd be in trouble anyway as the filename and path lookup +// failure is due maybe to a deleted file or renamed file and then we are +// going to have a bad day either way. +const char * +_eina_debug_file_get(const char *fname) +{ + char buf[4096]; + const char *file; + static const char **path = NULL; + static char *pathstrs = NULL; + + // no filename provided + if ((!fname) || (!fname[0])) return NULL; + // it's a full path so return as-is + if (fname[0] == '/') return fname; + // first look in cache for filename -> full path lookup and if + // there, return that (yes - assuming filesystem paths doesn't change + // which is unlikely as they were set up at star most likely) + eina_spinlock_take(&_eina_debug_lock); + file = _eina_debug_file_lookup(fname); + eina_spinlock_release(&_eina_debug_lock); + if (file) return file; + + // store PATH permanently - yes. if it changes runtime it will break. + // for speed reasons we need to assume it won't change. store path broken + // down into an array of ptrs to strings with NULL ptr at the end. this + // will only execute once as an "init" for a breoken up path so it should + // not matter speed-wise + eina_spinlock_take(&_eina_debug_lock); + if (!path) + { + unsigned int n; + char *p1, *p2; + const char *p; + const char *pathstr = getenv("PATH"); + + if (!pathstr) return NULL; + // dup the entire env as we will rpelace : with 0 bytes to break str + pathstrs = _eina_debug_chunk_strdup(pathstr); + for (n = 0, p = pathstr; *p;) + { + n++; + p = strchr(p, ':'); + if (!p) break; + p++; + } + path = _eina_debug_chunk_push(sizeof(const char *) * (n + 1)); + for (n = 0, p1 = pathstrs; *p1; n++) + { + path[n] = p1; + p2 = strchr(p1, ':'); + if (!p2) break; + *p2 = 0; + p1 = p2 + 1; + } + path[n] = NULL; + } + eina_spinlock_release(&_eina_debug_lock); + + // a relative path - resolve with realpath. due to the cache store above + // we shouldn't have to do this very often + if ((!strncmp(fname, "./", 2)) || (!strncmp(fname, "../", 3))) + { // relative path + if (realpath(fname, buf)) file = buf; + else file = NULL; + } + // search in $PATH for the file then - this should also be very rare as + // we will store and cache results permanently + else if (path) + { + struct stat st; + unsigned int n; + + for (n = 0; path[n]; n++) + { + snprintf(buf, sizeof(buf), "%s/%s", path[n], fname); + if (stat(buf, &st) == 0) + { + file = buf; + break; + } + } + } + // if it's found - store it in cache for later + if (file) + { + eina_spinlock_take(&_eina_debug_lock); + file = _eina_debug_file_store(fname, file); + eina_spinlock_release(&_eina_debug_lock); + } + return file; +} +#endif diff --git a/src/lib/eina/eina_debug_chunk.c b/src/lib/eina/eina_debug_chunk.c new file mode 100644 index 0000000000..3351ce19a7 --- /dev/null +++ b/src/lib/eina/eina_debug_chunk.c @@ -0,0 +1,228 @@ +#include "eina_debug.h" + +#ifdef EINA_HAVE_DEBUG + +# ifdef HAVE_MMAP +# include +# endif + +// custom memory allocators to avoid malloc/free during backtrace handling +// just in case we're inside some signal handler due to mem corruption and +// are inside a malloc/free lock and thus would deadlock ourselves if we +// allocated memory, so implement scratch space just big enough for what we +// need and then some via either a static 8k+4k buffer pair or via a growable +// mmaped mem chunk pair +# ifdef HAVE_MMAP +// implement using mmap so we can grow if needed - unlikelt though +static unsigned char *chunk1 = NULL; +static unsigned char *chunk2 = NULL; +static unsigned char *chunk3 = NULL; +static int chunk1_size = 0; +static int chunk1_num = 0; +static int chunk2_size = 0; +static int chunk2_num = 0; +static int chunk3_size = 0; +static int chunk3_num = 0; + +// get a new chunk of "anonymous mmaped memory" +static void * +_eina_debug_chunk_need(int size) +{ + void *ptr; + + ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0); + if (ptr == MAP_FAILED) return NULL; + return ptr; +} + +// release a chunk of this mmaped anon mem if we don't need it anymore +static void +_eina_debug_chunk_noneed(void *ptr, int size) +{ + munmap(ptr, size); +} + +// push a new bit of mem on our growing stack of mem - given our workload, +// we never free anything here, only ever grow new things on this stack +void * +_eina_debug_chunk_push(int size) +{ + void *ptr; + + // no initial chunk1 block - allocate it + if (!chunk1) + { + chunk1 = _eina_debug_chunk_need(8 * 1024); + if (!chunk1) return NULL; + chunk1_size = 8 * 1024; + } + // round size up to the nearest pointer size for alignment + size = sizeof(void *) * ((size + sizeof(void *) - 1) / sizeof(void *)); + // if our chunk is too small - grow it + if ((chunk1_num + size) > chunk1_size) + { + // get a new chunk twice as big + void *newchunk = _eina_debug_chunk_need(chunk1_size * 2); + if (!newchunk) return NULL; + // copy content over + memcpy(newchunk, chunk1, chunk1_num); + // release old chunk + _eina_debug_chunk_noneed(chunk1, chunk1_size); + // switch to our new 2x as big chunk + chunk1 = newchunk; + chunk1_size = chunk1_size * 2; + } + // get the mem at the top of this stack and return it, then move along + ptr = chunk1 + chunk1_num; + chunk1_num += size; + return ptr; +} + +// grow a single existing chunk (we use this for the filename -> path lookup) +void * +_eina_debug_chunk_realloc(int size) +{ + // we have a null/empty second chunk - allocate one + if (!chunk2) + { + chunk2 = _eina_debug_chunk_need(4 * 1024); + if (!chunk2) return NULL; + chunk2_size = 4 * 1024; + } + // if our chunk is too small - grow it + if (size > chunk2_size) + { + // get a new chunk twice as big + void *newchunk = _eina_debug_chunk_need(chunk2_size * 2); + if (!newchunk) return NULL; + // copy content over + memcpy(newchunk, chunk2, chunk2_num); + // release old chunk + _eina_debug_chunk_noneed(chunk2, chunk2_size); + // switch to our new 2x as big chunk + chunk2 = newchunk; + chunk2_size = chunk2_size * 2; + } + // record new size and return chunk ptr as we just re-use it + chunk2_num = size; + return chunk2; +} + +// grow a single existing chunk (we use this for the filename -> path lookup) +void * +_eina_debug_chunk_tmp_push(int size) +{ + void *ptr; + + // no initial chunk1 block - allocate it + if (!chunk3) + { + chunk3 = _eina_debug_chunk_need(32 * 1024); + if (!chunk3) return NULL; + chunk3_size = 32 * 1024; + } + // round size up to the nearest pointer size for alignment + size = sizeof(void *) * ((size + sizeof(void *) - 1) / sizeof(void *)); + // if our chunk is too small - grow it + if ((chunk3_num + size) > chunk3_size) + { + // get a new chunk twice as big + void *newchunk = _eina_debug_chunk_need(chunk3_size * 2); + if (!newchunk) return NULL; + // copy content over + memcpy(newchunk, chunk3, chunk3_num); + // release old chunk + _eina_debug_chunk_noneed(chunk3, chunk3_size); + // switch to our new 2x as big chunk + chunk3 = newchunk; + chunk3_size = chunk3_size * 2; + } + // get the mem at the top of this stack and return it, then move along + ptr = chunk3 + chunk3_num; + chunk3_num += size; + return ptr; +} + +void +_eina_debug_chunk_tmp_reset(void) +{ + chunk3_num = 0; +} +# else +// implement with static buffers - once we exceed these we will fail. sorry +// maybe one day find another solution, but these buffers should be enough +// for now for thos eplatforms (like windows) where we can't do the mmap +// tricks above. +static unsigned char chunk1[8 * 1024]; +static unsigned char chunk2[4 * 1024]; +static unsigned char chunk3[128 * 1024]; +static int chunk1_size = sizeof(chunk1); +static int chunk1_num = 0; +static int chunk2_size = sizeof(chunk2); +static int chunk2_num = 0; +static int chunk3_size = sizeof(chunk3); +static int chunk3_num = 0; + +// push a new bit of mem on our growing stack of mem - given our workload, +// we never free anything here, only ever grow new things on this stack +void * +_eina_debug_chunk_push(int size) +{ + void *ptr; + + // round size up to the nearest pointer size for alignment + size = sizeof(void *) * ((size + sizeof(void *) - 1) / sizeof(void *)); + // if we ran out of space - fail + if ((chunk1_num + size) > chunk1_size) return NULL; + // get the mem at the top of this stack and return it, then move along + ptr = chunk1 + chunk1_num; + chunk1_num += size; + return ptr; +} + +// grow a single existing chunk (we use this for the filename -> path lookup) +void * +_eina_debug_chunk_realloc(int size) +{ + // if we ran out of space - fail + if (size > chunk2_size) return NULL; + // record new size and return chunk ptr as we just re-use it + chunk2_num = size; + return chunk2; +} + +// grow a single existing chunk (we use this for the filename -> path lookup) +void * +_eina_debug_chunk_tmp_push(int size) +{ + void *ptr; + + // round size up to the nearest pointer size for alignment + size = sizeof(void *) * ((size + sizeof(void *) - 1) / sizeof(void *)); + // if we ran out of space - fail + if ((chunk3_num + size) > chunk3_size) return NULL; + // get the mem at the top of this stack and return it, then move along + ptr = chunk3 + chunk1_num; + chunk3_num += size; + return ptr; +} + +void +_eina_debug_chunk_tmp_reset(void) +{ + chunk3_num = 0; +} +# endif + +// handy - duplicate a string on our growing stack - never expect to free it +char * +_eina_debug_chunk_strdup(const char *str) +{ + int len = strlen(str); + char *s = _eina_debug_chunk_push(len + 1); + if (!s) return NULL; + strcpy(s, str); + return s; +} +#endif diff --git a/src/lib/eina/eina_debug_monitor.c b/src/lib/eina/eina_debug_monitor.c new file mode 100644 index 0000000000..74833f3a87 --- /dev/null +++ b/src/lib/eina/eina_debug_monitor.c @@ -0,0 +1,320 @@ +#include "eina_debug.h" + +#ifdef EINA_HAVE_DEBUG + +#define DEBUG_SERVER ".ecore/efl_debug/0" + +extern pthread_t _eina_debug_thread_mainloop; +extern volatile pthread_t *_eina_debug_thread_active; +extern volatile int _eina_debug_thread_active_num; + +int _eina_debug_monitor_service_fd = -1; +Eina_Semaphore _eina_debug_monitor_return_sem; + +static Eina_Bool _monitor_thread_runs = EINA_FALSE; +static pthread_t _monitor_thread; + +// _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; + +static inline int +_eina_debug_unwind_bt(void **bt, int max) +{ + unw_cursor_t cursor; + unw_context_t uc; + unw_word_t p; + int total; + + unw_getcontext(&uc); + unw_init_local(&cursor, &uc); + for (total = 0; (unw_step(&cursor) > 0) && (total < max); total++) + { + unw_get_reg(&cursor, UNW_REG_IP, &p); + bt[total] = (void *)p; + } + return total; +} + +static void +_eina_debug_signal(int sig EINA_UNUSED, + siginfo_t *si EINA_UNUSED, + void *foo EINA_UNUSED) +{ + int i, slot = 0; + pthread_t self = pthread_self(); + clockid_t cid; + // XXX: use pthread_getcpuclockid() to get cpu time used since last poll + // + // clockid_t cid; + // struct timespec ts ts; + // pthread_getcpuclockid(pthread_self(), &cid); + // clock_gettime(cid, &ts); + // printf("%4ld.%03ld\n", ts.tv_sec, ts.tv_nsec / 1000000); + // + // also get current cpu with: + // getcpu() + if (self != _eina_debug_thread_mainloop) + { + for (i = 0; i < _eina_debug_thread_active_num; i++) + { + if (self == _eina_debug_thread_active[i]) + { + slot = i + 1; + goto found; + } + } + fprintf(stderr, "EINA DEBUG ERROR: can't find thread slot!\n"); + eina_semaphore_release(&_eina_debug_monitor_return_sem, 1); + return; + } +found: +// printf("dump into slot %i for %p\n", slot, (void *)self); + _bt_cpu[slot] = sched_getcpu(); + pthread_getcpuclockid(self, &cid); + clock_gettime(cid, &(_bt_ts[slot])); +// _bt_buf_len[slot] = backtrace(_bt_buf[slot], EINA_MAX_BT); + _bt_buf_len[slot] = _eina_debug_unwind_bt(_bt_buf[slot], EINA_MAX_BT); + eina_semaphore_release(&_eina_debug_monitor_return_sem, 1); +} + +#define SIG SIGPROF +//#define SIG ((SIGRTMIN + SIGRTMAX) / 2) + +static inline double +get_time(void) +{ + struct timeval timev; + gettimeofday(&timev, NULL); + return (double)timev.tv_sec + (((double)timev.tv_usec) / 1000000.0); +} + +static void +_eina_debug_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 + pthread_kill(pth, SIG); +} + +// this is a DEDICATED debug thread to monitor the application so it works +// even if the mainloop is blocked or the app otherwise deadlocked in some +// way. this is an alternative to using external debuggers so we can get +// users or developers to get useful information about an app at all times +static void * +_eina_debug_monitor(void *data EINA_UNUSED) +{ + int bts = 0, ret, max_fd; + double t0, t; + fd_set rfds, wfds, exfds; + struct timeval tv = { 0 }; + unsigned int poll_time = 1000; + Eina_Bool poll_on = EINA_FALSE; + + t0 = get_time(); + for (;;) + { + int i; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&exfds); + FD_SET(_eina_debug_monitor_service_fd, &rfds); + max_fd = _eina_debug_monitor_service_fd; + if (poll_on) + { + if ((tv.tv_sec == 0) && (tv.tv_usec == 0)) + { + tv.tv_sec = 0; + tv.tv_usec = poll_time; + } + ret = select(max_fd + 1, &rfds, &wfds, &exfds, &tv); + } + else ret = select(max_fd + 1, &rfds, &wfds, &exfds, NULL); + if ((ret == 1) && (FD_ISSET(_eina_debug_monitor_service_fd, &rfds))) + { + unsigned int size; + int rret; + + // XXX: handle protocol + rret = read(_eina_debug_monitor_service_fd, &size, 4); + if ((rret == 4) && (size > 0) && (size < 63356)) + { + char *buf = alloca(size); + + rret = read(_eina_debug_monitor_service_fd, buf, size); + if ((rret == (int)size) && (size >= 4)) + { + if (!strncmp(buf, "PLON", 4)) + { + if (size >= 8) memcpy(&poll_time, buf + 4, 4); + poll_on = EINA_TRUE; + } + else if (!strncmp(buf, "PLOF", 4)) + { + poll_time = 1000; + poll_on = EINA_FALSE; + } + else + fprintf(stderr, "EINA DEBUG ERROR: Uunknown command\n"); + } + else + { + if (rret <= 0) + { + fprintf(stderr, "EINA DEBUG ERROR: Lost debug daemon!\n"); + goto fail; + } + else + { + } + } + } + else + { + if (rret <= 0) + { + fprintf(stderr, "EINA_DEBUG ERROR: Lost debug daemon!\n"); + goto fail; + } + else + { + fprintf(stderr, "EINA DEBUG ERROR: Invalid message size %i\n", size); + goto fail; + } + } + } + + if (poll_on) + { + // take a lock on grabbing thread debug info like backtraces + eina_spinlock_take(&_eina_debug_thread_lock); + // 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 + ((1 + _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 + ((1 + _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 + ((1 + _eina_debug_thread_active_num) * sizeof(int)); + if (!_bt_cpu) goto err; + // now get an array of void pts for mainloop bt + _bt_buf[0] = _eina_debug_chunk_tmp_push(EINA_MAX_BT * sizeof(void *)); + if (!_bt_buf[0]) 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 + 1] = _eina_debug_chunk_tmp_push(EINA_MAX_BT * sizeof(void *)); + if (!_bt_buf[i + 1]) goto err; + } + // get an array of ints to stor the bt len for mainloop + threads + _bt_buf_len = _eina_debug_chunk_tmp_push + ((1 + _eina_debug_thread_active_num) * sizeof(int)); + // collect bt from the mainloop - always there + _eina_debug_collect_bt(_eina_debug_thread_mainloop); + // now collect per thread + for (i = 0; i < _eina_debug_thread_active_num; i++) + _eina_debug_collect_bt(_eina_debug_thread_active[i]); + // 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 + 1); i++) + eina_semaphore_lock(&_eina_debug_monitor_return_sem); + // we now have gotten all the data from all threadd + mainloop. + // we can process it now as we see fit, so release thread lock +// for (i = 0; i < (_eina_debug_thread_active_num + 1); i++) +// { +// _eina_debug_dump_fhandle_bt(stderr, _bt_buf[i], _bt_buf_len[i]); +// } +err: + eina_spinlock_release(&_eina_debug_thread_lock); + bts++; + if (bts >= 10000) + { + t = get_time(); + fprintf(stderr, "%1.5f bt's per sec\n", (double)bts / (t - t0)); + t0 = t; + bts = 0; + } + } + } +fail: + close(_eina_debug_monitor_service_fd); + _eina_debug_monitor_service_fd = -1; + return NULL; +} + +// start up the debug monitor if we haven't already +void +_eina_debug_monitor_thread_start(void) +{ + int err; + + if (_monitor_thread_runs) return; + // XXX: set up socket conn to debug daemon and then have thread deal with + // it from there on + err = pthread_create(&_monitor_thread, NULL, _eina_debug_monitor, NULL); + if (err != 0) + { + fprintf(stderr, "EINA DEBUG ERROR: Can't create debug thread!\n"); + abort(); + } + else _monitor_thread_runs = EINA_TRUE; +} + +void +_eina_debug_monitor_signal_init(void) +{ + struct sigaction sa; + + sa.sa_sigaction = _eina_debug_signal; + sa.sa_flags = SA_RESTART | SA_SIGINFO; + sigemptyset(&sa.sa_mask); + if (sigaction(SIG, &sa, NULL) != 0) + fprintf(stderr, "EINA DEBUG ERROR: Can't set up sig %i handler!\n", SIG); +} + +static const char * +_socket_home_get() +{ + const char *dir = getenv("XDG_RUNTIME_DIR"); + if (!dir) dir = getenv("HOME"); + if (!dir) dir = getenv("TMPDIR"); + if (!dir) dir = "/tmp"; + return dir; +} + +void +_eina_debug_monitor_service_connect(void) +{ + char buf[4096]; + int fd, socket_unix_len, curstate = 0; + struct sockaddr_un socket_unix; + + snprintf(buf, sizeof(buf), "%s/%s", _socket_home_get(), DEBUG_SERVER); + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) goto err; + if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) goto err; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void *)&curstate, + sizeof(curstate)) < 0) + goto err; + socket_unix.sun_family = AF_UNIX; + strncpy(socket_unix.sun_path, buf, sizeof(socket_unix.sun_path)); +#define LENGTH_OF_SOCKADDR_UN(s) \ + (strlen((s)->sun_path) + (size_t)(((struct sockaddr_un *)NULL)->sun_path)) + socket_unix_len = LENGTH_OF_SOCKADDR_UN(&socket_unix); + if (connect(fd, (struct sockaddr *)&socket_unix, socket_unix_len) < 0) + goto err; + _eina_debug_monitor_service_fd = fd; + return; +err: + close(fd); + return; +} +#endif diff --git a/src/lib/eina/eina_debug_proto.c b/src/lib/eina/eina_debug_proto.c new file mode 100644 index 0000000000..44ab536664 --- /dev/null +++ b/src/lib/eina/eina_debug_proto.c @@ -0,0 +1,19 @@ +#include "eina_debug.h" + +#ifdef EINA_HAVE_DEBUG + +void +_eina_debug_monitor_service_greet(void) +{ + const char *hello = "HELO"; + unsigned int version = 1; + unsigned int msize = 4 + 4 + 4; + unsigned int pid = getpid(); + unsigned char buf[16]; + memcpy(buf + 0, &msize, 4); + memcpy(buf + 4, hello, 4); + memcpy(buf + 8, &version, 4); + memcpy(buf + 12, &pid, 4); + write(_eina_debug_monitor_service_fd, buf, sizeof(buf)); +} +#endif diff --git a/src/lib/eina/eina_debug_thread.c b/src/lib/eina/eina_debug_thread.c new file mode 100644 index 0000000000..e3c8d7a389 --- /dev/null +++ b/src/lib/eina/eina_debug_thread.c @@ -0,0 +1,80 @@ +#include "eina_debug.h" + +#ifdef EINA_HAVE_DEBUG + +// a really simple store of currently known active threads. the mainloop is +// special and inittied at debug init time - assuming eina inits in the +// mainloop thread (whihc is expected). also a growable array of thread +// id's for other threads is held here so we can loop over them and do things +// like get them to stop and dump a backtrace for us +Eina_Spinlock _eina_debug_thread_lock; + +pthread_t _eina_debug_thread_mainloop = 0; +pthread_t *_eina_debug_thread_active = NULL; +int _eina_debug_thread_active_num = 0; + +static int _thread_active_size = 0; + +// add a thread id to our tracking array - very simple. add to end, and +// if array to small, reallocate it to be bigger by 16 slots AND double that +// size (so grows should slow down FAST). we will never shrink this array +void +_eina_debug_thread_add(void *th) +{ + pthread_t *pth = th; + // take thread tracking lock + eina_spinlock_take(&_eina_debug_thread_lock); + // if we don't have enough space to store thread id's - make some more + if (_thread_active_size < (_eina_debug_thread_active_num + 1)) + { + pthread_t *threads = realloc + (_eina_debug_thread_active, + ((_eina_debug_thread_active_num + 16) * 2) * sizeof(pthread_t *)); + if (threads) + { + _eina_debug_thread_active = threads; + _thread_active_size = (_eina_debug_thread_active_num + 16) * 2; + } + } + // add new thread id to the end + _eina_debug_thread_active[_eina_debug_thread_active_num] = *pth; + _eina_debug_thread_active_num++; + // release our lock cleanly + eina_spinlock_release(&_eina_debug_thread_lock); +} + +// remove a thread id from our tracking array - simply find and shuffle all +// later elements down. this array should be small almsot all the time and +// shouldn't bew changing THAT often for this to matter +void +_eina_debug_thread_del(void *th) +{ + pthread_t *pth = th; + int i; + // take a thread tracking lock + eina_spinlock_take(&_eina_debug_thread_lock); + // find the thread id to remove + for (i = 0; i < _eina_debug_thread_active_num; i++) + { + if (_eina_debug_thread_active[i] == *pth) + { + // found it - now shuffle down all further thread id's in array + for (; i < (_eina_debug_thread_active_num - 1); i++) + _eina_debug_thread_active[i] = _eina_debug_thread_active[i + 1]; + // reduce our counter and get out of loop + _eina_debug_thread_active_num--; + break; + } + } + // release lock cleanly + eina_spinlock_release(&_eina_debug_thread_lock); +} + +// register the thread that is the mainloop - always there +void +_eina_debug_thread_mainloop_set(void *th) +{ + pthread_t *pth = th; + _eina_debug_thread_mainloop = *pth; +} +#endif diff --git a/src/lib/eina/eina_log.c b/src/lib/eina/eina_log.c index c3174ff3a5..5f458f01d0 100644 --- a/src/lib/eina/eina_log.c +++ b/src/lib/eina/eina_log.c @@ -29,11 +29,6 @@ #include #include -#if defined HAVE_EXECINFO_H && defined HAVE_BACKTRACE && defined HAVE_BACKTRACE_SYMBOLS -# include -# define EINA_LOG_BACKTRACE -#endif - #ifdef HAVE_SYSTEMD # include #endif @@ -42,6 +37,11 @@ # include #endif +#include "eina_debug.h" +#ifdef EINA_HAVE_DEBUG +# define EINA_LOG_BACKTRACE +#endif + #include "eina_config.h" #include "eina_private.h" #include "eina_inlist.h" @@ -121,7 +121,7 @@ static Eina_Bool _disable_timing = EINA_TRUE; static int _abort_level_on_critical = EINA_LOG_LEVEL_CRITICAL; #ifdef EINA_LOG_BACKTRACE -static int _backtrace_level = -1; +static int _backtrace_level = 999; #endif static Eina_Bool _threads_enabled = EINA_FALSE; @@ -1849,21 +1849,11 @@ eina_log_domain_registered_level_set(int domain, int level) } #ifdef EINA_LOG_BACKTRACE -# define DISPLAY_BACKTRACE(File, Level) \ - if (EINA_UNLIKELY(Level < _backtrace_level)) \ - { \ - void *bt[256]; \ - char **strings; \ - int btlen; \ - int i; \ - \ - btlen = backtrace((void **)bt, 256); \ - strings = backtrace_symbols((void **)bt, btlen); \ - fprintf(File, "*** Backtrace ***\n"); \ - for (i = 0; i < btlen; ++i) \ - fprintf(File, "%s\n", strings[i]); \ - free(strings); \ - } +# define DISPLAY_BACKTRACE(File, Level) \ + if (EINA_UNLIKELY(Level < _backtrace_level)) { \ + fprintf(File, "*** Backtrace ***\n"); \ + EINA_BT(File); \ + } #else # define DISPLAY_BACKTRACE(File, Level) #endif diff --git a/src/lib/eina/eina_main.c b/src/lib/eina/eina_main.c index ea85b30b80..358c3a4bfb 100644 --- a/src/lib/eina/eina_main.c +++ b/src/lib/eina/eina_main.c @@ -120,6 +120,7 @@ EAPI Eina_Inlist *_eina_tracking = NULL; */ #define S(x) extern Eina_Bool eina_ ## x ## _init(void); \ extern Eina_Bool eina_ ## x ## _shutdown(void) + S(debug); S(log); S(error); S(safety_checks); @@ -165,6 +166,7 @@ struct eina_desc_setup static const struct eina_desc_setup _eina_desc_setup[] = { #define S(x) {# x, eina_ ## x ## _init, eina_ ## x ## _shutdown} /* log is a special case as it needs printf */ + S(debug), S(stringshare), S(error), S(safety_checks), diff --git a/src/lib/eina/eina_thread.c b/src/lib/eina/eina_thread.c index 2924fc7360..86cb8a932b 100644 --- a/src/lib/eina/eina_thread.c +++ b/src/lib/eina/eina_thread.c @@ -29,6 +29,8 @@ /* undefs EINA_ARG_NONULL() so NULL checks are not compiled out! */ #include "eina_safety_checks.h" +#include "eina_debug.h" + # include # include @@ -100,13 +102,20 @@ _eina_internal_call(void *context) { Eina_Thread_Call *c = context; void *r; + pthread_t self; if (c->prio == EINA_THREAD_BACKGROUND || c->prio == EINA_THREAD_IDLE) eina_sched_prio_drop(); - /* FIXME: set priority and affinity */ + self = pthread_self(); +#ifdef EINA_HAVE_DEBUG + _eina_debug_thread_add(&self); +#endif r = c->func((void*) c->data, eina_thread_self()); +#ifdef EINA_HAVE_DEBUG + _eina_debug_thread_del(&self); +#endif free(c);