1 /*
2 * Copyright (C) 2020-2021 Alexandros Theodotou <alex at zrythm dot org>
3 *
4 * This file is part of Zrythm
5 *
6 * Zrythm is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Zrythm is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with Zrythm. If not, see <https://www.gnu.org/licenses/>.
18 *
19 * This file incorporates work covered by the following copyright and
20 * permission notice:
21 *
22 Copyright (C) 2016 Florian Cabot
23
24 This program is free software; you can redistribute it and/or modify
25 it under the terms of the GNU General Public License as published by
26 the Free Software Foundation; either version 3 of the License, or
27 (at your option) any later version.
28
29 This program is distributed in the hope that it will be useful,
30 but WITHOUT ANY WARRANTY; without even the implied warranty of
31 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 GNU General Public License for more details.
33
34 * You should have received a copy of the GNU General Public License
35 * along with this program. If not, see <https://www.gnu.org/licenses/>.
36 */
37
38 #include "zrythm-config.h"
39
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #ifdef _WOE32
44 #include <windows.h>
45 #include <dbghelp.h>
46 #else
47 #include <execinfo.h>
48 #endif
49
50 #include "utils/backtrace.h"
51 #include "utils/datetime.h"
52 #include "utils/io.h"
53 #include "utils/string.h"
54 #include "zrythm.h"
55 #include "zrythm_app.h"
56
57 #include <gtk/gtk.h>
58
59 #ifdef HAVE_LIBBACKTRACE
60 #include <backtrace-supported.h>
61 #include <backtrace.h>
62 #if BACKTRACE_SUPPORTED
63 #define CAN_USE_LIBBACKTRACE 1
64 #endif
65 #endif
66
67 #if !defined (_WOE32)
68 /**
69 * Resolve symbol name and source location given
70 * the path to the executable and an address.
71 *
72 * @param buf Buffer to save the output in.
73 *
74 * @return 0 if address has been resolved and a
75 * message has been printed; else returns 1.
76 */
77 static int
addr2line(char const * const program_name,void const * const addr,char * buf,int lineNb)78 addr2line (
79 char const * const program_name,
80 void const * const addr,
81 char * buf,
82 int lineNb)
83 {
84 char addr2line_cmd[512] = {0};
85
86 /* have addr2line map the address to the relent line in the code */
87 #ifdef __APPLE__
88 /* apple does things differently... */
89 /* FIXME re-enable */
90 // sprintf(addr2line_cmd,"atos -o %.256s %p", program_name, addr);
91 return 1;
92 #else
93 sprintf (
94 addr2line_cmd, "addr2line -f -e %.256s %p",
95 program_name, addr);
96 #endif
97
98 /* This will print a nicely formatted string specifying the
99 function and source line of the address */
100
101 FILE *fp;
102 char outLine1[1035];
103 char outLine2[1035];
104
105 /* Open the command for reading. */
106 /* FIXME use reproc, this uses fork () */
107 fp = popen (addr2line_cmd, "r");
108 if (fp == NULL)
109 return 1;
110
111 while (fgets(outLine1, sizeof(outLine1)-1, fp) != NULL)
112 {
113 //if we have a pair of lines
114 if(fgets(outLine2, sizeof(outLine2)-1, fp) != NULL)
115 {
116 //if symbols are readable
117 if(outLine2[0] != '?')
118 {
119 //only let func name in outLine1
120 int i;
121 for(i = 0; i < 1035; ++i)
122 {
123 if(outLine1[i] == '\r' || outLine1[i] == '\n')
124 {
125 outLine1[i] = '\0';
126 break;
127 }
128 }
129
130 //don't display the whole path
131 int lastSlashPos=0;
132
133 for(i = 0; i < 1035; ++i)
134 {
135 if(outLine2[i] == '\0')
136 break;
137 if(outLine2[i] == '/')
138 lastSlashPos = i+1;
139 }
140 sprintf (
141 buf, "[%i] %p in %s at %s",
142 lineNb, addr, outLine1,
143 outLine2+lastSlashPos);
144 }
145 else
146 {
147 pclose(fp);
148 return 1;
149 }
150 }
151 else
152 {
153 pclose(fp);
154 return 1;
155 }
156 }
157
158 /* close */
159 pclose(fp);
160
161 return 0;
162 }
163 #endif
164
165 #ifdef CAN_USE_LIBBACKTRACE
166 static struct backtrace_state * state = NULL;
167
168 /**
169 * Note: to get the line number for external libs
170 * use `addr2line -e /lib64/libc.so.6 0x27a50`.
171 *
172 * __libc_start_main in ../csu/libc-start.c:332 from /lib64/libc.so.6(+0x27a50)[0x7feffe060000].
173 */
174 static void
syminfo_cb(void * data,uintptr_t pc,const char * symname,uintptr_t symval,uintptr_t symsize,const char * binary_filename,uintptr_t base_address)175 syminfo_cb (
176 void * data,
177 uintptr_t pc,
178 const char * symname,
179 uintptr_t symval,
180 uintptr_t symsize
181 #ifdef BACKTRACE_HAVE_BIN_FILENAME_AND_BASE_ADDR
182 ,
183 const char * binary_filename,
184 uintptr_t base_address
185 #endif
186 )
187 {
188 GString * msg_str = (GString *) data;
189
190 if (!symname)
191 {
192 symname = "unknown";
193 }
194
195 #ifdef BACKTRACE_HAVE_BIN_FILENAME_AND_BASE_ADDR
196 if (base_address != 0 || symval != 0)
197 {
198 g_string_append_printf (
199 msg_str,
200 " %s from %s(+0x%lx)[0x%lx]",
201 symname, binary_filename,
202 symval - base_address, base_address);
203 }
204 else
205 #endif
206 {
207 g_string_append_printf (
208 msg_str,
209 " %s", symname);
210 }
211 }
212
213 static int
full_cb(void * data,uintptr_t pc,const char * filename,int lineno,const char * function)214 full_cb (
215 void * data,
216 uintptr_t pc,
217 const char * filename,
218 int lineno,
219 const char * function)
220 {
221 GString ** msg_str = (GString **) data;
222
223 if (!(*msg_str))
224 {
225 return -1;
226 }
227
228 if (filename)
229 {
230 g_string_append_printf (
231 *msg_str, "%s", filename);
232 }
233 else
234 {
235 g_string_append_printf (
236 *msg_str, "%s", "???");
237 }
238
239 if (function)
240 {
241 g_string_append_printf (
242 *msg_str, " (%s:%d)",
243 function, lineno);
244 }
245 else
246 {
247 backtrace_syminfo (
248 state, pc, syminfo_cb, NULL, *msg_str);
249 }
250
251 /*g_string_append_printf (*/
252 /*msg_str, " [%lu]\n", pc);*/
253 g_string_append_printf (
254 *msg_str, "%s", "\n");
255
256 return 0;
257 }
258
259 static void
bt_error_cb(void * data,const char * msg,int errnum)260 bt_error_cb (
261 void * data,
262 const char * msg,
263 int errnum)
264 {
265 GString ** msg_str = (GString **) data;
266 g_message ("backtrace error: %s", msg);
267
268 if (*msg_str)
269 {
270 g_string_free (*msg_str, true);
271 *msg_str = NULL;
272 }
273 }
274
275 #endif
276
277 /**
278 * Returns the backtrace with \ref max_lines
279 * number of lines and a string prefix.
280 *
281 * @param exe_path Executable path for running
282 * addr2line.
283 * @param with_lines Whether to show line numbers.
284 * This is very slow.
285 */
286 char *
_backtrace_get(const char * exe_path,const char * prefix,int max_lines,bool with_lines,bool write_to_file)287 _backtrace_get (
288 const char * exe_path,
289 const char * prefix,
290 int max_lines,
291 bool with_lines,
292 bool write_to_file)
293 {
294 char message[12000];
295 char current_line[2000];
296 (void) current_line;
297
298 strcpy (message, prefix);
299
300 #ifdef CAN_USE_LIBBACKTRACE
301 GString * msg_str = g_string_new (prefix);
302
303 state =
304 backtrace_create_state (
305 exe_path, true, bt_error_cb, &msg_str);
306
307 if (state && msg_str)
308 {
309
310 if (write_to_file && ZRYTHM)
311 {
312 char * str_datetime =
313 datetime_get_for_filename ();
314 char * user_bt_dir =
315 zrythm_get_dir (ZRYTHM_DIR_USER_BACKTRACE);
316 char * backtrace_filepath =
317 g_strdup_printf (
318 "%s%sbacktrace_%s.txt",
319 user_bt_dir, G_DIR_SEPARATOR_S,
320 str_datetime);
321 io_mkdir (user_bt_dir);
322 FILE * f = fopen (backtrace_filepath, "a");
323 if (!f)
324 {
325 g_message (
326 "failed to open file %s",
327 backtrace_filepath);
328 g_free (str_datetime);
329 g_free (user_bt_dir);
330 g_free (backtrace_filepath);
331 goto call_backtrace_full;
332 }
333 backtrace_print (state, 0, f);
334 fclose (f);
335 g_free (str_datetime);
336 g_free (user_bt_dir);
337 g_free (backtrace_filepath);
338 }
339
340 call_backtrace_full:
341 if (msg_str)
342 {
343 g_message ("getting bt");
344 int ret =
345 backtrace_full (
346 state, 0, full_cb, bt_error_cb, &msg_str);
347 g_message ("ret %d", ret);
348
349 if (msg_str)
350 {
351 /* replace multiple instances of ??? with a single
352 * one */
353 char * bt_str = g_string_free (msg_str, false);
354 string_replace_regex (
355 &bt_str, "(\\?\\?\\?\n)+\\1", "??? ...\n");
356
357 return bt_str;
358 }
359 else
360 {
361 goto read_traditional_bt;
362 }
363 }
364 else
365 {
366 goto read_traditional_bt;
367 }
368 }
369 else
370 {
371 read_traditional_bt:
372 g_message (
373 "failed to get a backtrace state, creating traditional "
374 "backtrace...");
375 }
376 #endif
377
378 #if defined (_WOE32)
379 unsigned int i;
380 void * stack[ 100 ];
381 unsigned short frames;
382 SYMBOL_INFO * symbol;
383 HANDLE process;
384
385 process = GetCurrentProcess();
386
387 SymInitialize (process, NULL, TRUE);
388
389 frames =
390 CaptureStackBackTrace (0, 100, stack, NULL);
391 symbol =
392 (SYMBOL_INFO *)
393 calloc (
394 sizeof (SYMBOL_INFO) + 256 * sizeof( char ),
395 1);
396 symbol->MaxNameLen = 255;
397 symbol->SizeOfStruct = sizeof (SYMBOL_INFO);
398
399 IMAGEHLP_LINE64 * line =
400 calloc (
401 sizeof (IMAGEHLP_LINE64) +
402 256 * sizeof( char ),
403 1);
404 line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
405
406 for (i = 0; i < frames; i++)
407 {
408 SymFromAddr (
409 process, (DWORD64) (stack[i]), 0,
410 symbol);
411
412 bool got_line = false;
413 if (with_lines)
414 {
415 got_line =
416 SymGetLineFromAddr64 (
417 process, (DWORD64) (stack[i]), 0,
418 line);
419
420 if (!got_line)
421 {
422 g_message (
423 "failed to get line info for %s",
424 symbol->Name);
425 }
426 }
427
428 if (got_line)
429 {
430 sprintf (
431 current_line,
432 "%u: %s - 0x%0X %s:%d\n",
433 frames - i - 1,
434 symbol->Name, symbol->Address,
435 line->FileName, line->LineNumber);
436 }
437 else
438 {
439 sprintf (
440 current_line,
441 "%u: %s - 0x%0X\n", frames - i - 1,
442 symbol->Name, symbol->Address);
443 }
444
445 strcat (message, current_line);
446 }
447 free (symbol);
448 free (line);
449 #else
450 void * array[max_lines];
451 char ** strings;
452
453 /* get void*'s for all entries on the stack */
454 int size = backtrace (array, max_lines);
455
456 /* print out all the frames to stderr */
457 strings = backtrace_symbols (array, size);
458 for (int i = 0; i < size - 2; i++)
459 {
460 /* if addr2line failed, print what we can */
461 if (!with_lines ||
462 addr2line (
463 exe_path, array[i],
464 current_line,
465 size - 2 - i - 1) != 0)
466 {
467 sprintf (
468 current_line, "%s\n", strings[i]);
469 }
470 strcat (message, current_line);
471 }
472 free (strings);
473 #endif
474
475 return g_strdup (message);
476 }
477