1 /* EINA - EFL data type library
2  * Copyright (C) 2015 Carsten Haitzler
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library;
16  * if not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <Eina.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 
24 // right now this is quick and dirty and may have some parsing ... frailty,
25 // so don't put malicious data through it... :) but cat in eina bt's through
26 // this to get a nicely clean and readable bt with filenames of binaries,
27 // shared objects, source files, and line numbers. even nicely colored and
28 // columnated. this is more the start of a bunch of debug tools for efl to make
29 // it easier to identify issues.
30 //
31 // how to use:
32 //
33 // cat mybacktrace.txt | eina_btlog
34 //
35 // (or just run it and copy & paste in on stdin - what i do mostly, and out
36 // pops a nice backtrace, hit ctrl+d to end)
37 
38 #if defined (__MacOSX__) || (defined (__MACH__) && defined (__APPLE__))
39 # define ATOS_COMPATIBLE
40 #endif
41 
42 typedef struct _Bt Bt;
43 
44 struct _Bt
45 {
46    char *bin_dir;
47    char *bin_name;
48    char *file_dir;
49    char *file_name;
50    char *func_name;
51    char *comment;
52    int line;
53 };
54 
55 typedef Eina_Bool (*Translate_Func)(const char *prog,
56                                     const char *bin_dir,
57                                     const char *bin_name,
58                                     unsigned long long addr,
59                                     char **file_dir,
60                                     char **file_name,
61                                     char **func_name,
62                                     int *file_line);
63 
64 typedef struct _Translation_Desc Translation_Desc;
65 
66 struct _Translation_Desc
67 {
68    const char *name;
69    const char *test;
70    Translate_Func func;
71    const char *prog;
72 };
73 
74 static Translate_Func _translate = NULL;
75 static const char *_prog = NULL;
76 static Eina_Bool color = EINA_TRUE;
77 static Eina_Bool show_comments = EINA_TRUE;
78 static Eina_Bool show_compact = EINA_FALSE;
79 
80 static void
path_split(const char * path,char ** dir,char ** file)81 path_split(const char *path, char **dir, char **file)
82 {
83    const char *p;
84 
85    if (!path)
86      {
87         *dir = NULL;
88         *file = NULL;
89         return;
90      }
91    p = strrchr(path, '/');
92    if (!p)
93      {
94         *dir = NULL;
95         *file = strdup(path);
96         return;
97      }
98    *dir = malloc(p - path + 1);
99    if (!*dir)
100      {
101         *dir = NULL;
102         *file = NULL;
103         return;
104      }
105    strncpy(*dir, path, p - path);
106    (*dir)[p - path] = 0;
107    *file = strdup(p + 1);
108 }
109 
110 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)111 _addr2line(const char *prog, const char *bin_dir, const char *bin_name, unsigned long long addr,
112            char **file_dir, char **file_name, char **func_name, int *file_line)
113 {
114    char buf[4096], func[4096], *f1 = NULL, *f2 = NULL;
115    Eina_Bool ok = EINA_FALSE;
116    int line;
117    FILE *p;
118 
119    snprintf(buf, sizeof(buf), "%s -f -e %s/%s -C -a 0x%llx",
120             prog, bin_dir, bin_name, addr);
121    p = popen(buf, "r");
122    if (!p) return EINA_FALSE;
123    if ((fscanf(p, "%4095s\n", buf) == 1) &&
124        (fscanf(p, "%4095s\n", func) == 1))
125      {
126         if (fscanf(p, "%[^:]:%i\n", buf, &line) == 2)
127           {
128              path_split(buf, &(f1), &(f2));
129              if ((!f1) || (!f2))
130                {
131                   free(f1);
132                   free(f2);
133                   pclose(p);
134                   return EINA_FALSE;
135                }
136           }
137         else
138           {
139              f1 = strdup("??");
140              f2 = strdup("??");
141           }
142         *file_dir = f1;
143         *file_name = f2;
144         *func_name = strdup(func);
145         *file_line = line;
146         ok = EINA_TRUE;
147      }
148    pclose(p);
149    return ok;
150 }
151 
152 #ifdef ATOS_COMPATIBLE
153 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)154 _atos(const char *prog, const char *bin_dir, const char *bin_name, unsigned long long addr,
155       char **file_dir, char **file_name, char **func_name, int *file_line)
156 {
157    char buf[4096];
158    FILE *p = NULL;
159    char *f1 = NULL, *s;
160    Eina_Bool ret = EINA_FALSE;
161    unsigned int count = 0, len;
162    Eina_Bool func_done = EINA_FALSE;
163    unsigned int spaces = 0, func_space_count;
164 
165    // Example of what we want to parse
166    // $ atos -o /usr/local/lib/libevas.1.dylib 0xa82d
167    // evas_object_clip_recalc (in libevas.1.dylib) (evas_inline.x:353)
168    //
169    // WARNING! Sometimes:
170    // tlv_load_notification (in libdyld.dylib) + 382
171    //
172    // WARNING! Objective-C methods:
173    // -[EcoreCocoaWindow windowDidResize:] (in libecore_cocoa.1.dylib) (ecore_cocoa_window.m:97)
174 
175    snprintf(buf, sizeof(buf), "%s -o %s/%s 0x%llx", prog, bin_dir, bin_name, addr);
176    p = popen(buf, "r");
177    if (!p) goto end;
178 
179    s = fgets(buf, sizeof(buf), p);
180    if (!s) goto end;
181 
182    /* Default value, used as a fallback when cannot be determined */
183    *file_line = -1;
184 
185    if ((*s == '-') || (*s == '+')) /* objc methods... will contain an extra space */
186      func_space_count = 2;
187    else
188      func_space_count = 1;
189 
190    do
191      {
192         if (*s == ' ') spaces++;
193 
194         if ((spaces == func_space_count) && (func_done == EINA_FALSE))
195           {
196              *s = '\0';
197              *func_name = eina_strndup(buf, (int)(s - &(buf[0])));
198              func_done = EINA_TRUE;
199           }
200         else if (*s == '(')
201           {
202              count++;
203              if ((count == 2) && (f1 == NULL))
204                {
205                   f1 = s + 1; /* skip the leading '(' */
206                }
207           }
208         else if ((*s == ':') && (func_done == EINA_TRUE))
209           {
210              *s = '\0';
211              *file_name = eina_strndup(f1, (int)(s - f1));
212              s++;
213              len = strlen(s);
214              s[len - 1] = '\0'; /* Remove the closing parenthesis */
215              *file_line = atoi(s);
216              break; /* Done */
217           }
218      }
219    while (*(++s) != '\0');
220 
221    /* Cannot be determined */
222    *file_dir = strdup("??");
223 
224    if (!*func_name) *func_name = strdup("??");
225    if (!*file_name) *file_name = strdup("??");
226 
227    ret = EINA_TRUE;
228 end:
229    if (p) pclose(p);
230    return ret;
231 }
232 #endif
233 
234 static const char *
bt_input_translate(const char * line,char ** comment)235 bt_input_translate(const char *line, char **comment)
236 {
237    static char local[PATH_MAX + sizeof(" 0x1234567890123456789 0x1234567890123456789\n")];
238    const char *addrstart, *addrend, *filestart, *fileend, *basestart, *baseend;
239 
240    /* new bt format is more human readable, but needs some cleanup before we bt_append()
241     *
242     * Example:
243     *   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)
244     *   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)
245     */
246    *comment = NULL;
247 
248    addrstart = strstr(line, "0x");
249    if (!addrstart) return NULL;
250 
251    addrend = strchr(addrstart, ':');
252    if (!addrend) return NULL;
253 
254    filestart = strstr(addrend, "(in ");
255    if (!filestart) return NULL;
256 
257    filestart += strlen("(in ");
258    basestart = strstr(filestart, " 0x");
259    if (!basestart) return NULL;
260    fileend = basestart;
261    basestart += strlen(" ");
262    baseend = strchr(basestart, ')');
263    if (!baseend) return NULL;
264 
265    snprintf(local, sizeof(local), "%.*s %.*s %.*s\n",
266             (int)(fileend - filestart), filestart,
267             (int)(addrend - addrstart), addrstart,
268             (int)(baseend - basestart), basestart);
269    *comment = eina_strndup(line, addrstart - line);
270    return local;
271 }
272 
273 static Eina_List *
bt_append(Eina_List * btl,const char * btline)274 bt_append(Eina_List *btl, const char *btline)
275 {
276    Bt *bt = calloc(1, sizeof(Bt));
277    if (!bt) return btl;
278    const char *translation;
279    char *comment = NULL;
280    char *bin = strdup(btline);
281    unsigned long long offset = 0, base = 0;
282 
283    translation = bt_input_translate(btline, &comment);
284    if (translation)
285      btline = translation;
286 
287    // parse:
288    // /usr/local/lib/libeina.so.1 0x1ec88
289    // /usr/local/lib/libelementary.so.1 0x10f695
290    // /usr/local/lib/libeo.so.1 0xa474
291    // /usr/local/lib/libelementary.so.1 0x139bd6
292    // /usr/local/bin/elementary_test 0x8196d
293    // /usr/local/bin/elementary_test 0x81b6a
294    if (sscanf(btline, "%s %llx %llx", bin, &offset, &base) == 3)
295      {
296         path_split(bin, &(bt->bin_dir), &(bt->bin_name));
297         if (!bt->bin_dir) bt->bin_dir = strdup("");
298         if (!bt->bin_name) bt->bin_name = strdup("");
299         if (!_translate(_prog, bt->bin_dir, bt->bin_name, offset - base,
300                         &(bt->file_dir), &(bt->file_name),
301                         &(bt->func_name), &(bt->line)))
302           {
303              if (!_translate(_prog, bt->bin_dir, bt->bin_name, offset,
304                              &(bt->file_dir), &(bt->file_name),
305                              &(bt->func_name), &(bt->line)))
306                {
307                   bt->file_dir = strdup("");
308                   bt->file_name = strdup("");
309                   bt->func_name = strdup("");
310                }
311           }
312         bt->comment = comment;
313         btl = eina_list_append(btl, bt);
314      }
315    else
316      {
317         free(comment);
318         bt->comment = strdup(btline);
319         btl = eina_list_append(btl, bt);
320      }
321    free(bin);
322 
323    return btl;
324 }
325 
326 static Eina_Bool
_translation_function_detect(const Translation_Desc * desc)327 _translation_function_detect(const Translation_Desc *desc)
328 {
329    const Translation_Desc *d = desc;
330    FILE *p;
331    int ret;
332 
333    while ((d->name != NULL) && (d->func != NULL) && (d->test != NULL))
334      {
335          p = popen(d->test, "r");
336          if (p)
337            {
338               ret = pclose(p);
339               if (ret == 0)
340                 {
341                    _translate = d->func;
342                    _prog = d->prog;
343                    break;
344                 }
345            }
346          d++;
347      }
348 
349    return (_translate == NULL) ? EINA_FALSE : EINA_TRUE;
350 }
351 
352 int
main(int argc,char ** argv)353 main(int argc, char **argv)
354 {
355    Eina_List *btl = NULL, *l;
356    char buf[4096];
357    Bt *bt;
358    int cols[6] = { 0 }, len, i;
359    const char *func_color = "";
360    const char *dir_color = "";
361    const char *sep_color = "";
362    const char *file_color = "";
363    const char *line_color = "";
364    const char *reset_color = "";
365    const Translation_Desc desc[] = {
366 #ifdef ATOS_COMPATIBLE
367         { /* Mac OS X */
368            .name = "atos",
369            .test = "atos --help &> /dev/null",
370            .func = _atos,
371            .prog = "atos"
372         },
373         { /* Mac OS X */
374            .name = "atos (old)",
375            .test = "xcrun atos --help &> /dev/null",
376            .func = _atos,
377            .prog = "xcrun atos"
378         },
379 #endif
380         { /* GNU binutils */
381            .name = "addr2line",
382            .test = "addr2line --help &> /dev/null",
383            .func = _addr2line,
384            .prog = "addr2line"
385         },
386         { /* For imported GNU binutils */
387            .name = "GNU addr2line",
388            .test = "gaddr2line --help &> /dev/null",
389            .func = _addr2line,
390            .prog = "addr2line"
391         },
392         { NULL, NULL, NULL, NULL } /* Sentinel */
393    };
394 
395    eina_init();
396 
397    for (i = 1; i < argc; i++)
398      {
399         if (!strcmp(argv[i], "-h"))
400           {
401              printf("Usage: eina_btlog [-n]\n"
402                     "  -n   Do not use color escape codes\n"
403                     "  -C   Do not show comments (non-bt fragments)\n"
404                     "  -c   Show compact output format\n"
405                     "\n"
406                     "Provide addresses logged from EFL applications to stdin.\n"
407                     "Example:\n\n"
408                     "\tcat log.txt | eina_btlog\n"
409                     "\n");
410              eina_shutdown();
411              return 0;
412           }
413         else if (!strcmp(argv[i], "-n")) color = EINA_FALSE;
414         else if (!strcmp(argv[i], "-C")) show_comments = EINA_FALSE;
415         else if (!strcmp(argv[i], "-c")) show_compact = EINA_TRUE;
416      }
417 
418    if (color)
419      {
420         func_color = EINA_COLOR_GREEN;
421         dir_color = EINA_COLOR_BLUE;
422         sep_color = EINA_COLOR_CYAN;
423         file_color = EINA_COLOR_WHITE;
424         line_color = EINA_COLOR_YELLOW;
425         reset_color = EINA_COLOR_RESET;
426      }
427 
428    if (!_translation_function_detect(desc))
429      {
430         EINA_LOG_CRIT("Fail to determine a program to translate backtrace "
431                       "into human-readable text");
432         return 1;
433      }
434 
435  repeat:
436    while (fgets(buf, sizeof(buf) - 1, stdin))
437      {
438         btl = bt_append(btl, buf);
439         if (show_compact) goto do_show;
440         bt = eina_list_last_data_get(btl);
441         if (bt && !bt->bin_dir) break; /* flush once first non-bt is found */
442      }
443 
444    /* compute columns for expanded display */
445    for (i = 0; i < 6; i++) cols[i] = 0;
446    EINA_LIST_FOREACH(btl, l, bt)
447      {
448         if (!bt->bin_dir) continue;
449         len = strlen(bt->bin_dir);
450         if (len > cols[0]) cols[0] = len;
451         len = strlen(bt->bin_name);
452         if (len > cols[1]) cols[1] = len;
453 
454         len = strlen(bt->file_dir);
455         if (len > cols[2]) cols[2] = len;
456         len = strlen(bt->file_name);
457         if (len > cols[3]) cols[3] = len;
458 
459         snprintf(buf, sizeof(buf), "%i", bt->line);
460         len = strlen(buf);
461         if (len > cols[4]) cols[4] = len;
462 
463         len = strlen(bt->func_name);
464         if (len > cols[5]) cols[5] = len;
465      }
466 
467  do_show:
468    EINA_LIST_FOREACH(btl, l, bt)
469      {
470         if (bt->comment && show_comments)
471           fputs(bt->comment, stdout);
472         if (!bt->bin_dir) continue;
473 
474         if (show_compact)
475           {
476              printf("%s%s%s (in %s%s%s:%s%d%s)\n",
477                     func_color, bt->func_name, reset_color,
478                     file_color, bt->file_name, reset_color,
479                     line_color, bt->line, reset_color);
480              fflush(stdout);
481              continue;
482           }
483 
484         printf("    "
485                "%s%*s%s/%s%-*s%s" /* bin info */
486                "| "
487                "%s%*s%s/%s%-*s%s" /* file info */
488                ": "
489                "%s%*i%s" /* line info */
490                " @ "
491                "%s%s%s()%s\n", /* func info */
492                /* bin info */
493                dir_color, cols[0], bt->bin_dir, sep_color,
494                file_color, cols[1], bt->bin_name,
495                reset_color,
496                /* file info */
497                dir_color, cols[2], bt->file_dir, sep_color,
498                file_color, cols[3], bt->file_name,
499                reset_color,
500                /* line info */
501                line_color, cols[4], bt->line,
502                reset_color,
503                /* func info */
504                func_color, bt->func_name,
505                sep_color,
506                reset_color);
507      }
508    fflush(stdout);
509    EINA_LIST_FREE(btl, bt)
510      {
511         free(bt->bin_dir);
512         free(bt->bin_name);
513         free(bt->file_dir);
514         free(bt->file_name);
515         free(bt->func_name);
516         free(bt->comment);
517         free(bt);
518      }
519    /* if not EOF, then we just flushed due non-bt line, try again */
520    if (!feof(stdin)) goto repeat;
521 
522    eina_shutdown();
523 
524    return 0;
525 }
526