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

source code of gtk/subprojects/glib/glib/gbacktrace.c