[gimp] app: add Windows backend to GimpBacktrace



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]