Commit 667efc22 authored by Ell's avatar Ell

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.
parent a6ec8571
......@@ -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)
......
......@@ -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 \
......
......@@ -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
......
/* 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 */
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment