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