/src/gstreamer/subprojects/glib-2.86.3/glib/gbacktrace.c
Line | Count | Source |
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 | 0 | #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 | | g_on_error_query (const gchar *prg_name) |
148 | 0 | { |
149 | 0 | #ifndef G_OS_WIN32 |
150 | 0 | static const gchar * const query1 = "[E]xit, [H]alt"; |
151 | 0 | static const gchar * const query2 = ", show [S]tack trace"; |
152 | 0 | static const gchar * const query3 = " or [P]roceed"; |
153 | 0 | 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 | 0 | "(process:%u): %s%s%s: ", |
162 | 0 | (guint) getpid (), |
163 | 0 | query1, |
164 | 0 | query2, |
165 | 0 | query3); |
166 | 0 | fflush (stdout); |
167 | |
|
168 | 0 | if (isatty(0) && isatty(1)) |
169 | 0 | { |
170 | 0 | if (fgets (buf, 8, stdin) == NULL) |
171 | 0 | _exit (0); |
172 | 0 | } |
173 | 0 | else |
174 | 0 | { |
175 | 0 | strcpy (buf, "E\n"); |
176 | 0 | } |
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 | 0 | { |
187 | 0 | g_on_error_stack_trace (prg_name); |
188 | 0 | goto retry; |
189 | 0 | } |
190 | 0 | else if ((buf[0] == 'H' || buf[0] == 'h') |
191 | 0 | && buf[1] == '\n') |
192 | 0 | { |
193 | 0 | while (glib_on_error_halt) |
194 | 0 | ; |
195 | 0 | glib_on_error_halt = TRUE; |
196 | 0 | return; |
197 | 0 | } |
198 | 0 | 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 | 0 | } |
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 | | g_on_error_stack_trace (const gchar *prg_name) |
250 | 0 | { |
251 | 0 | #if defined(G_OS_UNIX) |
252 | 0 | pid_t pid; |
253 | 0 | gchar buf[16]; |
254 | 0 | gchar buf2[64]; |
255 | 0 | const gchar *args[5] = { DEBUGGER, NULL, NULL, NULL, NULL }; |
256 | 0 | int status; |
257 | |
|
258 | 0 | if (!prg_name) |
259 | 0 | { |
260 | 0 | _g_snprintf (buf2, sizeof (buf2), "/proc/%u/exe", (guint) getpid ()); |
261 | 0 | prg_name = buf2; |
262 | 0 | } |
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 | 0 | #endif |
274 | |
|
275 | 0 | pid = fork (); |
276 | 0 | if (pid == 0) |
277 | 0 | { |
278 | 0 | stack_trace (args); |
279 | 0 | _exit (0); |
280 | 0 | } |
281 | 0 | else if (pid == (pid_t) -1) |
282 | 0 | { |
283 | 0 | perror ("unable to fork " DEBUGGER); |
284 | 0 | return; |
285 | 0 | } |
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 | 0 | while (1) |
291 | 0 | { |
292 | 0 | pid_t retval = waitpid (pid, &status, 0); |
293 | 0 | if (retval == -1) |
294 | 0 | { |
295 | 0 | if (errno == EAGAIN || errno == EINTR) |
296 | 0 | continue; |
297 | 0 | break; |
298 | 0 | } |
299 | 0 | else if (WIFEXITED (status) || WIFSIGNALED (status)) |
300 | 0 | break; |
301 | 0 | } |
302 | | #else |
303 | | if (IsDebuggerPresent ()) |
304 | | G_BREAKPOINT (); |
305 | | else |
306 | | g_abort (); |
307 | | #endif |
308 | 0 | } |
309 | | |
310 | | #ifndef G_OS_WIN32 |
311 | | |
312 | | static gboolean stack_trace_done = FALSE; |
313 | | |
314 | | static void |
315 | | stack_trace_sigchld (int signum) |
316 | 0 | { |
317 | 0 | stack_trace_done = TRUE; |
318 | 0 | } |
319 | | |
320 | 0 | #define BUFSIZE 1024 |
321 | | |
322 | | static inline const char * |
323 | | get_strerror (char *buffer, gsize n) |
324 | 0 | { |
325 | 0 | #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 | 0 | } |
341 | | |
342 | | static gssize |
343 | | checked_write (int fd, gconstpointer buf, gsize n) |
344 | 0 | { |
345 | 0 | gssize written = write (fd, buf, n); |
346 | |
|
347 | 0 | if (written == -1) |
348 | 0 | { |
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 | 0 | } |
357 | | |
358 | 0 | return written; |
359 | 0 | } |
360 | | |
361 | | static int |
362 | | checked_dup (int fd) |
363 | 0 | { |
364 | 0 | int new_fd = dup (fd); |
365 | |
|
366 | 0 | if (new_fd == -1) |
367 | 0 | { |
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 | 0 | } |
376 | | |
377 | 0 | return new_fd; |
378 | 0 | } |
379 | | |
380 | | static void |
381 | | stack_trace (const char * const *args) |
382 | 0 | { |
383 | 0 | pid_t pid; |
384 | 0 | int in_fd[2]; |
385 | 0 | int out_fd[2]; |
386 | 0 | fd_set fdset; |
387 | 0 | fd_set readset; |
388 | 0 | struct timeval tv; |
389 | 0 | int sel, idx, state; |
390 | | #ifdef USE_LLDB |
391 | | int line_idx; |
392 | | #endif |
393 | 0 | char buffer[BUFSIZE]; |
394 | 0 | 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 | 0 | { |
402 | 0 | perror ("unable to open pipe"); |
403 | 0 | _exit (0); |
404 | 0 | } |
405 | | |
406 | 0 | pid = fork (); |
407 | 0 | if (pid == 0) |
408 | 0 | { |
409 | | /* Save stderr for printing failure below */ |
410 | 0 | int old_err = dup (2); |
411 | 0 | if (old_err != -1) |
412 | 0 | { |
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 | 0 | } |
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 | 0 | { |
430 | 0 | close (2); |
431 | | /* We can ignore the return value here as we're failing anyways */ |
432 | 0 | (void) !dup (old_err); |
433 | 0 | } |
434 | 0 | perror ("exec " DEBUGGER " failed"); |
435 | 0 | _exit (0); |
436 | 0 | } |
437 | 0 | else if (pid == (pid_t) -1) |
438 | 0 | { |
439 | 0 | perror ("unable to fork"); |
440 | 0 | _exit (0); |
441 | 0 | } |
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 | 0 | #endif |
460 | |
|
461 | 0 | idx = 0; |
462 | | #ifdef USE_LLDB |
463 | | line_idx = 0; |
464 | | #endif |
465 | 0 | state = 0; |
466 | |
|
467 | 0 | while (1) |
468 | 0 | { |
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 | 0 | { |
479 | 0 | if (read (out_fd[0], &c, 1)) |
480 | 0 | { |
481 | | #ifdef USE_LLDB |
482 | | line_idx += 1; |
483 | | #endif |
484 | |
|
485 | 0 | switch (state) |
486 | 0 | { |
487 | 0 | case 0: |
488 | | #ifdef USE_LLDB |
489 | | if (c == '*' || (c == ' ' && line_idx == 1)) |
490 | | #else |
491 | 0 | if (c == '#') |
492 | 0 | #endif |
493 | 0 | { |
494 | 0 | state = 1; |
495 | 0 | idx = 0; |
496 | 0 | buffer[idx++] = c; |
497 | 0 | } |
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 | 0 | { |
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 | 0 | } |
512 | 0 | break; |
513 | 0 | default: |
514 | 0 | break; |
515 | 0 | } |
516 | 0 | } |
517 | 0 | } |
518 | 0 | else if (stack_trace_done) |
519 | 0 | break; |
520 | 0 | } |
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 | 0 | } |
528 | | |
529 | | #endif /* !G_OS_WIN32 */ |