/* 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 . */ #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) #if defined (__MacOSX__) || (defined (__MACH__) && defined (__APPLE__)) # define ATOS_COMPATIBLE #endif typedef struct _Bt Bt; struct _Bt { char *bin_dir; char *bin_name; char *file_dir; char *file_name; char *func_name; char *comment; int line; }; typedef Eina_Bool (*Translate_Func)(const char *prog, const char *bin_dir, const char *bin_name, unsigned long long addr, char **file_dir, char **file_name, char **func_name, int *file_line); typedef struct _Translation_Desc Translation_Desc; struct _Translation_Desc { const char *name; const char *test; Translate_Func func; const char *prog; }; static Translate_Func _translate = NULL; static const char *_prog = NULL; static Eina_Bool color = EINA_TRUE; static Eina_Bool show_comments = EINA_TRUE; static Eina_Bool show_compact = EINA_FALSE; static void path_split(const char *path, char **dir, char **file) { const char *p; if (!path) { *dir = NULL; *file = NULL; return; } p = strrchr(path, '/'); 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 *prog, 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), "%s -f -e %s/%s -C -a 0x%llx", prog, bin_dir, bin_name, addr); p = popen(buf, "r"); if (!p) return EINA_FALSE; if ((fscanf(p, "%4095s\n", buf) == 1) && (fscanf(p, "%4095s\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; } #ifdef ATOS_COMPATIBLE static Eina_Bool _atos(const char *prog, 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]; FILE *p = NULL; char *f1 = NULL, *s; Eina_Bool ret = EINA_FALSE; unsigned int count = 0, len; Eina_Bool func_done = EINA_FALSE; unsigned int spaces = 0, func_space_count; // Example of what we want to parse // $ atos -o /usr/local/lib/libevas.1.dylib 0xa82d // evas_object_clip_recalc (in libevas.1.dylib) (evas_inline.x:353) // // WARNING! Sometimes: // tlv_load_notification (in libdyld.dylib) + 382 // // WARNING! Objective-C methods: // -[EcoreCocoaWindow windowDidResize:] (in libecore_cocoa.1.dylib) (ecore_cocoa_window.m:97) snprintf(buf, sizeof(buf), "%s -o %s/%s 0x%llx", prog, bin_dir, bin_name, addr); p = popen(buf, "r"); if (!p) goto end; s = fgets(buf, sizeof(buf), p); if (!s) goto end; /* Default value, used as a fallback when cannot be determined */ *file_line = -1; if ((*s == '-') || (*s == '+')) /* objc methods... will contain an extra space */ func_space_count = 2; else func_space_count = 1; do { if (*s == ' ') spaces++; if ((spaces == func_space_count) && (func_done == EINA_FALSE)) { *s = '\0'; *func_name = eina_strndup(buf, (int)(s - &(buf[0]))); func_done = EINA_TRUE; } else if (*s == '(') { count++; if ((count == 2) && (f1 == NULL)) { f1 = s + 1; /* skip the leading '(' */ } } else if ((*s == ':') && (func_done == EINA_TRUE)) { *s = '\0'; *file_name = eina_strndup(f1, (int)(s - f1)); s++; len = strlen(s); s[len - 1] = '\0'; /* Remove the closing parenthesis */ *file_line = atoi(s); break; /* Done */ } } while (*(++s) != '\0'); /* Cannot be determined */ *file_dir = strdup("??"); if (!*func_name) *func_name = strdup("??"); if (!*file_name) *file_name = strdup("??"); ret = EINA_TRUE; end: if (p) pclose(p); return ret; } #endif static const char * bt_input_translate(const char *line, char **comment) { static char local[PATH_MAX + sizeof(" 0x1234567890123456789 0x1234567890123456789\n")]; const char *addrstart, *addrend, *filestart, *fileend, *basestart, *baseend; /* new bt format is more human readable, but needs some cleanup before we bt_append() * * Example: * ERR<23314>:eo_lifecycle ../src/lib/eo/efl_object.eo.c:78 efl_del() 0x00000005c7c291: __libc_start_main+0xf1 (in /usr/lib/libc.so.6 0x5c5c000) * ERR<23314>:eo_lifecycle ../src/lib/eo/efl_object.eo.c:78 efl_del() 0x00000004e409aa: libeo_dbg.so+0x99aa (in src/lib/eo/.libs/libeo_dbg.so 0x4e37000) */ *comment = NULL; addrstart = strstr(line, "0x"); if (!addrstart) return NULL; addrend = strchr(addrstart, ':'); if (!addrend) return NULL; filestart = strstr(addrend, "(in "); if (!filestart) return NULL; filestart += strlen("(in "); basestart = strstr(filestart, " 0x"); if (!basestart) return NULL; fileend = basestart; basestart += strlen(" "); baseend = strchr(basestart, ')'); if (!baseend) return NULL; snprintf(local, sizeof(local), "%.*s %.*s %.*s\n", (int)(fileend - filestart), filestart, (int)(addrend - addrstart), addrstart, (int)(baseend - basestart), basestart); *comment = eina_strndup(line, addrstart - line); return local; } static Eina_List * bt_append(Eina_List *btl, const char *btline) { Bt *bt = calloc(1, sizeof(Bt)); if (!bt) return btl; const char *translation; char *comment = NULL; char *bin = strdup(btline); unsigned long long offset = 0, base = 0; translation = bt_input_translate(btline, &comment); if (translation) btline = translation; // 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 (!_translate(_prog, bt->bin_dir, bt->bin_name, offset - base, &(bt->file_dir), &(bt->file_name), &(bt->func_name), &(bt->line))) { if (!_translate(_prog, 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(""); } } bt->comment = comment; btl = eina_list_append(btl, bt); } else { free(comment); bt->comment = strdup(btline); btl = eina_list_append(btl, bt); } free(bin); return btl; } static Eina_Bool _translation_function_detect(const Translation_Desc *desc) { const Translation_Desc *d = desc; FILE *p; int ret; while ((d->name != NULL) && (d->func != NULL) && (d->test != NULL)) { p = popen(d->test, "r"); if (p) { ret = pclose(p); if (ret == 0) { _translate = d->func; _prog = d->prog; break; } } d++; } return (_translate == NULL) ? EINA_FALSE : EINA_TRUE; } int main(int argc, char **argv) { Eina_List *btl = NULL, *l; char buf[4096]; Bt *bt; int cols[6] = { 0 }, len, i; const char *func_color = ""; const char *dir_color = ""; const char *sep_color = ""; const char *file_color = ""; const char *line_color = ""; const char *reset_color = ""; const Translation_Desc desc[] = { #ifdef ATOS_COMPATIBLE { /* Mac OS X */ .name = "atos", .test = "atos --help &> /dev/null", .func = _atos, .prog = "atos" }, { /* Mac OS X */ .name = "atos (old)", .test = "xcrun atos --help &> /dev/null", .func = _atos, .prog = "xcrun atos" }, #endif { /* GNU binutils */ .name = "addr2line", .test = "addr2line --help &> /dev/null", .func = _addr2line, .prog = "addr2line" }, { /* For imported GNU binutils */ .name = "GNU addr2line", .test = "gaddr2line --help &> /dev/null", .func = _addr2line, .prog = "addr2line" }, { NULL, NULL, NULL, NULL } /* Sentinel */ }; eina_init(); for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "-h")) { printf("Usage: eina_btlog [-n]\n" " -n Do not use color escape codes\n" " -C Do not show comments (non-bt fragments)\n" " -c Show compact output format\n" "\n" "Provide addresses logged from EFL applications to stdin.\n" "Example:\n\n" "\tcat log.txt | eina_btlog\n" "\n"); eina_shutdown(); return 0; } else if (!strcmp(argv[i], "-n")) color = EINA_FALSE; else if (!strcmp(argv[i], "-C")) show_comments = EINA_FALSE; else if (!strcmp(argv[i], "-c")) show_compact = EINA_TRUE; } if (color) { func_color = EINA_COLOR_GREEN; dir_color = EINA_COLOR_BLUE; sep_color = EINA_COLOR_CYAN; file_color = EINA_COLOR_WHITE; line_color = EINA_COLOR_YELLOW; reset_color = EINA_COLOR_RESET; } if (!_translation_function_detect(desc)) { EINA_LOG_CRIT("Fail to determine a program to translate backtrace " "into human-readable text"); return 1; } repeat: while (fgets(buf, sizeof(buf) - 1, stdin)) { btl = bt_append(btl, buf); if (show_compact) goto do_show; bt = eina_list_last_data_get(btl); if (bt && !bt->bin_dir) break; /* flush once first non-bt is found */ } /* compute columns for expanded display */ for (i = 0; i < 6; i++) cols[i] = 0; EINA_LIST_FOREACH(btl, l, bt) { if (!bt->bin_dir) continue; 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; } do_show: EINA_LIST_FOREACH(btl, l, bt) { if (bt->comment && show_comments) fputs(bt->comment, stdout); if (!bt->bin_dir) continue; if (show_compact) { printf("%s%s%s (in %s%s%s:%s%d%s)\n", func_color, bt->func_name, reset_color, file_color, bt->file_name, reset_color, line_color, bt->line, reset_color); fflush(stdout); continue; } printf(" " "%s%*s%s/%s%-*s%s" /* bin info */ "| " "%s%*s%s/%s%-*s%s" /* file info */ ": " "%s%*i%s" /* line info */ " @ " "%s%s%s()%s\n", /* func info */ /* bin info */ dir_color, cols[0], bt->bin_dir, sep_color, file_color, cols[1], bt->bin_name, reset_color, /* file info */ dir_color, cols[2], bt->file_dir, sep_color, file_color, cols[3], bt->file_name, reset_color, /* line info */ line_color, cols[4], bt->line, reset_color, /* func info */ func_color, bt->func_name, sep_color, reset_color); } fflush(stdout); EINA_LIST_FREE(btl, bt) { free(bt->bin_dir); free(bt->bin_name); free(bt->file_dir); free(bt->file_name); free(bt->func_name); free(bt->comment); free(bt); } /* if not EOF, then we just flushed due non-bt line, try again */ if (!feof(stdin)) goto repeat; eina_shutdown(); return 0; }