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