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 */