Coverage Report

Created: 2025-12-28 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 */