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