diff options
Diffstat (limited to '')
-rw-r--r-- | app/core/gimpbacktrace-linux.c | 725 |
1 files changed, 725 insertions, 0 deletions
diff --git a/app/core/gimpbacktrace-linux.c b/app/core/gimpbacktrace-linux.c new file mode 100644 index 0000000..a8593f8 --- /dev/null +++ b/app/core/gimpbacktrace-linux.c @@ -0,0 +1,725 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * gimpbacktrace-linux.c + * Copyright (C) 2018 Ell + * + * 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 <https://www.gnu.org/licenses/>. + */ + + +#define _GNU_SOURCE + + +#include "config.h" + +#include <gio/gio.h> + +#include "gimpbacktrace-backend.h" + + +#ifdef GIMP_BACKTRACE_BACKEND_LINUX + + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <signal.h> +#include <execinfo.h> +#include <dlfcn.h> +#include <string.h> +#include <stdio.h> + +#ifdef HAVE_LIBBACKTRACE +#include <backtrace.h> +#endif + +#ifdef HAVE_LIBUNWIND +#define UNW_LOCAL_ONLY +#include <libunwind.h> +#endif + +#include "core-types.h" + +#include "gimpbacktrace.h" + + +#define MAX_N_THREADS 256 +#define MAX_N_FRAMES 256 +#define MAX_THREAD_NAME_SIZE 32 +#define N_SKIPPED_FRAMES 2 +#define MAX_WAIT_TIME (G_TIME_SPAN_SECOND / 20) +#define BACKTRACE_SIGNAL SIGUSR1 + + +typedef struct _GimpBacktraceThread GimpBacktraceThread; + + +struct _GimpBacktraceThread +{ + pid_t tid; + gchar name[MAX_THREAD_NAME_SIZE]; + gchar state; + + guintptr frames[MAX_N_FRAMES]; + gint n_frames; +}; + +struct _GimpBacktrace +{ + GimpBacktraceThread *threads; + gint n_threads; +}; + + +/* local function prototypes */ + +static inline gint gimp_backtrace_normalize_frame (GimpBacktrace *backtrace, + gint thread, + gint frame); + +static gint gimp_backtrace_enumerate_threads (gboolean include_current_thread, + pid_t *threads, + gint size); +static void gimp_backtrace_read_thread_name (pid_t tid, + gchar *name, + gint size); +static gchar gimp_backtrace_read_thread_state (pid_t tid); + +static void gimp_backtrace_signal_handler (gint signum); + + +/* static variables */ + +static GMutex mutex; +static gint n_initializations; +static gboolean initialized; +static struct sigaction orig_action; +static pid_t blacklisted_threads[MAX_N_THREADS]; +static gint n_blacklisted_threads; +static GimpBacktrace *handler_backtrace; +static gint handler_n_remaining_threads; +static gint handler_lock; + +#ifdef HAVE_LIBBACKTRACE +static struct backtrace_state *backtrace_state; +#endif + +static const gchar * const blacklisted_thread_names[] = +{ + "gmain", + "threaded-ml" +}; + + +/* private functions */ + + +static inline gint +gimp_backtrace_normalize_frame (GimpBacktrace *backtrace, + gint thread, + gint frame) +{ + if (frame >= 0) + return frame + N_SKIPPED_FRAMES; + else + return backtrace->threads[thread].n_frames + frame; +} + +static gint +gimp_backtrace_enumerate_threads (gboolean include_current_thread, + pid_t *threads, + gint size) +{ + DIR *dir; + struct dirent *dirent; + pid_t tid; + gint n_threads; + + dir = opendir ("/proc/self/task"); + + if (! dir) + return 0; + + tid = syscall (SYS_gettid); + + n_threads = 0; + + while (n_threads < size && (dirent = readdir (dir))) + { + pid_t id = g_ascii_strtoull (dirent->d_name, NULL, 10); + + if (id) + { + if (! include_current_thread && id == tid) + id = 0; + } + + if (id) + { + gint i; + + for (i = 0; i < n_blacklisted_threads; i++) + { + if (id == blacklisted_threads[i]) + { + id = 0; + + break; + } + } + } + + if (id) + threads[n_threads++] = id; + } + + closedir (dir); + + return n_threads; +} + +static void +gimp_backtrace_read_thread_name (pid_t tid, + gchar *name, + gint size) +{ + gchar filename[64]; + gint fd; + + if (size <= 0) + return; + + name[0] = '\0'; + + g_snprintf (filename, sizeof (filename), + "/proc/self/task/%llu/comm", + (unsigned long long) tid); + + fd = open (filename, O_RDONLY); + + if (fd >= 0) + { + gint n = read (fd, name, size); + + if (n > 0) + name[n - 1] = '\0'; + + close (fd); + } +} + +static gchar +gimp_backtrace_read_thread_state (pid_t tid) +{ + gchar buffer[64]; + gint fd; + gchar state = '\0'; + + g_snprintf (buffer, sizeof (buffer), + "/proc/self/task/%llu/stat", + (unsigned long long) tid); + + fd = open (buffer, O_RDONLY); + + if (fd >= 0) + { + gint n = read (fd, buffer, sizeof (buffer)); + + if (n > 0) + buffer[n - 1] = '\0'; + + sscanf (buffer, "%*d %*s %c", &state); + + close (fd); + } + + return state; +} + +static void +gimp_backtrace_signal_handler (gint signum) +{ + GimpBacktrace *curr_backtrace; + gint lock; + + do + { + lock = g_atomic_int_get (&handler_lock); + + if (lock < 0) + continue; + } + while (! g_atomic_int_compare_and_exchange (&handler_lock, lock, lock + 1)); + + curr_backtrace = g_atomic_pointer_get (&handler_backtrace); + + if (curr_backtrace) + { + pid_t tid = syscall (SYS_gettid); + gint i; + + for (i = 0; i < curr_backtrace->n_threads; i++) + { + GimpBacktraceThread *thread = &curr_backtrace->threads[i]; + + if (thread->tid == tid) + { + thread->n_frames = backtrace ((gpointer *) thread->frames, + MAX_N_FRAMES); + + g_atomic_int_dec_and_test (&handler_n_remaining_threads); + + break; + } + } + } + + g_atomic_int_dec_and_test (&handler_lock); +} + + +/* public functions */ + + +void +gimp_backtrace_init (void) +{ +#ifdef HAVE_LIBBACKTRACE + backtrace_state = backtrace_create_state (NULL, 0, NULL, NULL); +#endif +} + +gboolean +gimp_backtrace_start (void) +{ + g_mutex_lock (&mutex); + + if (n_initializations == 0) + { + struct sigaction action = {}; + + action.sa_handler = gimp_backtrace_signal_handler; + action.sa_flags = SA_RESTART; + + sigemptyset (&action.sa_mask); + + if (sigaction (BACKTRACE_SIGNAL, &action, &orig_action) == 0) + { + pid_t *threads; + gint n_threads; + gint i; + + n_blacklisted_threads = 0; + + threads = g_new (pid_t, MAX_N_THREADS); + + n_threads = gimp_backtrace_enumerate_threads (TRUE, + threads, MAX_N_THREADS); + + for (i = 0; i < n_threads; i++) + { + gchar name[MAX_THREAD_NAME_SIZE]; + gint j; + + gimp_backtrace_read_thread_name (threads[i], + name, MAX_THREAD_NAME_SIZE); + + for (j = 0; j < G_N_ELEMENTS (blacklisted_thread_names); j++) + { + if (! strcmp (name, blacklisted_thread_names[j])) + { + blacklisted_threads[n_blacklisted_threads++] = threads[i]; + } + } + } + + g_free (threads); + + initialized = TRUE; + } + } + + n_initializations++; + + g_mutex_unlock (&mutex); + + return initialized; +} + +void +gimp_backtrace_stop (void) +{ + g_return_if_fail (n_initializations > 0); + + g_mutex_lock (&mutex); + + n_initializations--; + + if (n_initializations == 0 && initialized) + { + if (sigaction (BACKTRACE_SIGNAL, &orig_action, NULL) < 0) + g_warning ("failed to restore origianl backtrace signal handler"); + + initialized = FALSE; + } + + g_mutex_unlock (&mutex); +} + +GimpBacktrace * +gimp_backtrace_new (gboolean include_current_thread) +{ + GimpBacktrace *backtrace; + pid_t pid; + pid_t *threads; + gint n_threads; + gint64 start_time; + gint i; + + if (! initialized) + return NULL; + + pid = getpid (); + + threads = g_new (pid_t, MAX_N_THREADS); + + n_threads = gimp_backtrace_enumerate_threads (include_current_thread, + threads, MAX_N_THREADS); + + if (n_threads == 0) + { + g_free (threads); + + return NULL; + } + + g_mutex_lock (&mutex); + + backtrace = g_slice_new (GimpBacktrace); + + backtrace->threads = g_new (GimpBacktraceThread, n_threads); + backtrace->n_threads = n_threads; + + while (! g_atomic_int_compare_and_exchange (&handler_lock, 0, -1)); + + g_atomic_pointer_set (&handler_backtrace, backtrace); + g_atomic_int_set (&handler_n_remaining_threads, n_threads); + + g_atomic_int_set (&handler_lock, 0); + + for (i = 0; i < n_threads; i++) + { + GimpBacktraceThread *thread = &backtrace->threads[i]; + + thread->tid = threads[i]; + thread->n_frames = 0; + + gimp_backtrace_read_thread_name (thread->tid, + thread->name, MAX_THREAD_NAME_SIZE); + + thread->state = gimp_backtrace_read_thread_state (thread->tid); + + syscall (SYS_tgkill, pid, threads[i], BACKTRACE_SIGNAL); + } + + g_free (threads); + + start_time = g_get_monotonic_time (); + + while (g_atomic_int_get (&handler_n_remaining_threads) > 0) + { + gint64 time = g_get_monotonic_time (); + + if (time - start_time > MAX_WAIT_TIME) + break; + + g_usleep (1000); + } + + while (! g_atomic_int_compare_and_exchange (&handler_lock, 0, -1)); + + g_atomic_pointer_set (&handler_backtrace, NULL); + + g_atomic_int_set (&handler_lock, 0); + +#if 0 + if (handler_n_remaining_threads > 0) + { + gint j = 0; + + for (i = 0; i < n_threads; i++) + { + if (backtrace->threads[i].n_frames == 0) + { + if (n_blacklisted_threads < MAX_N_THREADS) + { + blacklisted_threads[n_blacklisted_threads++] = + backtrace->threads[i].tid; + } + } + else + { + if (j < i) + backtrace->threads[j] = backtrace->threads[i]; + + j++; + } + } + + n_threads = j; + } +#endif + + g_mutex_unlock (&mutex); + + if (n_threads == 0) + { + gimp_backtrace_free (backtrace); + + return NULL; + } + + return backtrace; +} + +void +gimp_backtrace_free (GimpBacktrace *backtrace) +{ + if (! backtrace) + return; + + g_free (backtrace->threads); + + g_slice_free (GimpBacktrace, backtrace); +} + +gint +gimp_backtrace_get_n_threads (GimpBacktrace *backtrace) +{ + g_return_val_if_fail (backtrace != NULL, 0); + + return backtrace->n_threads; +} + +guintptr +gimp_backtrace_get_thread_id (GimpBacktrace *backtrace, + gint thread) +{ + g_return_val_if_fail (backtrace != NULL, 0); + g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, 0); + + return backtrace->threads[thread].tid; +} + +const gchar * +gimp_backtrace_get_thread_name (GimpBacktrace *backtrace, + gint thread) +{ + g_return_val_if_fail (backtrace != NULL, NULL); + g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, NULL); + + if (backtrace->threads[thread].name[0]) + return backtrace->threads[thread].name; + else + return NULL; +} + +gboolean +gimp_backtrace_is_thread_running (GimpBacktrace *backtrace, + gint thread) +{ + g_return_val_if_fail (backtrace != NULL, FALSE); + g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, FALSE); + + return backtrace->threads[thread].state == 'R'; +} + +gint +gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace, + guintptr thread_id, + gint thread_hint) +{ + pid_t tid = thread_id; + gint i; + + g_return_val_if_fail (backtrace != NULL, -1); + + if (thread_hint >= 0 && + thread_hint < backtrace->n_threads && + backtrace->threads[thread_hint].tid == tid) + { + return thread_hint; + } + + for (i = 0; i < backtrace->n_threads; i++) + { + if (backtrace->threads[i].tid == tid) + return i; + } + + return -1; +} + +gint +gimp_backtrace_get_n_frames (GimpBacktrace *backtrace, + gint thread) +{ + g_return_val_if_fail (backtrace != NULL, 0); + g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, 0); + + return MAX (backtrace->threads[thread].n_frames - N_SKIPPED_FRAMES, 0); +} + +guintptr +gimp_backtrace_get_frame_address (GimpBacktrace *backtrace, + gint thread, + gint frame) +{ + g_return_val_if_fail (backtrace != NULL, 0); + g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, 0); + + frame = gimp_backtrace_normalize_frame (backtrace, thread, frame); + + g_return_val_if_fail (frame >= N_SKIPPED_FRAMES && + frame < backtrace->threads[thread].n_frames, 0); + + return backtrace->threads[thread].frames[frame]; +} + +#ifdef HAVE_LIBBACKTRACE +static void +gimp_backtrace_syminfo_callback (GimpBacktraceAddressInfo *info, + guintptr pc, + const gchar *symname, + guintptr symval, + guintptr symsize) +{ + if (symname) + g_strlcpy (info->symbol_name, symname, sizeof (info->symbol_name)); + + info->symbol_address = symval; +} + +static gint +gimp_backtrace_pcinfo_callback (GimpBacktraceAddressInfo *info, + guintptr pc, + const gchar *filename, + gint lineno, + const gchar *function) +{ + if (function) + g_strlcpy (info->symbol_name, function, sizeof (info->symbol_name)); + + if (filename) + g_strlcpy (info->source_file, filename, sizeof (info->source_file)); + + info->source_line = lineno; + + return 0; +} +#endif /* HAVE_LIBBACKTRACE */ + +gboolean +gimp_backtrace_get_address_info (guintptr address, + GimpBacktraceAddressInfo *info) +{ + Dl_info dl_info; + gboolean result = FALSE; + + g_return_val_if_fail (info != NULL, FALSE); + + info->object_name[0] = '\0'; + + info->symbol_name[0] = '\0'; + info->symbol_address = 0; + + info->source_file[0] = '\0'; + info->source_line = 0; + + if (dladdr ((gpointer) address, &dl_info)) + { + if (dl_info.dli_fname) + { + g_strlcpy (info->object_name, dl_info.dli_fname, + sizeof (info->object_name)); + } + + if (dl_info.dli_sname) + { + g_strlcpy (info->symbol_name, dl_info.dli_sname, + sizeof (info->symbol_name)); + } + + info->symbol_address = (guintptr) dl_info.dli_saddr; + + result = TRUE; + } + +#ifdef HAVE_LIBBACKTRACE + if (backtrace_state) + { + backtrace_syminfo ( + backtrace_state, address, + (backtrace_syminfo_callback) gimp_backtrace_syminfo_callback, + NULL, + info); + + backtrace_pcinfo ( + backtrace_state, address, + (backtrace_full_callback) gimp_backtrace_pcinfo_callback, + NULL, + info); + + result = TRUE; + } +#endif /* HAVE_LIBBACKTRACE */ + +#ifdef HAVE_LIBUNWIND +/* we use libunwind to get the symbol name, when available, even if dladdr() or + * libbacktrace already found one, since it provides more descriptive names in + * some cases, and, in particular, full symbol names for C++ lambdas. + * + * note that, in some cases, this can result in a discrepancy between the + * symbol name, and the corresponding source location. + */ +#if 0 + if (! info->symbol_name[0]) +#endif + { + unw_context_t context = {}; + unw_cursor_t cursor; + unw_word_t offset; + + if (unw_init_local (&cursor, &context) == 0 && + unw_set_reg (&cursor, UNW_REG_IP, address) == 0 && + unw_get_proc_name (&cursor, + info->symbol_name, sizeof (info->symbol_name), + &offset) == 0) + { + info->symbol_address = address - offset; + + result = TRUE; + } + } +#endif /* HAVE_LIBUNWIND */ + + return result; +} + + +#endif /* GIMP_BACKTRACE_BACKEND_LINUX */ |