mirror of https://github.com/GNOME/gimp.git
704 lines
18 KiB
C
704 lines
18 KiB
C
/* 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 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
|
|
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;
|
|
|
|
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) 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 */
|