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 |
73 | static 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... */ |
88 | GLIB_AVAILABLE_IN_ALL volatile gboolean glib_on_error_halt; |
89 | volatile 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 | */ |
145 | void |
146 | g_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 | */ |
251 | void |
252 | g_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 | |
305 | static gboolean stack_trace_done = FALSE; |
306 | |
307 | static void |
308 | stack_trace_sigchld (int signum) |
309 | { |
310 | stack_trace_done = TRUE; |
311 | } |
312 | |
313 | #define BUFSIZE 1024 |
314 | |
315 | static void |
316 | stack_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 | |