From 0d8e054556de4ef8a6d77db5fcb42e0fb8b837f2 Mon Sep 17 00:00:00 2001 From: Jean Guyomarc'h Date: Sat, 28 May 2016 18:26:10 +0200 Subject: [PATCH] eina_btlog: add Mac OS X support for backtrace This was actually difficult... Mac OS X can use addr2line (sometimes called gaddr2line in function of the package managers). However, addr2line does NOT handle specific cases. It was therefore necessary to use Mac OS X' own tool: atos, which gracefully handles all backtraces, including the one containing objective-c messages or fat archives. eina_btlog now tests different utilities one by one, and determines whether it is supported or not. Fixes T3711 --- src/bin/eina/eina_btlog.c | 169 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 164 insertions(+), 5 deletions(-) diff --git a/src/bin/eina/eina_btlog.c b/src/bin/eina/eina_btlog.c index d3cbf77c5c..6c03fd8e47 100644 --- a/src/bin/eina/eina_btlog.c +++ b/src/bin/eina/eina_btlog.c @@ -27,14 +27,18 @@ // 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 @@ -47,6 +51,25 @@ struct _Bt int line; }; +typedef Eina_Bool (*Translate_Func)(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; +}; + +static Translate_Func _translate = NULL; + static void path_split(const char *path, char **dir, char **file) { @@ -119,6 +142,88 @@ _addr2line(const char *bin_dir, const char *bin_name, unsigned long long addr, return ok; } +#ifdef ATOS_COMPATIBLE +static Eina_Bool +_atos(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), "atos -o %s/%s 0x%llx", 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 == '-') /* objc method... 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 = 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 = 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 Eina_List * bt_append(Eina_List *btl, const char *btline) { @@ -139,11 +244,11 @@ bt_append(Eina_List *btl, const char *btline) 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, + if (!_translate(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, + if (!_translate(bt->bin_dir, bt->bin_name, offset, &(bt->file_dir), &(bt->file_name), &(bt->func_name), &(bt->line))) { @@ -159,6 +264,32 @@ bt_append(Eina_List *btl, const char *btline) 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); + ret = WEXITSTATUS(ret); + if (ret == 0) + { + _translate = d->func; + break; + } + } + d++; + } + + return (_translate == NULL) ? EINA_FALSE : EINA_TRUE; +} + int main(void) { @@ -166,8 +297,36 @@ main(void) char buf[4096]; Bt *bt; int cols[6] = { 0 }, len, i; + const Translation_Desc desc[] = { +#ifdef ATOS_COMPATIBLE + { /* Mac OS X */ + .name = "atos", + .test = "atos --help &> /dev/null", + .func = _atos + }, +#endif + { /* GNU binutils */ + .name = "addr2line", + .test = "addr2line --help &> /dev/null", + .func = _addr2line + }, + { /* For imported GNU binutils */ + .name = "GNU addr2line", + .test = "gaddr2line --help &> /dev/null", + .func = _addr2line + }, + { NULL, NULL, NULL } /* Sentinel */ + }; eina_init(); + + if (!_translation_function_detect(desc)) + { + EINA_LOG_CRIT("Fail to determine a program to translate backtrace " + "into human-readable text"); + return 1; + } + while (fgets(buf, sizeof(buf) - 1, stdin)) { btl = bt_append(btl, buf);