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