
In the GimpBacktrace Windows backend, avoid reporting meaningless symbol addresses when failing to retrieve meaningful ones. Unfortunately, it seems that we never get symbol addresses for symbols that have debug information, which negatively affects the log viewer's call graph. We're going to have to work around this.
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 */
|