Change LGPL-2.1+ to LGPL-2.1-or-later
[platform/upstream/glib.git] / glib / gbacktrace.c
1 /* GLIB - Library of useful routines for C programming
2  * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library 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 GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /*
21  * Modified by the GLib Team and others 1997-2000.  See the AUTHORS
22  * file for a list of people on the GLib Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GLib at ftp://ftp.gtk.org/pub/gtk/.
25  */
26
27 /*
28  * MT safe ; except for g_on_error_stack_trace, but who wants thread safety
29  * then
30  */
31
32 #include "config.h"
33 #include "glibconfig.h"
34
35 #include <signal.h>
36 #include <stdarg.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39
40 #ifdef HAVE_SYS_TIME_H
41 #include <sys/time.h>
42 #endif
43 #include <sys/types.h>
44
45 #include <time.h>
46
47 #ifdef G_OS_UNIX
48 #include "glib-unixprivate.h"
49 #include <errno.h>
50 #include <unistd.h>
51 #include <sys/wait.h>
52 #ifdef HAVE_SYS_SELECT_H
53 #include <sys/select.h>
54 #endif /* HAVE_SYS_SELECT_H */
55 #endif
56
57 #include <string.h>
58
59 #ifdef G_OS_WIN32
60 #  define STRICT                /* Strict typing, please */
61 #  define _WIN32_WINDOWS 0x0401 /* to get IsDebuggerPresent */
62 #  include <windows.h>
63 #  undef STRICT
64 #else
65 #  include <fcntl.h>
66 #endif
67
68 #include "gbacktrace.h"
69
70 #include "gtypes.h"
71 #include "gmain.h"
72 #include "gprintfint.h"
73 #include "gunicode.h"
74 #include "gutils.h"
75
76 #ifndef G_OS_WIN32
77 static void stack_trace (const char * const *args);
78 #endif
79
80 /* Default to using LLDB for backtraces on macOS. */
81 #ifdef __APPLE__
82 #define USE_LLDB
83 #endif
84
85 #ifdef USE_LLDB
86 #define DEBUGGER "lldb"
87 #else
88 #define DEBUGGER "gdb"
89 #endif
90
91 /* People want to hit this from their debugger... */
92 GLIB_AVAILABLE_IN_ALL volatile gboolean glib_on_error_halt;
93 volatile gboolean glib_on_error_halt = TRUE;
94
95 /**
96  * g_on_error_query:
97  * @prg_name: the program name, needed by gdb for the "[S]tack trace"
98  *     option. If @prg_name is %NULL, g_get_prgname() is called to get
99  *     the program name (which will work correctly if gdk_init() or
100  *     gtk_init() has been called)
101  *
102  * Prompts the user with
103  * `[E]xit, [H]alt, show [S]tack trace or [P]roceed`.
104  * This function is intended to be used for debugging use only.
105  * The following example shows how it can be used together with
106  * the g_log() functions.
107  *
108  * |[<!-- language="C" -->
109  * #include <glib.h>
110  *
111  * static void
112  * log_handler (const gchar   *log_domain,
113  *              GLogLevelFlags log_level,
114  *              const gchar   *message,
115  *              gpointer       user_data)
116  * {
117  *   g_log_default_handler (log_domain, log_level, message, user_data);
118  *
119  *   g_on_error_query (MY_PROGRAM_NAME);
120  * }
121  *
122  * int
123  * main (int argc, char *argv[])
124  * {
125  *   g_log_set_handler (MY_LOG_DOMAIN,
126  *                      G_LOG_LEVEL_WARNING |
127  *                      G_LOG_LEVEL_ERROR |
128  *                      G_LOG_LEVEL_CRITICAL,
129  *                      log_handler,
130  *                      NULL);
131  *   ...
132  * ]|
133  *
134  * If "[E]xit" is selected, the application terminates with a call
135  * to _exit(0).
136  *
137  * If "[S]tack" trace is selected, g_on_error_stack_trace() is called.
138  * This invokes gdb, which attaches to the current process and shows
139  * a stack trace. The prompt is then shown again.
140  *
141  * If "[P]roceed" is selected, the function returns.
142  *
143  * This function may cause different actions on non-UNIX platforms.
144  *
145  * On Windows consider using the `G_DEBUGGER` environment
146  * variable (see [Running GLib Applications](glib-running.html)) and
147  * calling g_on_error_stack_trace() instead.
148  */
149 void
150 g_on_error_query (const gchar *prg_name)
151 {
152 #ifndef G_OS_WIN32
153   static const gchar * const query1 = "[E]xit, [H]alt";
154   static const gchar * const query2 = ", show [S]tack trace";
155   static const gchar * const query3 = " or [P]roceed";
156   gchar buf[16];
157
158   if (!prg_name)
159     prg_name = g_get_prgname ();
160
161  retry:
162
163   if (prg_name)
164     _g_fprintf (stdout,
165                 "%s (pid:%u): %s%s%s: ",
166                 prg_name,
167                 (guint) getpid (),
168                 query1,
169                 query2,
170                 query3);
171   else
172     _g_fprintf (stdout,
173                 "(process:%u): %s%s: ",
174                 (guint) getpid (),
175                 query1,
176                 query3);
177   fflush (stdout);
178
179   if (isatty(0) && isatty(1))
180     {
181       if (fgets (buf, 8, stdin) == NULL)
182         _exit (0);
183     }
184   else
185     {
186       strcpy (buf, "E\n");
187     }
188
189   if ((buf[0] == 'E' || buf[0] == 'e')
190       && buf[1] == '\n')
191     _exit (0);
192   else if ((buf[0] == 'P' || buf[0] == 'p')
193            && buf[1] == '\n')
194     return;
195   else if (prg_name
196            && (buf[0] == 'S' || buf[0] == 's')
197            && buf[1] == '\n')
198     {
199       g_on_error_stack_trace (prg_name);
200       goto retry;
201     }
202   else if ((buf[0] == 'H' || buf[0] == 'h')
203            && buf[1] == '\n')
204     {
205       while (glib_on_error_halt)
206         ;
207       glib_on_error_halt = TRUE;
208       return;
209     }
210   else
211     goto retry;
212 #else
213   if (!prg_name)
214     prg_name = g_get_prgname ();
215
216   /* MessageBox is allowed on UWP apps only when building against
217    * the debug CRT, which will set -D_DEBUG */
218 #if defined(_DEBUG) || !defined(G_WINAPI_ONLY_APP)
219   {
220     WCHAR *caption = NULL;
221
222     if (prg_name && *prg_name)
223       {
224         caption = g_utf8_to_utf16 (prg_name, -1, NULL, NULL, NULL);
225       }
226
227     MessageBoxW (NULL, L"g_on_error_query called, program terminating",
228                  caption,
229                  MB_OK|MB_ICONERROR);
230
231     g_free (caption);
232   }
233 #else
234   printf ("g_on_error_query called, program '%s' terminating\n",
235       (prg_name && *prg_name) ? prg_name : "(null)");
236 #endif
237   _exit(0);
238 #endif
239 }
240
241 /**
242  * g_on_error_stack_trace:
243  * @prg_name: the program name, needed by gdb for the "[S]tack trace"
244  *     option
245  *
246  * Invokes gdb, which attaches to the current process and shows a
247  * stack trace. Called by g_on_error_query() when the "[S]tack trace"
248  * option is selected. You can get the current process's program name
249  * with g_get_prgname(), assuming that you have called gtk_init() or
250  * gdk_init().
251  *
252  * This function may cause different actions on non-UNIX platforms.
253  *
254  * When running on Windows, this function is *not* called by
255  * g_on_error_query(). If called directly, it will raise an
256  * exception, which will crash the program. If the `G_DEBUGGER` environment
257  * variable is set, a debugger will be invoked to attach and
258  * handle that exception (see [Running GLib Applications](glib-running.html)).
259  */
260 void
261 g_on_error_stack_trace (const gchar *prg_name)
262 {
263 #if defined(G_OS_UNIX)
264   pid_t pid;
265   gchar buf[16];
266   const gchar *args[5] = { DEBUGGER, NULL, NULL, NULL, NULL };
267   int status;
268
269   if (!prg_name)
270     return;
271
272   _g_sprintf (buf, "%u", (guint) getpid ());
273
274 #ifdef USE_LLDB
275   args[1] = prg_name;
276   args[2] = "-p";
277   args[3] = buf;
278 #else
279   args[1] = prg_name;
280   args[2] = buf;
281 #endif
282
283   pid = fork ();
284   if (pid == 0)
285     {
286       stack_trace (args);
287       _exit (0);
288     }
289   else if (pid == (pid_t) -1)
290     {
291       perror ("unable to fork " DEBUGGER);
292       return;
293     }
294
295   /* Wait until the child really terminates. On Mac OS X waitpid ()
296    * will also return when the child is being stopped due to tracing.
297    */
298   while (1)
299     {
300       pid_t retval = waitpid (pid, &status, 0);
301       if (WIFEXITED (retval) || WIFSIGNALED (retval))
302         break;
303     }
304 #else
305   if (IsDebuggerPresent ())
306     G_BREAKPOINT ();
307   else
308     g_abort ();
309 #endif
310 }
311
312 #ifndef G_OS_WIN32
313
314 static gboolean stack_trace_done = FALSE;
315
316 static void
317 stack_trace_sigchld (int signum)
318 {
319   stack_trace_done = TRUE;
320 }
321
322 #define BUFSIZE 1024
323
324 static inline const char *
325 get_strerror (char *buffer, gsize n)
326 {
327 #if defined(STRERROR_R_CHAR_P)
328   return strerror_r (errno, buffer, n);
329 #elif defined(HAVE_STRERROR_R)
330   int ret = strerror_r (errno, buffer, n);
331   if (ret == 0 || ret == EINVAL)
332     return buffer;
333   return NULL;
334 #else
335   const char *error_str = strerror (errno);
336   if (!error_str)
337     return NULL;
338
339   strncpy (buffer, error_str, n);
340   return buffer;
341 #endif
342 }
343
344 static gssize
345 checked_write (int fd, gconstpointer buf, gsize n)
346 {
347   gssize written = write (fd, buf, n);
348
349   if (written == -1)
350     {
351       char msg[BUFSIZE] = {0};
352       char error_str[BUFSIZE / 2] = {0};
353
354       get_strerror (error_str, sizeof (error_str) - 1);
355       snprintf (msg, sizeof (msg) - 1, "Unable to write to fd %d: %s", fd, error_str);
356       perror (msg);
357       _exit (0);
358     }
359
360   return written;
361 }
362
363 static int
364 checked_dup (int fd)
365 {
366   int new_fd = dup (fd);
367
368   if (new_fd == -1)
369     {
370       char msg[BUFSIZE] = {0};
371       char error_str[BUFSIZE / 2] = {0};
372
373       get_strerror (error_str, sizeof (error_str) - 1);
374       snprintf (msg, sizeof (msg) - 1, "Unable to duplicate fd %d: %s", fd, error_str);
375       perror (msg);
376       _exit (0);
377     }
378
379   return new_fd;
380 }
381
382 static void
383 stack_trace (const char * const *args)
384 {
385   pid_t pid;
386   int in_fd[2];
387   int out_fd[2];
388   fd_set fdset;
389   fd_set readset;
390   struct timeval tv;
391   int sel, idx, state;
392 #ifdef USE_LLDB
393   int line_idx;
394 #endif
395   char buffer[BUFSIZE];
396   char c;
397
398   stack_trace_done = FALSE;
399   signal (SIGCHLD, stack_trace_sigchld);
400
401   if (!g_unix_open_pipe_internal (in_fd, TRUE, FALSE) ||
402       !g_unix_open_pipe_internal (out_fd, TRUE, FALSE))
403     {
404       perror ("unable to open pipe");
405       _exit (0);
406     }
407
408   pid = fork ();
409   if (pid == 0)
410     {
411       /* Save stderr for printing failure below */
412       int old_err = dup (2);
413       if (old_err != -1)
414         {
415           int getfd = fcntl (old_err, F_GETFD);
416           if (getfd != -1)
417             (void) fcntl (old_err, F_SETFD, getfd | FD_CLOEXEC);
418         }
419
420       close (0);
421       checked_dup (in_fd[0]);   /* set the stdin to the in pipe */
422       close (1);
423       checked_dup (out_fd[1]);  /* set the stdout to the out pipe */
424       close (2);
425       checked_dup (out_fd[1]);  /* set the stderr to the out pipe */
426
427       execvp (args[0], (char **) args);      /* exec gdb */
428
429       /* Print failure to original stderr */
430       if (old_err != -1)
431         {
432           close (2);
433           /* We can ignore the return value here as we're failing anyways */
434           (void) !dup (old_err);
435         }
436       perror ("exec " DEBUGGER " failed");
437       _exit (0);
438     }
439   else if (pid == (pid_t) -1)
440     {
441       perror ("unable to fork");
442       _exit (0);
443     }
444
445   FD_ZERO (&fdset);
446   FD_SET (out_fd[0], &fdset);
447
448 #ifdef USE_LLDB
449   checked_write (in_fd[1], "bt\n", 3);
450   checked_write (in_fd[1], "p x = 0\n", 8);
451   checked_write (in_fd[1], "process detach\n", 15);
452   checked_write (in_fd[1], "quit\n", 5);
453 #else
454   /* Don't wrap so that lines are not truncated */
455   checked_write (in_fd[1], "set width unlimited\n", 20);
456   checked_write (in_fd[1], "backtrace\n", 10);
457   checked_write (in_fd[1], "p x = 0\n", 8);
458   checked_write (in_fd[1], "quit\n", 5);
459 #endif
460
461   idx = 0;
462 #ifdef USE_LLDB
463   line_idx = 0;
464 #endif
465   state = 0;
466
467   while (1)
468     {
469       readset = fdset;
470       tv.tv_sec = 1;
471       tv.tv_usec = 0;
472
473       sel = select (FD_SETSIZE, &readset, NULL, NULL, &tv);
474       if (sel == -1)
475         break;
476
477       if ((sel > 0) && (FD_ISSET (out_fd[0], &readset)))
478         {
479           if (read (out_fd[0], &c, 1))
480             {
481 #ifdef USE_LLDB
482               line_idx += 1;
483 #endif
484
485               switch (state)
486                 {
487                 case 0:
488 #ifdef USE_LLDB
489                   if (c == '*' || (c == ' ' && line_idx == 1))
490 #else
491                   if (c == '#')
492 #endif
493                     {
494                       state = 1;
495                       idx = 0;
496                       buffer[idx++] = c;
497                     }
498                   break;
499                 case 1:
500                   if (idx < BUFSIZE)
501                     buffer[idx++] = c;
502                   if ((c == '\n') || (c == '\r'))
503                     {
504                       buffer[idx] = 0;
505                       _g_fprintf (stdout, "%s", buffer);
506                       state = 0;
507                       idx = 0;
508 #ifdef USE_LLDB
509                       line_idx = 0;
510 #endif
511                     }
512                   break;
513                 default:
514                   break;
515                 }
516             }
517         }
518       else if (stack_trace_done)
519         break;
520     }
521
522   close (in_fd[0]);
523   close (in_fd[1]);
524   close (out_fd[0]);
525   close (out_fd[1]);
526   _exit (0);
527 }
528
529 #endif /* !G_OS_WIN32 */