eina_btlog: add Mac OS X support for backtrace
authorJean Guyomarc'h <jean@guyomarch.bzh>
Sat, 28 May 2016 16:26:10 +0000 (18:26 +0200)
committerJean Guyomarc'h <jean@guyomarch.bzh>
Sat, 28 May 2016 17:19:02 +0000 (19:19 +0200)
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

index d3cbf77..6c03fd8 100644 (file)
 // 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);