summaryrefslogtreecommitdiffstats
path: root/app/core/gimpbacktrace-windows.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/core/gimpbacktrace-windows.c')
-rw-r--r--app/core/gimpbacktrace-windows.c706
1 files changed, 706 insertions, 0 deletions
diff --git a/app/core/gimpbacktrace-windows.c b/app/core/gimpbacktrace-windows.c
new file mode 100644
index 0000000..da1fb73
--- /dev/null
+++ b/app/core/gimpbacktrace-windows.c
@@ -0,0 +1,706 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpbacktrace-windows.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/>.
+ */
+
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "gimpbacktrace-backend.h"
+
+
+#ifdef GIMP_BACKTRACE_BACKEND_WINDOWS
+
+
+#include <windows.h>
+#include <psapi.h>
+#include <tlhelp32.h>
+#include <dbghelp.h>
+
+#include <string.h>
+
+#include "core-types.h"
+
+#include "gimpbacktrace.h"
+
+
+#define MAX_N_THREADS 256
+#define MAX_N_FRAMES 256
+#define THREAD_ENUMERATION_INTERVAL G_TIME_SPAN_SECOND
+
+
+typedef struct _Thread Thread;
+typedef struct _GimpBacktraceThread GimpBacktraceThread;
+
+
+struct _Thread
+{
+ DWORD tid;
+
+ union
+ {
+ gchar *name;
+ guint64 time;
+ };
+};
+
+struct _GimpBacktraceThread
+{
+ DWORD tid;
+ const gchar *name;
+ guint64 time;
+ guint64 last_time;
+
+ 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 void gimp_backtrace_set_thread_name (DWORD tid,
+ const gchar *name);
+
+static gboolean gimp_backtrace_enumerate_threads (void);
+
+static LONG WINAPI gimp_backtrace_exception_handler (PEXCEPTION_POINTERS info);
+
+
+/* static variables */
+
+static GMutex mutex;
+static gint n_initializations;
+static gboolean initialized;
+Thread threads[MAX_N_THREADS];
+gint n_threads;
+gint64 last_thread_enumeration_time;
+Thread thread_names[MAX_N_THREADS];
+gint n_thread_names;
+gint thread_names_spinlock;
+Thread thread_times[MAX_N_THREADS];
+gint n_thread_times;
+
+DWORD WINAPI (* gimp_backtrace_SymSetOptions) (DWORD SymOptions);
+BOOL WINAPI (* gimp_backtrace_SymInitialize) (HANDLE hProcess,
+ PCSTR UserSearchPath,
+ BOOL fInvadeProcess);
+BOOL WINAPI (* gimp_backtrace_SymCleanup) (HANDLE hProcess);
+BOOL WINAPI (* gimp_backtrace_SymFromAddr) (HANDLE hProcess,
+ DWORD64 Address,
+ PDWORD64 Displacement,
+ PSYMBOL_INFO Symbol);
+BOOL WINAPI (* gimp_backtrace_SymGetLineFromAddr64) (HANDLE hProcess,
+ DWORD64 qwAddr,
+ PDWORD pdwDisplacement,
+ PIMAGEHLP_LINE64 Line64);
+
+
+/* private functions */
+
+
+static inline gint
+gimp_backtrace_normalize_frame (GimpBacktrace *backtrace,
+ gint thread,
+ gint frame)
+{
+ if (frame >= 0)
+ return frame;
+ else
+ return backtrace->threads[thread].n_frames + frame;
+}
+
+static void
+gimp_backtrace_set_thread_name (DWORD tid,
+ const gchar *name)
+{
+ while (! g_atomic_int_compare_and_exchange (&thread_names_spinlock,
+ 0, 1));
+
+ if (n_thread_names < MAX_N_THREADS)
+ {
+ Thread *thread = &thread_names[n_thread_names++];
+
+ thread->tid = tid;
+ thread->name = g_strdup (name);
+ }
+
+ g_atomic_int_set (&thread_names_spinlock, 0);
+}
+
+static gboolean
+gimp_backtrace_enumerate_threads (void)
+{
+ HANDLE hThreadSnap;
+ THREADENTRY32 te32;
+ DWORD pid;
+ gint64 time;
+
+ time = g_get_monotonic_time ();
+
+ if (time - last_thread_enumeration_time < THREAD_ENUMERATION_INTERVAL)
+ return n_threads > 0;
+
+ last_thread_enumeration_time = time;
+
+ n_threads = 0;
+
+ hThreadSnap = CreateToolhelp32Snapshot (TH32CS_SNAPTHREAD, 0);
+
+ if (hThreadSnap == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ te32.dwSize = sizeof (te32);
+
+ if (! Thread32First (hThreadSnap, &te32))
+ {
+ CloseHandle (hThreadSnap);
+
+ return FALSE;
+ }
+
+ pid = GetCurrentProcessId ();
+
+ while (! g_atomic_int_compare_and_exchange (&thread_names_spinlock, 0, 1));
+
+ do
+ {
+ if (n_threads == MAX_N_THREADS)
+ break;
+
+ if (te32.th32OwnerProcessID == pid)
+ {
+ Thread *thread = &threads[n_threads++];
+ gint i;
+
+ thread->tid = te32.th32ThreadID;
+ thread->name = NULL;
+
+ for (i = n_thread_names - 1; i >= 0; i--)
+ {
+ if (thread->tid == thread_names[i].tid)
+ {
+ thread->name = thread_names[i].name;
+
+ break;
+ }
+ }
+ }
+ }
+ while (Thread32Next (hThreadSnap, &te32));
+
+ g_atomic_int_set (&thread_names_spinlock, 0);
+
+ CloseHandle (hThreadSnap);
+
+ return n_threads > 0;
+}
+
+static LONG WINAPI
+gimp_backtrace_exception_handler (PEXCEPTION_POINTERS info)
+{
+ #define EXCEPTION_SET_THREAD_NAME ((DWORD) 0x406D1388)
+
+ typedef struct _THREADNAME_INFO
+ {
+ DWORD dwType; /* must be 0x1000 */
+ LPCSTR szName; /* pointer to name (in user addr space) */
+ DWORD dwThreadID; /* thread ID (-1=caller thread) */
+ DWORD dwFlags; /* reserved for future use, must be zero */
+ } THREADNAME_INFO;
+
+ if (info->ExceptionRecord != NULL &&
+ info->ExceptionRecord->ExceptionCode == EXCEPTION_SET_THREAD_NAME &&
+ info->ExceptionRecord->NumberParameters *
+ sizeof (ULONG_PTR) == sizeof (THREADNAME_INFO))
+ {
+ THREADNAME_INFO name_info;
+
+ memcpy (&name_info, info->ExceptionRecord->ExceptionInformation,
+ sizeof (name_info));
+
+ if (name_info.dwType == 0x1000)
+ {
+ DWORD tid = name_info.dwThreadID;
+
+ if (tid == -1)
+ tid = GetCurrentThreadId ();
+
+ gimp_backtrace_set_thread_name (tid, name_info.szName);
+
+ return EXCEPTION_CONTINUE_EXECUTION;
+ }
+ }
+
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ #undef EXCEPTION_SET_THREAD_NAME
+}
+
+
+/* public functions */
+
+
+void
+gimp_backtrace_init (void)
+{
+ gimp_backtrace_set_thread_name (GetCurrentThreadId (), g_get_prgname ());
+
+ AddVectoredExceptionHandler (TRUE, gimp_backtrace_exception_handler);
+}
+
+gboolean
+gimp_backtrace_start (void)
+{
+ g_mutex_lock (&mutex);
+
+ if (n_initializations == 0)
+ {
+ HMODULE hModule;
+ DWORD options;
+
+ hModule = LoadLibraryA ("mgwhelp.dll");
+
+ #define INIT_PROC(name) \
+ G_STMT_START \
+ { \
+ gimp_backtrace_##name = name; \
+ \
+ if (hModule) \
+ { \
+ gpointer proc = GetProcAddress (hModule, #name); \
+ \
+ if (proc) \
+ gimp_backtrace_##name = proc; \
+ } \
+ } \
+ G_STMT_END
+
+ INIT_PROC (SymSetOptions);
+ INIT_PROC (SymInitialize);
+ INIT_PROC (SymCleanup);
+ INIT_PROC (SymFromAddr);
+ INIT_PROC (SymGetLineFromAddr64);
+
+ #undef INIT_PROC
+
+ options = SymGetOptions ();
+
+ options &= ~SYMOPT_UNDNAME;
+ options |= SYMOPT_OMAP_FIND_NEAREST |
+ SYMOPT_DEFERRED_LOADS |
+ SYMOPT_DEBUG;
+
+#ifdef ARCH_X86_64
+ options |= SYMOPT_INCLUDE_32BIT_MODULES;
+#endif
+
+ gimp_backtrace_SymSetOptions (options);
+
+ if (gimp_backtrace_SymInitialize (GetCurrentProcess (), NULL, TRUE))
+ {
+ n_threads = 0;
+ last_thread_enumeration_time = 0;
+ n_thread_times = 0;
+
+ 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)
+ {
+ if (initialized)
+ {
+ gimp_backtrace_SymCleanup (GetCurrentProcess ());
+
+ initialized = FALSE;
+ }
+ }
+
+ g_mutex_unlock (&mutex);
+}
+
+GimpBacktrace *
+gimp_backtrace_new (gboolean include_current_thread)
+{
+ GimpBacktrace *backtrace;
+ HANDLE hProcess;
+ DWORD tid;
+ gint i;
+
+ if (! initialized)
+ return NULL;
+
+ g_mutex_lock (&mutex);
+
+ if (! gimp_backtrace_enumerate_threads ())
+ {
+ g_mutex_unlock (&mutex);
+
+ return NULL;
+ }
+
+ hProcess = GetCurrentProcess ();
+ tid = GetCurrentThreadId ();
+
+ backtrace = g_slice_new (GimpBacktrace);
+
+ backtrace->threads = g_new (GimpBacktraceThread, n_threads);
+ backtrace->n_threads = 0;
+
+ for (i = 0; i < n_threads; i++)
+ {
+ GimpBacktraceThread *thread = &backtrace->threads[backtrace->n_threads];
+ HANDLE hThread;
+ CONTEXT context = {};
+ STACKFRAME64 frame = {};
+ DWORD machine_type;
+ FILETIME creation_time;
+ FILETIME exit_time;
+ FILETIME kernel_time;
+ FILETIME user_time;
+
+ if (! include_current_thread && threads[i].tid == tid)
+ continue;
+
+ hThread = OpenThread (THREAD_QUERY_INFORMATION |
+ THREAD_GET_CONTEXT |
+ THREAD_SUSPEND_RESUME,
+ FALSE,
+ threads[i].tid);
+
+ if (hThread == INVALID_HANDLE_VALUE)
+ continue;
+
+ if (threads[i].tid != tid && SuspendThread (hThread) == (DWORD) -1)
+ {
+ CloseHandle (hThread);
+
+ continue;
+ }
+
+ context.ContextFlags = CONTEXT_FULL;
+
+ if (! GetThreadContext (hThread, &context))
+ {
+ if (threads[i].tid != tid)
+ ResumeThread (hThread);
+
+ CloseHandle (hThread);
+
+ continue;
+ }
+
+#ifdef ARCH_X86_64
+ machine_type = IMAGE_FILE_MACHINE_AMD64;
+ frame.AddrPC.Offset = context.Rip;
+ frame.AddrPC.Mode = AddrModeFlat;
+ frame.AddrStack.Offset = context.Rsp;
+ frame.AddrStack.Mode = AddrModeFlat;
+ frame.AddrFrame.Offset = context.Rbp;
+ frame.AddrFrame.Mode = AddrModeFlat;
+#elif defined (ARCH_X86)
+ machine_type = IMAGE_FILE_MACHINE_I386;
+ frame.AddrPC.Offset = context.Eip;
+ frame.AddrPC.Mode = AddrModeFlat;
+ frame.AddrStack.Offset = context.Esp;
+ frame.AddrStack.Mode = AddrModeFlat;
+ frame.AddrFrame.Offset = context.Ebp;
+ frame.AddrFrame.Mode = AddrModeFlat;
+#else
+#error unsupported architecture
+#endif
+
+ thread->tid = threads[i].tid;
+ thread->name = threads[i].name;
+ thread->last_time = 0;
+ thread->time = 0;
+
+ thread->n_frames = 0;
+
+ while (thread->n_frames < MAX_N_FRAMES &&
+ StackWalk64 (machine_type, hProcess, hThread, &frame, &context,
+ NULL,
+ SymFunctionTableAccess64,
+ SymGetModuleBase64,
+ NULL))
+ {
+ thread->frames[thread->n_frames++] = frame.AddrPC.Offset;
+
+ if (frame.AddrPC.Offset == frame.AddrReturn.Offset)
+ break;
+ }
+
+ if (GetThreadTimes (hThread,
+ &creation_time, &exit_time,
+ &kernel_time, &user_time))
+ {
+ thread->time = (((guint64) kernel_time.dwHighDateTime << 32) |
+ ((guint64) kernel_time.dwLowDateTime)) +
+ (((guint64) user_time.dwHighDateTime << 32) |
+ ((guint64) user_time.dwLowDateTime));
+
+ if (i < n_thread_times && thread->tid == thread_times[i].tid)
+ {
+ thread->last_time = thread_times[i].time;
+ }
+ else
+ {
+ gint j;
+
+ for (j = 0; j < n_thread_times; j++)
+ {
+ if (thread->tid == thread_times[j].tid)
+ {
+ thread->last_time = thread_times[j].time;
+
+ break;
+ }
+ }
+ }
+ }
+
+ if (threads[i].tid != tid)
+ ResumeThread (hThread);
+
+ CloseHandle (hThread);
+
+ if (thread->n_frames > 0)
+ backtrace->n_threads++;
+ }
+
+ n_thread_times = backtrace->n_threads;
+
+ for (i = 0; i < backtrace->n_threads; i++)
+ {
+ thread_times[i].tid = backtrace->threads[i].tid;
+ thread_times[i].time = backtrace->threads[i].time;
+ }
+
+ g_mutex_unlock (&mutex);
+
+ if (backtrace->n_threads == 0)
+ {
+ gimp_backtrace_free (backtrace);
+
+ return NULL;
+ }
+
+ return backtrace;
+}
+
+void
+gimp_backtrace_free (GimpBacktrace *backtrace)
+{
+ if (backtrace)
+ {
+ 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);
+
+ return backtrace->threads[thread].name;
+}
+
+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].time >
+ backtrace->threads[thread].last_time;
+}
+
+gint
+gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace,
+ guintptr thread_id,
+ gint thread_hint)
+{
+ DWORD 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 backtrace->threads[thread].n_frames;
+}
+
+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 >= 0 &&
+ frame < backtrace->threads[thread].n_frames, 0);
+
+ return backtrace->threads[thread].frames[frame];
+}
+
+gboolean
+gimp_backtrace_get_address_info (guintptr address,
+ GimpBacktraceAddressInfo *info)
+{
+ SYMBOL_INFO *symbol_info;
+ HANDLE hProcess;
+ HMODULE hModule;
+ DWORD64 offset = 0;
+ IMAGEHLP_LINE64 line = {};
+ DWORD line_offset = 0;
+ gboolean result = FALSE;
+
+ hProcess = GetCurrentProcess ();
+ hModule = (HMODULE) (guintptr) SymGetModuleBase64 (hProcess, address);
+
+ if (hModule && GetModuleFileNameExA (hProcess, hModule,
+ info->object_name,
+ sizeof (info->object_name)))
+ {
+ result = TRUE;
+ }
+ else
+ {
+ info->object_name[0] = '\0';
+ }
+
+ symbol_info = g_malloc (sizeof (SYMBOL_INFO) +
+ sizeof (info->symbol_name) - 1);
+
+ symbol_info->SizeOfStruct = sizeof (SYMBOL_INFO);
+ symbol_info->MaxNameLen = sizeof (info->symbol_name);
+
+ if (gimp_backtrace_SymFromAddr (hProcess, address,
+ &offset, symbol_info))
+ {
+ g_strlcpy (info->symbol_name, symbol_info->Name,
+ sizeof (info->symbol_name));
+
+ info->symbol_address = offset ? address - offset : 0;
+
+ result = TRUE;
+ }
+ else
+ {
+ info->symbol_name[0] = '\0';
+ info->symbol_address = 0;
+ }
+
+ g_free (symbol_info);
+
+ if (gimp_backtrace_SymGetLineFromAddr64 (hProcess, address,
+ &line_offset, &line))
+ {
+ g_strlcpy (info->source_file, line.FileName,
+ sizeof (info->source_file));
+
+ info->source_line = line.LineNumber;
+
+ result = TRUE;
+ }
+ else
+ {
+ info->source_file[0] = '\0';
+ info->source_line = 0;
+ }
+
+ return result;
+}
+
+
+#endif /* GIMP_BACKTRACE_BACKEND_WINDOWS */