summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Withnall <philip@tecnocode.co.uk>2021-06-08 12:10:41 +0000
committerPhilip Withnall <philip@tecnocode.co.uk>2021-06-08 12:10:41 +0000
commit601ef3b6be457a6b0c15ab3a341a0e51f1d02ffd (patch)
treea2e32417452633009901283f365c37ccc12faa92
parentff8b43a15498aeafe392acd97d1ff1107252227e (diff)
parent0908e6a8e7370be6ebb8caf79d8bfe9920cd8c11 (diff)
downloadglib-601ef3b6be457a6b0c15ab3a341a0e51f1d02ffd.tar.gz
Merge branch 'resimplify-w32-veh' into 'main'
Re-simplify exception handling on Windows See merge request GNOME/glib!2031
-rw-r--r--glib/gwin32-private.c30
-rw-r--r--glib/gwin32.c275
-rw-r--r--glib/tests/win32.c62
3 files changed, 221 insertions, 146 deletions
diff --git a/glib/gwin32-private.c b/glib/gwin32-private.c
index f7913b553..c28e92baa 100644
--- a/glib/gwin32-private.c
+++ b/glib/gwin32-private.c
@@ -18,16 +18,16 @@
/* Copy @cmdline into @debugger, and substitute @pid for `%p`
* and @event for `%e`.
- * If @debugger_size (in bytes) is overflowed, return %FALSE.
+ * If @debugger_size (in wchar_ts) is overflowed, return %FALSE.
* Also returns %FALSE when `%` is followed by anything other
* than `e` or `p`.
*/
static gboolean
-_g_win32_subst_pid_and_event (char *debugger,
- gsize debugger_size,
- const char *cmdline,
- DWORD pid,
- guintptr event)
+_g_win32_subst_pid_and_event_w (wchar_t *debugger,
+ gsize debugger_size,
+ const wchar_t *cmdline,
+ DWORD pid,
+ guintptr event)
{
gsize i = 0, dbg_i = 0;
/* These are integers, and they can't be longer than 20 characters
@@ -35,31 +35,31 @@ _g_win32_subst_pid_and_event (char *debugger,
* Use 30 just to be sure.
*/
#define STR_BUFFER_SIZE 30
- char pid_str[STR_BUFFER_SIZE] = {0};
+ wchar_t pid_str[STR_BUFFER_SIZE] = {0};
gsize pid_str_len;
- char event_str[STR_BUFFER_SIZE] = {0};
+ wchar_t event_str[STR_BUFFER_SIZE] = {0};
gsize event_str_len;
- _snprintf_s (pid_str, STR_BUFFER_SIZE, G_N_ELEMENTS (pid_str), "%lu", pid);
+ _snwprintf_s (pid_str, STR_BUFFER_SIZE, G_N_ELEMENTS (pid_str), L"%lu", pid);
pid_str[G_N_ELEMENTS (pid_str) - 1] = 0;
- pid_str_len = strlen (pid_str);
- _snprintf_s (event_str, STR_BUFFER_SIZE, G_N_ELEMENTS (pid_str), "%Iu", event);
+ pid_str_len = wcslen (pid_str);
+ _snwprintf_s (event_str, STR_BUFFER_SIZE, G_N_ELEMENTS (pid_str), L"%Iu", event);
event_str[G_N_ELEMENTS (pid_str) - 1] = 0;
- event_str_len = strlen (event_str);
+ event_str_len = wcslen (event_str);
#undef STR_BUFFER_SIZE
while (cmdline[i] != 0 && dbg_i < debugger_size)
{
- if (cmdline[i] != '%')
+ if (cmdline[i] != L'%')
debugger[dbg_i++] = cmdline[i++];
- else if (cmdline[i + 1] == 'p')
+ else if (cmdline[i + 1] == L'p')
{
gsize j = 0;
while (j < pid_str_len && dbg_i < debugger_size)
debugger[dbg_i++] = pid_str[j++];
i += 2;
}
- else if (cmdline[i + 1] == 'e')
+ else if (cmdline[i + 1] == L'e')
{
gsize j = 0;
while (j < event_str_len && dbg_i < debugger_size)
diff --git a/glib/gwin32.c b/glib/gwin32.c
index b62f19461..f4590916f 100644
--- a/glib/gwin32.c
+++ b/glib/gwin32.c
@@ -1035,10 +1035,32 @@ g_console_win32_init (void)
* it will be non-NULL. Only used to later de-install the handler
* on library de-initialization.
*/
-static void *WinVEH_handle = NULL;
+static void *WinVEH_handle = NULL;
+
+#define DEBUGGER_BUFFER_SIZE (MAX_PATH + 1)
+/* This is the debugger that we'll run on crash */
+static wchar_t debugger[DEBUGGER_BUFFER_SIZE];
+
+static gsize number_of_exceptions_to_catch = 0;
+static DWORD *exceptions_to_catch = NULL;
+
+static HANDLE debugger_wakeup_event = 0;
+static DWORD debugger_spawn_flags = 0;
#include "gwin32-private.c"
+static char *
+copy_chars (char *buffer,
+ gsize *buffer_size,
+ const char *to_copy)
+{
+ gsize copy_count = MIN (strlen (to_copy), *buffer_size - 1);
+ memset (buffer, 0x20, copy_count);
+ strncpy_s (buffer, *buffer_size, to_copy, _TRUNCATE);
+ *buffer_size -= copy_count;
+ return &buffer[copy_count];
+}
+
/* Handles exceptions (useful for debugging).
* Issues a DebugBreak() call if the process is being debugged (not really
* useful - if the process is being debugged, this handler won't be invoked
@@ -1068,24 +1090,31 @@ static void *WinVEH_handle = NULL;
* or for control flow.
*
* This function deliberately avoids calling any GLib code.
+ * This is done on purpose. This function can be called when the program
+ * is in a bad state (crashing). It can also be called very early, as soon
+ * as the handler is installed. Therefore, it's imperative that
+ * it does as little as possible. Preferably, all the work that can be
+ * done in advance (when the program is not crashing yet) should be done
+ * in advance.
*/
static LONG __stdcall
g_win32_veh_handler (PEXCEPTION_POINTERS ExceptionInfo)
{
EXCEPTION_RECORD *er;
- char debugger[MAX_PATH + 1];
- WCHAR *debugger_utf16;
- const char *debugger_env = NULL;
- const char *catch_list;
- gboolean catch = FALSE;
+ gsize i;
STARTUPINFOW si;
PROCESS_INFORMATION pi;
- HANDLE event;
- SECURITY_ATTRIBUTES sa;
+#define ITOA_BUFFER_SIZE 100
+ char itoa_buffer[ITOA_BUFFER_SIZE];
+#define DEBUG_STRING_SIZE 1024
+ gsize dbgs = DEBUG_STRING_SIZE;
+ char debug_string[DEBUG_STRING_SIZE];
+ char *dbgp;
if (ExceptionInfo == NULL ||
ExceptionInfo->ExceptionRecord == NULL ||
- IsDebuggerPresent ())
+ IsDebuggerPresent () ||
+ debugger[0] == 0)
return EXCEPTION_CONTINUE_SEARCH;
er = ExceptionInfo->ExceptionRecord;
@@ -1097,102 +1126,22 @@ g_win32_veh_handler (PEXCEPTION_POINTERS ExceptionInfo)
case EXCEPTION_ILLEGAL_INSTRUCTION:
break;
default:
- catch_list = g_getenv ("G_VEH_CATCH");
+ for (i = 0; i < number_of_exceptions_to_catch; i++)
+ if (exceptions_to_catch[i] == er->ExceptionCode)
+ break;
- while (!catch &&
- catch_list != NULL &&
- catch_list[0] != 0)
- {
- unsigned long catch_code;
- char *end;
- errno = 0;
- catch_code = strtoul (catch_list, &end, 16);
- if (errno != NO_ERROR)
- break;
- catch_list = end;
- if (catch_list != NULL && catch_list[0] == ',')
- catch_list++;
- if (catch_code == er->ExceptionCode)
- catch = TRUE;
- }
-
- if (catch)
- break;
+ if (i == number_of_exceptions_to_catch)
+ return EXCEPTION_CONTINUE_SEARCH;
- return EXCEPTION_CONTINUE_SEARCH;
- }
-
- fprintf_s (stderr,
- "Exception code=0x%lx flags=0x%lx at 0x%p",
- er->ExceptionCode,
- er->ExceptionFlags,
- er->ExceptionAddress);
-
- switch (er->ExceptionCode)
- {
- case EXCEPTION_ACCESS_VIOLATION:
- fprintf_s (stderr,
- ". Access violation - attempting to %s at address 0x%p\n",
- er->ExceptionInformation[0] == 0 ? "read data" :
- er->ExceptionInformation[0] == 1 ? "write data" :
- er->ExceptionInformation[0] == 8 ? "execute data" :
- "do something bad",
- (void *) er->ExceptionInformation[1]);
- break;
- case EXCEPTION_IN_PAGE_ERROR:
- fprintf_s (stderr,
- ". Page access violation - attempting to %s at address 0x%p with status %Ix\n",
- er->ExceptionInformation[0] == 0 ? "read from an inaccessible page" :
- er->ExceptionInformation[0] == 1 ? "write to an inaccessible page" :
- er->ExceptionInformation[0] == 8 ? "execute data in page" :
- "do something bad with a page",
- (void *) er->ExceptionInformation[1],
- er->ExceptionInformation[2]);
- break;
- default:
- fprintf_s (stderr, "\n");
break;
}
- fflush (stderr);
-
- debugger_env = g_getenv ("G_DEBUGGER");
-
- if (debugger_env == NULL)
- return EXCEPTION_CONTINUE_SEARCH;
-
- /* Create an inheritable event */
memset (&si, 0, sizeof (si));
memset (&pi, 0, sizeof (pi));
- memset (&sa, 0, sizeof (sa));
si.cb = sizeof (si);
- sa.nLength = sizeof (sa);
- sa.bInheritHandle = TRUE;
- event = CreateEvent (&sa, FALSE, FALSE, NULL);
-
- /* Put process ID and event handle into debugger commandline */
- if (!_g_win32_subst_pid_and_event (debugger, G_N_ELEMENTS (debugger),
- debugger_env, GetCurrentProcessId (),
- (guintptr) event))
- {
- CloseHandle (event);
- return EXCEPTION_CONTINUE_SEARCH;
- }
- debugger[MAX_PATH] = '\0';
-
- debugger_utf16 = g_utf8_to_utf16 (debugger, -1, NULL, NULL, NULL);
/* Run the debugger */
- if (0 != CreateProcessW (NULL,
- debugger_utf16,
- NULL,
- NULL,
- TRUE,
- g_getenv ("G_DEBUGGER_OLD_CONSOLE") != NULL ? 0 : CREATE_NEW_CONSOLE,
- NULL,
- NULL,
- &si,
- &pi))
+ if (0 != CreateProcessW (NULL, debugger, NULL, NULL, TRUE, debugger_spawn_flags, NULL, NULL, &si, &pi))
{
CloseHandle (pi.hProcess);
CloseHandle (pi.hThread);
@@ -1202,12 +1151,66 @@ g_win32_veh_handler (PEXCEPTION_POINTERS ExceptionInfo)
* up forever in case the debugger does not support
* event signalling.
*/
- WaitForSingleObject (event, 60000);
- }
-
- g_free (debugger_utf16);
+ WaitForSingleObject (debugger_wakeup_event, 60000);
+
+ dbgp = &debug_string[0];
+
+ dbgp = copy_chars (dbgp, &dbgs, "Exception code=0x");
+ itoa_buffer[0] = 0;
+ _ui64toa_s (er->ExceptionCode, itoa_buffer, ITOA_BUFFER_SIZE, 16);
+ dbgp = copy_chars (dbgp, &dbgs, itoa_buffer);
+ dbgp = copy_chars (dbgp, &dbgs, " flags=0x");
+ itoa_buffer[0] = 0;
+ _ui64toa_s (er->ExceptionFlags, itoa_buffer, ITOA_BUFFER_SIZE, 16);
+ dbgp = copy_chars (dbgp, &dbgs, itoa_buffer);
+ dbgp = copy_chars (dbgp, &dbgs, " at 0x");
+ itoa_buffer[0] = 0;
+ _ui64toa_s ((guintptr) er->ExceptionAddress, itoa_buffer, ITOA_BUFFER_SIZE, 16);
+ dbgp = copy_chars (dbgp, &dbgs, itoa_buffer);
+
+ switch (er->ExceptionCode)
+ {
+ case EXCEPTION_ACCESS_VIOLATION:
+ dbgp = copy_chars (dbgp, &dbgs, ". Access violation - attempting to ");
+ if (er->ExceptionInformation[0] == 0)
+ dbgp = copy_chars (dbgp, &dbgs, "read data");
+ else if (er->ExceptionInformation[0] == 1)
+ dbgp = copy_chars (dbgp, &dbgs, "write data");
+ else if (er->ExceptionInformation[0] == 8)
+ dbgp = copy_chars (dbgp, &dbgs, "execute data");
+ else
+ dbgp = copy_chars (dbgp, &dbgs, "do something bad");
+ dbgp = copy_chars (dbgp, &dbgs, " at address 0x");
+ itoa_buffer[0] = 0;
+ _ui64toa_s (er->ExceptionInformation[1], itoa_buffer, ITOA_BUFFER_SIZE, 16);
+ dbgp = copy_chars (dbgp, &dbgs, itoa_buffer);
+ break;
+ case EXCEPTION_IN_PAGE_ERROR:
+ dbgp = copy_chars (dbgp, &dbgs, ". Page access violation - attempting to ");
+ if (er->ExceptionInformation[0] == 0)
+ dbgp = copy_chars (dbgp, &dbgs, "read from an inaccessible page");
+ else if (er->ExceptionInformation[0] == 1)
+ dbgp = copy_chars (dbgp, &dbgs, "write to an inaccessible page");
+ else if (er->ExceptionInformation[0] == 8)
+ dbgp = copy_chars (dbgp, &dbgs, "execute data in page");
+ else
+ dbgp = copy_chars (dbgp, &dbgs, "do something bad with a page");
+ dbgp = copy_chars (dbgp, &dbgs, " at address 0x");
+ itoa_buffer[0] = 0;
+ _ui64toa_s (er->ExceptionInformation[1], itoa_buffer, ITOA_BUFFER_SIZE, 16);
+ dbgp = copy_chars (dbgp, &dbgs, itoa_buffer);
+ dbgp = copy_chars (dbgp, &dbgs, " with status ");
+ itoa_buffer[0] = 0;
+ _ui64toa_s (er->ExceptionInformation[2], itoa_buffer, ITOA_BUFFER_SIZE, 16);
+ dbgp = copy_chars (dbgp, &dbgs, itoa_buffer);
+ break;
+ default:
+ break;
+ }
- CloseHandle (event);
+ dbgp = copy_chars (dbgp, &dbgs, "\n");
+ OutputDebugStringA (debug_string);
+ }
/* Now the debugger is present, and we can try
* resuming execution, re-triggering the exception,
@@ -1219,20 +1222,88 @@ g_win32_veh_handler (PEXCEPTION_POINTERS ExceptionInfo)
return EXCEPTION_CONTINUE_SEARCH;
}
+static gsize
+parse_catch_list (const wchar_t *catch_buffer,
+ DWORD *exceptions,
+ gsize num_exceptions)
+{
+ const wchar_t *catch_list = catch_buffer;
+ gsize result = 0;
+ gsize i = 0;
+
+ while (catch_list != NULL &&
+ catch_list[0] != 0)
+ {
+ unsigned long catch_code;
+ wchar_t *end;
+ errno = 0;
+ catch_code = wcstoul (catch_list, &end, 16);
+ if (errno != NO_ERROR)
+ break;
+ catch_list = end;
+ if (catch_list != NULL && catch_list[0] == L',')
+ catch_list++;
+ if (exceptions && i < num_exceptions)
+ exceptions[i++] = catch_code;
+ }
+
+ return result;
+}
+
void
g_crash_handler_win32_init (void)
{
+ wchar_t debugger_env[DEBUGGER_BUFFER_SIZE];
+#define CATCH_BUFFER_SIZE 1024
+ wchar_t catch_buffer[CATCH_BUFFER_SIZE];
+ SECURITY_ATTRIBUTES sa;
+
if (WinVEH_handle != NULL)
return;
/* Do not register an exception handler if we're not supposed to catch any
* exceptions. Exception handlers are considered dangerous to use, and can
* break advanced exception handling such as in CLRs like C# or other managed
- * code. See: https://blogs.msdn.microsoft.com/jmstall/2006/05/24/beware-of-the-vectored-exception-handler-and-managed-code/
+ * code. See: http://www.windows-tech.info/13/785f590867bd6316.php
*/
- if (g_getenv ("G_DEBUGGER") == NULL && g_getenv("G_VEH_CATCH") == NULL)
+ debugger_env[0] = 0;
+ if (!GetEnvironmentVariableW (L"G_DEBUGGER", debugger_env, DEBUGGER_BUFFER_SIZE))
return;
+ /* Create an inheritable event */
+ memset (&sa, 0, sizeof (sa));
+ sa.nLength = sizeof (sa);
+ sa.bInheritHandle = TRUE;
+ debugger_wakeup_event = CreateEvent (&sa, FALSE, FALSE, NULL);
+
+ /* Put process ID and event handle into debugger commandline */
+ if (!_g_win32_subst_pid_and_event_w (debugger, G_N_ELEMENTS (debugger),
+ debugger_env, GetCurrentProcessId (),
+ (guintptr) debugger_wakeup_event))
+ {
+ CloseHandle (debugger_wakeup_event);
+ debugger_wakeup_event = 0;
+ debugger[0] = 0;
+ return;
+ }
+ debugger[MAX_PATH] = L'\0';
+
+ catch_buffer[0] = 0;
+ if (GetEnvironmentVariableW (L"G_VEH_CATCH", catch_buffer, CATCH_BUFFER_SIZE))
+ {
+ number_of_exceptions_to_catch = parse_catch_list (catch_buffer, NULL, 0);
+ if (number_of_exceptions_to_catch > 0)
+ {
+ exceptions_to_catch = g_new0 (DWORD, number_of_exceptions_to_catch);
+ parse_catch_list (catch_buffer, exceptions_to_catch, number_of_exceptions_to_catch);
+ }
+ }
+
+ if (GetEnvironmentVariableW (L"G_DEBUGGER_OLD_CONSOLE", (wchar_t *) &debugger_spawn_flags, 1))
+ debugger_spawn_flags = 0;
+ else
+ debugger_spawn_flags = CREATE_NEW_CONSOLE;
+
WinVEH_handle = AddVectoredExceptionHandler (0, &g_win32_veh_handler);
}
diff --git a/glib/tests/win32.c b/glib/tests/win32.c
index 97e5887cf..121997311 100644
--- a/glib/tests/win32.c
+++ b/glib/tests/win32.c
@@ -33,34 +33,40 @@ static char *argv0 = NULL;
static void
test_subst_pid_and_event (void)
{
- const gchar not_enough[] = "too long when %e and %p are substituted";
- gchar debugger_3[3];
- gchar debugger_not_enough[G_N_ELEMENTS (not_enough)];
- gchar debugger_enough[G_N_ELEMENTS (not_enough) + 1];
- gchar debugger_big[65535] = {0};
+ const wchar_t not_enough[] = L"too long when %e and %p are substituted";
+ wchar_t debugger_3[3];
+ wchar_t debugger_not_enough[G_N_ELEMENTS (not_enough)];
+ wchar_t debugger_enough[G_N_ELEMENTS (not_enough) + 1];
+ char *debugger_enough_utf8;
+ wchar_t debugger_big[65535] = {0};
+ char *debugger_big_utf8;
gchar *output;
guintptr be = (guintptr) 0xFFFFFFFF;
DWORD bp = G_MAXSIZE;
/* %f is not valid */
- g_assert_false (_g_win32_subst_pid_and_event (debugger_3, G_N_ELEMENTS (debugger_3),
- "%f", 0, 0));
+ g_assert_false (_g_win32_subst_pid_and_event_w (debugger_3, G_N_ELEMENTS (debugger_3),
+ L"%f", 0, 0));
- g_assert_false (_g_win32_subst_pid_and_event (debugger_3, G_N_ELEMENTS (debugger_3),
- "string longer than 10", 0, 0));
+ g_assert_false (_g_win32_subst_pid_and_event_w (debugger_3, G_N_ELEMENTS (debugger_3),
+ L"string longer than 10", 0, 0));
/* 200 is longer than %e, so the string doesn't fit by 1 byte */
- g_assert_false (_g_win32_subst_pid_and_event (debugger_not_enough, G_N_ELEMENTS (debugger_not_enough),
- "too long when %e and %p are substituted", 10, 200));
+ g_assert_false (_g_win32_subst_pid_and_event_w (debugger_not_enough, G_N_ELEMENTS (debugger_not_enough),
+ not_enough, 10, 200));
/* This should fit */
- g_assert_true (_g_win32_subst_pid_and_event (debugger_enough, G_N_ELEMENTS (debugger_enough),
- "too long when %e and %p are substituted", 10, 200));
- g_assert_cmpstr (debugger_enough, ==, "too long when 200 and 10 are substituted");
-
- g_assert_true (_g_win32_subst_pid_and_event (debugger_big, G_N_ELEMENTS (debugger_big),
- "multipl%e big %e %entries and %pids are %provided here", bp, be));
+ g_assert_true (_g_win32_subst_pid_and_event_w (debugger_enough, G_N_ELEMENTS (debugger_enough),
+ not_enough, 10, 200));
+ debugger_enough_utf8 = g_utf16_to_utf8 (debugger_enough, -1, NULL, NULL, NULL);
+ g_assert_cmpstr (debugger_enough_utf8, ==, "too long when 200 and 10 are substituted");
+ g_free (debugger_enough_utf8);
+
+ g_assert_true (_g_win32_subst_pid_and_event_w (debugger_big, G_N_ELEMENTS (debugger_big),
+ L"multipl%e big %e %entries and %pids are %provided here", bp, be));
+ debugger_big_utf8 = g_utf16_to_utf8 (debugger_big, -1, NULL, NULL, NULL);
output = g_strdup_printf ("multipl%llu big %llu %lluntries and %luids are %lurovided here", (guint64) be, (guint64) be, (guint64) be, bp, bp);
- g_assert_cmpstr (debugger_big, ==, output);
+ g_assert_cmpstr (debugger_big_utf8, ==, output);
+ g_free (debugger_big_utf8);
g_free (output);
}
@@ -95,7 +101,6 @@ test_veh_crash_access_violation (void)
/* Run a test that crashes */
g_test_trap_subprocess ("/win32/subprocess/access_violation", 0, 0);
g_test_trap_assert_failed ();
- g_test_trap_assert_stderr ("Exception code=0xc0000005*");
}
static void
@@ -105,21 +110,11 @@ test_veh_crash_illegal_instruction (void)
/* Run a test that crashes */
g_test_trap_subprocess ("/win32/subprocess/illegal_instruction", 0, 0);
g_test_trap_assert_failed ();
- g_test_trap_assert_stderr ("Exception code=0xc000001d*");
}
static void
test_veh_debug (void)
{
- /* Run a test that crashes and runs a debugger */
- g_test_trap_subprocess ("/win32/subprocess/debuggee", 0, 0);
- g_test_trap_assert_failed ();
- g_test_trap_assert_stderr ("Exception code=0xc0000005*Debugger invoked, attaching to*");
-}
-
-static void
-test_veh_debuggee (void)
-{
/* Set up a debugger to be run on crash */
gchar *command = g_strdup_printf ("%s %s", argv0, "%p %e");
g_setenv ("G_DEBUGGER", command, TRUE);
@@ -129,6 +124,15 @@ test_veh_debuggee (void)
*/
g_setenv ("G_DEBUGGER_OLD_CONSOLE", "1", TRUE);
g_free (command);
+ /* Run a test that crashes and runs a debugger */
+ g_test_trap_subprocess ("/win32/subprocess/debuggee", 0, 0);
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("Debugger invoked, attaching to*");
+}
+
+static void
+test_veh_debuggee (void)
+{
/* Crash */
test_access_violation ();
}