[gimp] app: add Windows backend to GimpBacktrace
- From: Ell <ell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp] app: add Windows backend to GimpBacktrace
- Date: Wed, 12 Sep 2018 11:56:15 +0000 (UTC)
commit 667efc221d4586bfbe07cf91a017735e72810200
Author: Ell <ell_se yahoo com>
Date: Mon Sep 3 15:52:36 2018 -0400
app: add Windows backend to GimpBacktrace
The Windows backend produces full, multithreaded backtraces. When
DrMingw is available, it also provides full symbol and (where
available) source-location information. Otherwise, it provides
symbol information for most of our libraries, but not for the GIMP
binary itself.
app/Makefile.am | 10 +-
app/core/Makefile.am | 1 +
app/core/gimpbacktrace-backend.h | 2 +
app/core/gimpbacktrace-windows.c | 627 +++++++++++++++++++++++++++++++++++++++
configure.ac | 19 +-
5 files changed, 648 insertions(+), 11 deletions(-)
---
diff --git a/app/Makefile.am b/app/Makefile.am
index 22b3b75704..114a809caf 100644
--- a/app/Makefile.am
+++ b/app/Makefile.am
@@ -85,10 +85,13 @@ endif
if OS_WIN32
win32_ldflags = -mwindows -Wl,--tsaware $(WIN32_LARGE_ADDRESS_AWARE)
-# for GetProcessMemoryInfo() in the dashboard
+# for GimpDashboard and GimpBacktrace
psapi_cflags = -DPSAPI_VERSION=1
libpsapi = -lpsapi
+# for GimpBacktrace
+libdbghelp = -ldbghelp
+
if HAVE_EXCHNDL
exchndl = -lexchndl
endif
@@ -182,7 +185,9 @@ gimpconsoleldadd = \
$(INTLLIBS) \
$(RT_LIBS) \
$(libm) \
- $(libdl)
+ $(libdl) \
+ $(libpsapi) \
+ $(libdbghelp)
gimp_@GIMP_APP_VERSION@_LDFLAGS = \
$(AM_LDFLAGS) \
@@ -207,7 +212,6 @@ gimp_@GIMP_APP_VERSION@_LDADD = \
$(GTK_MAC_INTEGRATION_LIBS) \
$(DBUS_GLIB_LIBS) \
$(gimpconsoleldadd) \
- $(libpsapi) \
$(exchndl) \
$(GIMPRC)
diff --git a/app/core/Makefile.am b/app/core/Makefile.am
index 0730818cc3..b3a5a6173f 100644
--- a/app/core/Makefile.am
+++ b/app/core/Makefile.am
@@ -100,6 +100,7 @@ libappcore_a_sources = \
gimpbacktrace-backend.h \
gimpbacktrace-linux.c \
gimpbacktrace-none.c \
+ gimpbacktrace-windows.c \
gimpbezierdesc.h \
gimpbezierdesc.c \
gimpboundary.c \
diff --git a/app/core/gimpbacktrace-backend.h b/app/core/gimpbacktrace-backend.h
index a464d65e09..4bfb1c9434 100644
--- a/app/core/gimpbacktrace-backend.h
+++ b/app/core/gimpbacktrace-backend.h
@@ -24,6 +24,8 @@
#ifdef __gnu_linux__
# define GIMP_BACKTRACE_BACKEND_LINUX
+#elif defined (G_OS_WIN32) && defined (ARCH_X86)
+# define GIMP_BACKTRACE_BACKEND_WINDOWS
#else
# define GIMP_BACKTRACE_BACKEND_NONE
#endif
diff --git a/app/core/gimpbacktrace-windows.c b/app/core/gimpbacktrace-windows.c
new file mode 100644
index 0000000000..2f60f51524
--- /dev/null
+++ b/app/core/gimpbacktrace-windows.c
@@ -0,0 +1,627 @@
+/* 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 <exchndl.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;
+ gchar *name;
+};
+
+struct _GimpBacktraceThread
+{
+ DWORD tid;
+ const gchar *name;
+
+ 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 gboolean gimp_backtrace_enumerate_threads (void);
+
+static LONG 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;
+
+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 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
+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 ();
+
+ 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_info.szName);
+ }
+
+ g_atomic_int_set (&thread_names_spinlock, 0);
+
+ return EXCEPTION_CONTINUE_EXECUTION;
+ }
+ }
+
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ #undef EXCEPTION_SET_THREAD_NAME
+}
+
+
+/* public functions */
+
+
+void
+gimp_backtrace_init (void)
+{
+ 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;
+
+ 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;
+
+ 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;
+
+ 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->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 (threads[i].tid != tid)
+ ResumeThread (hThread);
+
+ CloseHandle (hThread);
+
+ if (thread->n_frames > 0)
+ backtrace->n_threads++;
+ }
+
+ 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;
+}
+
+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 < 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) 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 = address - offset;
+
+ 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 */
diff --git a/configure.ac b/configure.ac
index 4130b493ea..3b72d28eb0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1779,13 +1779,6 @@ AC_SUBST(FILE_HEIF)
AM_CONDITIONAL(HAVE_LIBHEIF, test "x$have_libheif" = xyes)
-#######################################
-# Check for detailed backtraces support
-#######################################
-
-detailed_backtraces=no
-
-
#####################
# Check for libunwind
#####################
@@ -1803,8 +1796,18 @@ fi
if test "x$have_libunwind" = xyes; then
AC_DEFINE(HAVE_LIBUNWIND, 1,
[Define to 1 if libunwind is available])
+fi
+
- detailed_backtraces=yes
+#######################################
+# Check for detailed backtraces support
+#######################################
+
+detailed_backtraces=no
+if test "x$platform_linux" = xyes; then
+ detailed_backtraces=$have_libunwind
+elif test "x$platform_win32" = xyes; then
+ detailed_backtraces=$enable_drmingw
fi
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]