/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "config.h"
#define _GNU_SOURCE /* need the POSIX signal API */
#include
#include
#ifdef HAVE_UNISTD_H
#include
#endif
#include
#ifdef HAVE_EXECINFO_H
/* Allowing backtrace() API. */
#include
#endif
#include
#include
#include
#include "libgimpbase/gimpbase.h"
#include "core/core-types.h"
#include "core/gimp.h"
#include "errors.h"
#ifdef G_OS_WIN32
#include
#endif
#define MAX_TRACES 3
/* private variables */
static Gimp *the_errors_gimp = NULL;
static gboolean use_debug_handler = FALSE;
static GimpStackTraceMode stack_trace_mode = GIMP_STACK_TRACE_QUERY;
static gchar *full_prog_name = NULL;
static gchar *backtrace_file = NULL;
/* local function prototypes */
static void gimp_third_party_message_log_func (const gchar *log_domain,
GLogLevelFlags flags,
const gchar *message,
gpointer data);
static void gimp_message_log_func (const gchar *log_domain,
GLogLevelFlags flags,
const gchar *message,
gpointer data);
static void gimp_error_log_func (const gchar *domain,
GLogLevelFlags flags,
const gchar *message,
gpointer data) G_GNUC_NORETURN;
static G_GNUC_NORETURN void gimp_eek (const gchar *reason,
const gchar *message,
gboolean use_handler);
static gboolean gimp_print_stack_trace (FILE *file,
gchar **trace);
static void gimp_on_error_query (void);
/* public functions */
void
errors_init (Gimp *gimp,
const gchar *_full_prog_name,
gboolean _use_debug_handler,
GimpStackTraceMode _stack_trace_mode,
const gchar *_backtrace_file)
{
const gchar * const log_domains[] =
{
"Gimp",
"Gimp-Actions",
"Gimp-Base",
"Gimp-Composite",
"Gimp-Config",
"Gimp-Core",
"Gimp-Dialogs",
"Gimp-Display",
"Gimp-File",
"Gimp-GEGL",
"Gimp-GUI",
"Gimp-Menus",
"Gimp-Operations",
"Gimp-PDB",
"Gimp-Paint",
"Gimp-Paint-Funcs",
"Gimp-Plug-In",
"Gimp-Text",
"Gimp-Tools",
"Gimp-Vectors",
"Gimp-Widgets",
"Gimp-XCF",
"LibGimpBase",
"LibGimpColor",
"LibGimpConfig",
"LibGimpMath",
"LibGimpModule",
"LibGimpThumb",
"LibGimpWidgets"
};
gint i;
g_return_if_fail (GIMP_IS_GIMP (gimp));
g_return_if_fail (_full_prog_name != NULL);
g_return_if_fail (full_prog_name == NULL);
#ifdef GIMP_UNSTABLE
g_printerr ("This is a development version of GIMP. "
"Debug messages may appear here.\n\n");
#endif /* GIMP_UNSTABLE */
the_errors_gimp = gimp;
use_debug_handler = _use_debug_handler ? TRUE : FALSE;
stack_trace_mode = _stack_trace_mode;
full_prog_name = g_strdup (_full_prog_name);
backtrace_file = g_strdup (_backtrace_file);
for (i = 0; i < G_N_ELEMENTS (log_domains); i++)
g_log_set_handler (log_domains[i],
#ifdef GIMP_UNSTABLE
G_LOG_LEVEL_WARNING |
#endif
G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_CRITICAL,
gimp_message_log_func, gimp);
g_log_set_handler ("GEGL",
G_LOG_LEVEL_MESSAGE,
gimp_third_party_message_log_func, gimp);
g_log_set_handler (NULL,
G_LOG_LEVEL_ERROR | G_LOG_FLAG_FATAL,
gimp_error_log_func, gimp);
}
void
errors_exit (void)
{
the_errors_gimp = NULL;
if (backtrace_file)
g_free (backtrace_file);
}
void
gimp_fatal_error (const gchar *message)
{
gimp_eek ("fatal error", message, TRUE);
}
void
gimp_terminate (const gchar *message)
{
gimp_eek ("terminated", message, use_debug_handler);
}
/* private functions */
static void
gimp_third_party_message_log_func (const gchar *log_domain,
GLogLevelFlags flags,
const gchar *message,
gpointer data)
{
Gimp *gimp = data;
if (gimp)
{
/* Whereas all GIMP messages are processed under the same domain,
* we need to keep the log domain information for third party
* messages.
*/
gimp_show_message (gimp, NULL, GIMP_MESSAGE_WARNING,
log_domain, message, NULL);
}
else
{
g_printerr ("%s: %s\n\n", log_domain, message);
}
}
static void
gimp_message_log_func (const gchar *log_domain,
GLogLevelFlags flags,
const gchar *message,
gpointer data)
{
static gint n_traces;
GimpMessageSeverity severity = GIMP_MESSAGE_WARNING;
Gimp *gimp = data;
gchar *trace = NULL;
GimpCoreConfig *config = gimp->config;
gboolean generate_backtrace = FALSE;
g_object_get (G_OBJECT (config),
"generate-backtrace", &generate_backtrace,
NULL);
if (generate_backtrace &&
(flags & (G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING)))
{
severity = (flags & G_LOG_LEVEL_CRITICAL) ?
GIMP_MESSAGE_ERROR : GIMP_MESSAGE_WARNING;
if (n_traces < MAX_TRACES)
{
/* Getting debug traces is time-expensive, and worse, some
* critical errors have the bad habit to create more errors
* (the first ones are therefore usually the most useful).
* This is why we keep track of how many times we made traces
* and stop doing them after a while.
* Hence when this happens, critical errors are simply processed as
* lower level errors.
*/
gimp_print_stack_trace (NULL, &trace);
n_traces++;
}
}
if (gimp)
{
gimp_show_message (gimp, NULL, severity,
NULL, message, trace);
}
else
{
g_printerr ("%s: %s\n\n",
gimp_filename_to_utf8 (full_prog_name), message);
if (trace)
g_printerr ("Back trace:\n%s\n\n", trace);
}
if (trace)
g_free (trace);
}
static void
gimp_error_log_func (const gchar *domain,
GLogLevelFlags flags,
const gchar *message,
gpointer data)
{
gimp_fatal_error (message);
}
static void
gimp_eek (const gchar *reason,
const gchar *message,
gboolean use_handler)
{
GimpCoreConfig *config = the_errors_gimp->config;
gboolean generate_backtrace = FALSE;
gboolean eek_handled = FALSE;
/* GIMP has 2 ways to handle termination signals and fatal errors: one
* is the stack trace mode which is set at start as command line
* option --stack-trace-mode, this won't change for the length of the
* session and outputs a trace in terminal; the other is set in
* preferences, outputs a trace in a GUI and can change anytime during
* the session.
* The GUI backtrace has priority if it is set.
*/
g_object_get (G_OBJECT (config),
"generate-backtrace", &generate_backtrace,
NULL);
/* Let's just always output on stdout at least so that there is a
* trace if the rest fails. */
g_printerr ("%s: %s: %s\n", gimp_filename_to_utf8 (full_prog_name),
reason, message);
#if ! defined (G_OS_WIN32) || defined (HAVE_EXCHNDL)
if (use_handler)
{
#ifndef GIMP_CONSOLE_COMPILATION
if (generate_backtrace && ! the_errors_gimp->no_interface)
{
FILE *fd;
gboolean has_backtrace = TRUE;
/* If GUI backtrace enabled (it is disabled by default), it
* takes precedence over the command line argument.
*/
#ifdef G_OS_WIN32
const gchar *gimpdebug = "gimp-debug-tool-" GIMP_TOOL_VERSION ".exe";
#elif defined (PLATFORM_OSX)
const gchar *gimpdebug = "gimp-debug-tool-" GIMP_TOOL_VERSION;
#else
const gchar *gimpdebug = LIBEXECDIR "/gimp-debug-tool-" GIMP_TOOL_VERSION;
#endif
gchar *args[7] = { (gchar *) gimpdebug, full_prog_name, NULL,
(gchar *) reason, (gchar *) message,
backtrace_file, NULL };
gchar pid[16];
g_snprintf (pid, 16, "%u", (guint) getpid ());
args[2] = pid;
#ifndef G_OS_WIN32
/* On Win32, the trace has already been processed by ExcHnl
* and is waiting for us in a text file.
*/
fd = g_fopen (backtrace_file, "w");
has_backtrace = gimp_print_stack_trace (fd, NULL);
fclose (fd);
#endif
/* We don't care about any return value. If it fails, too
* bad, we just won't have any stack trace.
* We still need to use the sync() variant because we have
* to keep GIMP up long enough for the debugger to get its
* trace.
*/
if (has_backtrace &&
g_file_test (backtrace_file, G_FILE_TEST_IS_REGULAR) &&
g_spawn_async (NULL, args, NULL,
G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL | G_SPAWN_STDOUT_TO_DEV_NULL,
NULL, NULL, NULL, NULL))
eek_handled = TRUE;
}
#endif /* !GIMP_CONSOLE_COMPILATION */
#ifndef G_OS_WIN32
if (! eek_handled)
{
switch (stack_trace_mode)
{
case GIMP_STACK_TRACE_NEVER:
break;
case GIMP_STACK_TRACE_QUERY:
{
sigset_t sigset;
sigemptyset (&sigset);
sigprocmask (SIG_SETMASK, &sigset, NULL);
gimp_on_error_query ();
}
break;
case GIMP_STACK_TRACE_ALWAYS:
{
sigset_t sigset;
sigemptyset (&sigset);
sigprocmask (SIG_SETMASK, &sigset, NULL);
gimp_print_stack_trace (stdout, NULL);
}
break;
default:
break;
}
}
#endif /* ! G_OS_WIN32 */
}
#endif /* ! G_OS_WIN32 || HAVE_EXCHNDL */
#if defined (G_OS_WIN32) && ! defined (GIMP_CONSOLE_COMPILATION)
/* g_on_error_* don't do anything reasonable on Win32. */
if (! eek_handled && ! the_errors_gimp->no_interface)
MessageBox (NULL, g_strdup_printf ("%s: %s", reason, message),
full_prog_name, MB_OK|MB_ICONERROR);
#endif
exit (EXIT_FAILURE);
}
/* In some error cases (e.g. segmentation fault), trying to allocate
* more memory will trigger more segmentation faults and therefore loop
* our error handling (which is just wrong). Therefore printing to a
* file description is an implementation without any memory allocation.
* On the other hand, if trace is not #NULL, a newly-allocated string
* will be returned.
*/
static gboolean
gimp_print_stack_trace (FILE *file,
gchar **trace)
{
gboolean stack_printed = FALSE;
/* This works only on UNIX systems. */
#ifndef G_OS_WIN32
GString *gtrace = NULL;
gchar gimp_pid[16];
pid_t pid;
gchar buffer[256];
ssize_t read_n;
int out_fd[2];
g_snprintf (gimp_pid, 16, "%u", (guint) getpid ());
if (pipe (out_fd) == -1)
{
return FALSE;
}
pid = fork ();
if (pid == 0)
{
/* Child process. */
gchar *args[7] = { "gdb", "-batch", "-ex", "backtrace full",
full_prog_name, NULL, NULL };
args[5] = gimp_pid;
/* Redirect the debugger output. */
dup2 (out_fd[1], STDOUT_FILENO);
close (out_fd[0]);
close (out_fd[1]);
/* Run GDB. */
if (execvp (args[0], args) == -1)
{
/* LLDB as alternative. */
gchar *args_lldb[11] = { "lldb", "--attach-pid", NULL, "--batch",
"--one-line", "bt",
"--one-line-on-crash", "bt",
"--one-line-on-crash", "quit", NULL };
args_lldb[2] = gimp_pid;
execvp (args_lldb[0], args_lldb);
}
_exit (0);
}
else if (pid > 0)
{
/* Main process */
int status;
waitpid (pid, &status, 0);
/* It is important to close the writing side of the pipe, otherwise
* the read() will wait forever without getting the information that
* writing is finished.
*/
close (out_fd[1]);
while ((read_n = read (out_fd[0], buffer, 256)) > 0)
{
/* It's hard to know if the debugger was found since it
* happened in the child. Let's just assume that any output
* means it succeeded.
*/
stack_printed = TRUE;
buffer[read_n] = '\0';
if (file)
g_fprintf (file, "%s", buffer);
if (trace)
{
if (! gtrace)
gtrace = g_string_new (NULL);
g_string_append (gtrace, (const gchar *) buffer);
}
}
close (out_fd[0]);
}
else if (pid == (pid_t) -1)
{
/* Fork failed. */
return FALSE;
}
#ifdef HAVE_EXECINFO_H
if (! stack_printed)
{
/* As a last resort, try using the backtrace() Linux API. It is a bit
* less fancy than gdb or lldb, which is why it is not given priority.
*/
void *bt_buf[100];
char **symbols;
int n_symbols;
int i;
n_symbols = backtrace (bt_buf, 100);
symbols = backtrace_symbols (bt_buf, n_symbols);
if (symbols)
{
stack_printed = TRUE;
for (i = 0; i < n_symbols; i++)
{
if (file)
g_fprintf (file, "%s\n", (const gchar *) symbols[i]);
if (trace)
{
if (! gtrace)
gtrace = g_string_new (NULL);
g_string_append (gtrace,
(const gchar *) symbols[i]);
g_string_append_c (gtrace, '\n');
}
}
free (symbols);
}
}
#endif /* HAVE_EXECINFO_H */
if (trace)
{
if (gtrace)
*trace = g_string_free (gtrace, FALSE);
else
*trace = NULL;
}
#endif /* G_OS_WIN32 */
return stack_printed;
}
/* This is mostly the same as g_on_error_query() except that we use our
* own backtrace function, much more complete.
*/
static void
gimp_on_error_query (void)
{
#ifndef G_OS_WIN32
gchar buf[16];
retry:
g_fprintf (stdout,
"%s (pid:%u): %s: ",
full_prog_name,
(guint) getpid (),
"[E]xit, show [S]tack trace or [P]roceed");
fflush (stdout);
if (isatty(0) && isatty(1))
fgets (buf, 8, stdin);
else
strcpy (buf, "E\n");
if ((buf[0] == 'E' || buf[0] == 'e')
&& buf[1] == '\n')
_exit (0);
else if ((buf[0] == 'P' || buf[0] == 'p')
&& buf[1] == '\n')
return;
else if ((buf[0] == 'S' || buf[0] == 's')
&& buf[1] == '\n')
{
if (! gimp_print_stack_trace (stdout, NULL))
g_fprintf (stderr, "%s\n", "Stack trace not available on your system.");
goto retry;
}
else
goto retry;
#endif
}