mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-27 07:56:14 +01:00
Merge branch 'exceptions' into 'master'
Basic W32 exception handling for glib See merge request GNOME/glib!582
This commit is contained in:
commit
a73c9411f9
@ -256,6 +256,69 @@ How to run and debug your GLib application
|
||||
</para>
|
||||
</formalpara>
|
||||
|
||||
<formalpara id="G_DEBUGGER">
|
||||
<title><envar>G_DEBUGGER</envar></title>
|
||||
|
||||
<para>
|
||||
When running on Windows, if set to a non-empty string, GLib will
|
||||
try to interpret the contents of this environment variable as
|
||||
a command line to a debugger, and run it if the process crashes.
|
||||
The debugger command line should contain <literal>%p</literal> and <literal>%e</literal> substitution
|
||||
tokens, which GLib will replace with the process ID of the crashing
|
||||
process and a handle to an event that the debugger should signal
|
||||
to let GLib know that the debugger successfully attached to the
|
||||
process. If <literal>%e</literal> is absent, or if the debugger is not able to
|
||||
signal events, GLib will resume execution after 60 seconds.
|
||||
If <literal>%p</literal> is absent, the debugger won't know which process to attach to,
|
||||
and GLib will also resume execution after 60 seconds.
|
||||
</para>
|
||||
<para>
|
||||
Additionally, even if <envar>G_DEBUGGER</envar> is not set, GLib would still
|
||||
try to print basic exception information (code and address) into
|
||||
stderr.
|
||||
</para>
|
||||
<para>
|
||||
By default the debugger gets a new console allocated for it.
|
||||
Set the <envar>G_DEBUGGER_OLD_CONSOLE</envar> environment variable to any
|
||||
non-empty string to make the debugger inherit the console of
|
||||
the crashing process. Normally this is only used by the GLib
|
||||
testsuite.
|
||||
</para>
|
||||
<para>
|
||||
The exception handler is written with the aim of making it as
|
||||
simple as possible, to minimize the risk of it invoking
|
||||
buggy functions or running buggy code, which would result
|
||||
in exceptions being raised recursively. Because of that
|
||||
it lacks most of the amenities that one would expect of GLib.
|
||||
Namely, it does not support Unicode, so it is highly advisable
|
||||
to only use ASCII characters in <envar>G_DEBUGGER</envar>.
|
||||
</para>
|
||||
<para>
|
||||
See also <link linkend="G_VEH_CATCH"><envar>G_VEH_CATCH</envar></link>.
|
||||
</para>
|
||||
</formalpara>
|
||||
|
||||
<formalpara id="G_VEH_CATCH">
|
||||
<title><envar>G_VEH_CATCH</envar></title>
|
||||
|
||||
<para>
|
||||
Catching some exceptions can break the program, since Windows
|
||||
will sometimes use exceptions for execution flow control and
|
||||
other purposes other than signalling a crash.
|
||||
</para>
|
||||
<para>
|
||||
The <envar>G_VEH_CATCH</envar> environment variable augments
|
||||
<ulink url="https://docs.microsoft.com/en-us/windows/desktop/debug/vectored-exception-handling">Vectored Exception Handling</ulink>
|
||||
on Windows (see <link linkend="G_DEBUGGER"><envar>G_DEBUGGER</envar></link>), allowing GLib to catch more
|
||||
exceptions. Set this variable to a comma-separated list of
|
||||
hexademical exception codes that should additionally be caught.
|
||||
</para>
|
||||
<para>
|
||||
By default GLib will only catch Access Violation, Stack Overflow and
|
||||
Illegal Instruction <ulink url="https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_exception_record">exceptions</ulink>.
|
||||
</para>
|
||||
</formalpara>
|
||||
|
||||
</refsect2>
|
||||
|
||||
<refsect2 id="setlocale">
|
||||
|
@ -125,6 +125,10 @@ volatile gboolean glib_on_error_halt = TRUE;
|
||||
* If "[P]roceed" is selected, the function returns.
|
||||
*
|
||||
* This function may cause different actions on non-UNIX platforms.
|
||||
*
|
||||
* On Windows consider using the `G_DEBUGGER` environment
|
||||
* variable (see [Running GLib Applications](glib-running.html)) and
|
||||
* calling g_on_error_stack_trace() instead.
|
||||
*/
|
||||
void
|
||||
g_on_error_query (const gchar *prg_name)
|
||||
@ -207,6 +211,12 @@ g_on_error_query (const gchar *prg_name)
|
||||
* gdk_init().
|
||||
*
|
||||
* This function may cause different actions on non-UNIX platforms.
|
||||
*
|
||||
* When running on Windows, this function is *not* called by
|
||||
* g_on_error_query(). If called directly, it will raise an
|
||||
* exception, which will crash the program. If the `G_DEBUGGER` environment
|
||||
* variable is set, a debugger will be invoked to attach and
|
||||
* handle that exception (see [Running GLib Applications](glib-running.html)).
|
||||
*/
|
||||
void
|
||||
g_on_error_stack_trace (const gchar *prg_name)
|
||||
|
@ -286,6 +286,7 @@ DllMain (HINSTANCE hinstDLL,
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
glib_dll = hinstDLL;
|
||||
g_crash_handler_win32_init ();
|
||||
g_clock_win32_init ();
|
||||
#ifdef THREADS_WIN32
|
||||
g_thread_win32_init ();
|
||||
@ -306,6 +307,7 @@ DllMain (HINSTANCE hinstDLL,
|
||||
if (lpvReserved == NULL)
|
||||
g_thread_win32_process_detach ();
|
||||
#endif
|
||||
g_crash_handler_win32_deinit ();
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -36,6 +36,8 @@ void g_thread_win32_thread_detach (void);
|
||||
void g_thread_win32_init (void);
|
||||
void g_console_win32_init (void);
|
||||
void g_clock_win32_init (void);
|
||||
void g_crash_handler_win32_init (void);
|
||||
void g_crash_handler_win32_deinit (void);
|
||||
extern HMODULE glib_dll;
|
||||
#endif
|
||||
|
||||
|
77
glib/gwin32-private.c
Normal file
77
glib/gwin32-private.c
Normal file
@ -0,0 +1,77 @@
|
||||
/* gwin32-private.c - private glib functions for gwin32.c
|
||||
*
|
||||
* Copyright 2019 Руслан Ижбулатов
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* Copy @cmdline into @debugger, and substitute @pid for `%p`
|
||||
* and @event for `%e`.
|
||||
* If @debugger_size (in bytes) 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)
|
||||
{
|
||||
gsize i = 0, dbg_i = 0;
|
||||
/* These are integers, and they can't be longer than 20 characters
|
||||
* even when they are 64-bit and in decimal notation.
|
||||
* Use 30 just to be sure.
|
||||
*/
|
||||
#define STR_BUFFER_SIZE 30
|
||||
char pid_str[STR_BUFFER_SIZE] = {0};
|
||||
gsize pid_str_len;
|
||||
char event_str[STR_BUFFER_SIZE] = {0};
|
||||
gsize event_str_len;
|
||||
#undef STR_BUFFER_SIZE
|
||||
snprintf (pid_str, G_N_ELEMENTS (pid_str), "%lu", pid);
|
||||
pid_str[G_N_ELEMENTS (pid_str) - 1] = 0;
|
||||
pid_str_len = strlen (pid_str);
|
||||
snprintf (event_str, G_N_ELEMENTS (pid_str), "%Iu", event);
|
||||
event_str[G_N_ELEMENTS (pid_str) - 1] = 0;
|
||||
event_str_len = strlen (event_str);
|
||||
|
||||
while (cmdline[i] != 0 && dbg_i < debugger_size)
|
||||
{
|
||||
if (cmdline[i] != '%')
|
||||
debugger[dbg_i++] = cmdline[i++];
|
||||
else if (cmdline[i + 1] == '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')
|
||||
{
|
||||
gsize j = 0;
|
||||
while (j < event_str_len && dbg_i < debugger_size)
|
||||
debugger[dbg_i++] = event_str[j++];
|
||||
i += 2;
|
||||
}
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
if (dbg_i < debugger_size)
|
||||
debugger[dbg_i] = 0;
|
||||
else
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
214
glib/gwin32.c
214
glib/gwin32.c
@ -1018,4 +1018,218 @@ g_console_win32_init (void)
|
||||
}
|
||||
}
|
||||
|
||||
/* This is a handle to the Vectored Exception Handler that
|
||||
* we install on library initialization. If installed correctly,
|
||||
* it will be non-NULL. Only used to later de-install the handler
|
||||
* on library de-initialization.
|
||||
*/
|
||||
static void *WinVEH_handle = NULL;
|
||||
|
||||
#include "gwin32-private.c"
|
||||
|
||||
/**
|
||||
* 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
|
||||
* anyway). If it is not, runs a debugger from G_DEBUGGER env var,
|
||||
* substituting first %p in it for PID, and the first %e for the event handle -
|
||||
* that event should be set once the debugger attaches itself (otherwise the
|
||||
* only way out of WaitForSingleObject() is to time out after 1 minute).
|
||||
* For example, G_DEBUGGER can be set to the following command:
|
||||
* ```
|
||||
* gdb.exe -ex "attach %p" -ex "signal-event %e" -ex "bt" -ex "c"
|
||||
* ```
|
||||
* This will make GDB attach to the process, signal the event (GDB must be
|
||||
* recent enough for the signal-event command to be available),
|
||||
* show the backtrace and resume execution, which should make it catch
|
||||
* the exception when Windows re-raises it again.
|
||||
* The command line can't be longer than MAX_PATH (260 characters).
|
||||
*
|
||||
* This function will only stop (and run a debugger) on the following exceptions:
|
||||
* * EXCEPTION_ACCESS_VIOLATION
|
||||
* * EXCEPTION_STACK_OVERFLOW
|
||||
* * EXCEPTION_ILLEGAL_INSTRUCTION
|
||||
* To make it stop at other exceptions one should set the G_VEH_CATCH
|
||||
* environment variable to a list of comma-separated hexademical numbers,
|
||||
* where each number is the code of an exception that should be caught.
|
||||
* This is done to prevent GLib from breaking when Windows uses
|
||||
* exceptions to shuttle information (SetThreadName(), OutputDebugString())
|
||||
* or for control flow.
|
||||
*
|
||||
* This function deliberately avoids calling any GLib code.
|
||||
*/
|
||||
static LONG __stdcall
|
||||
g_win32_veh_handler (PEXCEPTION_POINTERS ExceptionInfo)
|
||||
{
|
||||
EXCEPTION_RECORD *er;
|
||||
char debugger[MAX_PATH + 1];
|
||||
const char *debugger_env = NULL;
|
||||
const char *catch_list;
|
||||
gboolean catch = FALSE;
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
HANDLE event;
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
|
||||
if (ExceptionInfo == NULL ||
|
||||
ExceptionInfo->ExceptionRecord == NULL)
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
|
||||
er = ExceptionInfo->ExceptionRecord;
|
||||
|
||||
switch (er->ExceptionCode)
|
||||
{
|
||||
case EXCEPTION_ACCESS_VIOLATION:
|
||||
case EXCEPTION_STACK_OVERFLOW:
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||||
case EXCEPTION_BREAKPOINT: /* DebugBreak() raises this */
|
||||
break;
|
||||
default:
|
||||
catch_list = getenv ("G_VEH_CATCH");
|
||||
|
||||
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;
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
if (IsDebuggerPresent ())
|
||||
{
|
||||
/* This shouldn't happen, but still try to
|
||||
* avoid recursion with EXCEPTION_BREAKPOINT and
|
||||
* DebugBreak().
|
||||
*/
|
||||
if (er->ExceptionCode != EXCEPTION_BREAKPOINT)
|
||||
DebugBreak ();
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
fprintf (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 (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 (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 (stderr, "\n");
|
||||
break;
|
||||
}
|
||||
|
||||
fflush (stderr);
|
||||
|
||||
debugger_env = 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;
|
||||
}
|
||||
|
||||
/* Run the debugger */
|
||||
debugger[MAX_PATH] = '\0';
|
||||
if (0 != CreateProcessA (NULL,
|
||||
debugger,
|
||||
NULL,
|
||||
NULL,
|
||||
TRUE,
|
||||
getenv ("G_DEBUGGER_OLD_CONSOLE") != NULL ? 0 : CREATE_NEW_CONSOLE,
|
||||
NULL,
|
||||
NULL,
|
||||
&si,
|
||||
&pi))
|
||||
{
|
||||
CloseHandle (pi.hProcess);
|
||||
CloseHandle (pi.hThread);
|
||||
/* If successful, wait for 60 seconds on the event
|
||||
* we passed. The debugger should signal that event.
|
||||
* 60 second limit is here to prevent us from hanging
|
||||
* up forever in case the debugger does not support
|
||||
* event signalling.
|
||||
*/
|
||||
WaitForSingleObject (event, 60000);
|
||||
}
|
||||
|
||||
CloseHandle (event);
|
||||
|
||||
/* Now the debugger is present, and we can try
|
||||
* resuming execution, re-triggering the exception,
|
||||
* which will be caught by debugger this time around.
|
||||
*/
|
||||
if (IsDebuggerPresent ())
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
void
|
||||
g_crash_handler_win32_init (void)
|
||||
{
|
||||
if (WinVEH_handle != NULL)
|
||||
return;
|
||||
|
||||
WinVEH_handle = AddVectoredExceptionHandler (0, &g_win32_veh_handler);
|
||||
}
|
||||
|
||||
void
|
||||
g_crash_handler_win32_deinit (void)
|
||||
{
|
||||
if (WinVEH_handle != NULL)
|
||||
RemoveVectoredExceptionHandler (WinVEH_handle);
|
||||
|
||||
WinVEH_handle = NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -140,6 +140,9 @@ if host_machine.system() == 'windows'
|
||||
},
|
||||
}
|
||||
endif
|
||||
glib_tests += {
|
||||
'win32' : {},
|
||||
}
|
||||
else
|
||||
glib_tests += {
|
||||
'include' : {},
|
||||
|
173
glib/tests/win32.c
Normal file
173
glib/tests/win32.c
Normal file
@ -0,0 +1,173 @@
|
||||
/* Unit test for VEH on Windows
|
||||
* Copyright (C) 2019 Руслан Ижбулатов
|
||||
*
|
||||
* This work is provided "as is"; redistribution and modification
|
||||
* in whole or in part, in any medium, physical or electronic is
|
||||
* permitted without restriction.
|
||||
*
|
||||
* This work 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.
|
||||
*
|
||||
* In no event shall the authors or contributors be liable for any
|
||||
* direct, indirect, incidental, special, exemplary, or consequential
|
||||
* damages (including, but not limited to, procurement of substitute
|
||||
* goods or services; loss of use, data, or profits; or business
|
||||
* interruption) however caused and on any theory of liability, whether
|
||||
* in contract, strict liability, or tort (including negligence or
|
||||
* otherwise) arising in any way out of the use of this software, even
|
||||
* if advised of the possibility of such damage.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <gprintf.h>
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
|
||||
static char *argv0 = NULL;
|
||||
|
||||
#include "../gwin32-private.c"
|
||||
|
||||
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};
|
||||
gchar *output;
|
||||
DWORD be = 0xFFFFFFFF;
|
||||
guintptr bp = (guintptr) 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 (debugger_3, G_N_ELEMENTS (debugger_3),
|
||||
"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));
|
||||
|
||||
/* 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));
|
||||
output = g_strdup_printf ("multipl%lu big %lu %luntries and %lluids are %llurovided here", be, be, be, (guint64) bp, (guint64) bp);
|
||||
g_assert_cmpstr (debugger_big, ==, output);
|
||||
g_free (output);
|
||||
}
|
||||
|
||||
/* Crash with access violation */
|
||||
static void
|
||||
test_access_violation (void)
|
||||
{
|
||||
int *integer = NULL;
|
||||
/* Use SEM_NOGPFAULTERRORBOX to prevent an error dialog
|
||||
* from being shown.
|
||||
*/
|
||||
DWORD dwMode = SetErrorMode (SEM_NOGPFAULTERRORBOX);
|
||||
SetErrorMode (dwMode | SEM_NOGPFAULTERRORBOX);
|
||||
*integer = 1;
|
||||
SetErrorMode (dwMode);
|
||||
}
|
||||
|
||||
/* Crash with illegal instruction */
|
||||
static void
|
||||
test_illegal_instruction (void)
|
||||
{
|
||||
DWORD dwMode = SetErrorMode (SEM_NOGPFAULTERRORBOX);
|
||||
SetErrorMode (dwMode | SEM_NOGPFAULTERRORBOX);
|
||||
RaiseException (EXCEPTION_ILLEGAL_INSTRUCTION, 0, 0, NULL);
|
||||
SetErrorMode (dwMode);
|
||||
}
|
||||
|
||||
static void
|
||||
test_veh_crash_access_violation (void)
|
||||
{
|
||||
g_unsetenv ("G_DEBUGGER");
|
||||
/* 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
|
||||
test_veh_crash_illegal_instruction (void)
|
||||
{
|
||||
g_unsetenv ("G_DEBUGGER");
|
||||
/* 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/debugee", 0, 0);
|
||||
g_test_trap_assert_failed ();
|
||||
g_test_trap_assert_stderr ("Exception code=0xc0000005*Debugger invoked, attaching to*");
|
||||
}
|
||||
|
||||
static void
|
||||
test_veh_debugee (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);
|
||||
/* Because the "debugger" here is not really a debugger,
|
||||
* it can't write into stderr of this process, unless
|
||||
* we allow it to inherit our stderr.
|
||||
*/
|
||||
g_setenv ("G_DEBUGGER_OLD_CONSOLE", "1", TRUE);
|
||||
g_free (command);
|
||||
/* Crash */
|
||||
test_access_violation ();
|
||||
}
|
||||
|
||||
static void
|
||||
veh_debugger (int argc, char *argv[])
|
||||
{
|
||||
char *end;
|
||||
DWORD pid = strtoul (argv[1], &end, 10);
|
||||
guintptr event = (guintptr) _strtoui64 (argv[2], &end, 10);
|
||||
/* Unfreeze the debugee and announce ourselves */
|
||||
SetEvent ((HANDLE) event);
|
||||
CloseHandle ((HANDLE) event);
|
||||
g_fprintf (stderr, "Debugger invoked, attaching to %lu and signalling %" G_GUINTPTR_FORMAT, pid, event);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
argv0 = argv[0];
|
||||
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
if (argc > 2)
|
||||
{
|
||||
veh_debugger (argc, argv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
g_test_add_func ("/win32/substitute-pid-and-event", test_subst_pid_and_event);
|
||||
|
||||
g_test_add_func ("/win32/veh/access_violation", test_veh_crash_access_violation);
|
||||
g_test_add_func ("/win32/veh/illegal_instruction", test_veh_crash_illegal_instruction);
|
||||
g_test_add_func ("/win32/veh/debug", test_veh_debug);
|
||||
|
||||
g_test_add_func ("/win32/subprocess/debugee", test_veh_debugee);
|
||||
g_test_add_func ("/win32/subprocess/access_violation", test_access_violation);
|
||||
g_test_add_func ("/win32/subprocess/illegal_instruction", test_illegal_instruction);
|
||||
|
||||
return g_test_run();
|
||||
}
|
Loading…
Reference in New Issue
Block a user