except.c 18.7 KB
Newer Older
Alexandre Julliard's avatar
Alexandre Julliard committed
1 2 3 4
/*
 * Win32 exception functions
 *
 * Copyright (c) 1996 Onno Hovers, (onno@stack.urc.tue.nl)
5
 * Copyright (c) 1999 Alexandre Julliard
Alexandre Julliard's avatar
Alexandre Julliard committed
6
 *
7 8 9 10 11 12 13 14 15 16 17 18
 * 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, write to the Free Software
19
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20
 *
Alexandre Julliard's avatar
Alexandre Julliard committed
21 22 23 24 25
 * Notes:
 *  What really happens behind the scenes of those new
 *  __try{...}__except(..){....}  and
 *  __try{...}__finally{...}
 *  statements is simply not documented by Microsoft. There could be different
26 27 28
 *  reasons for this:
 *  One reason could be that they try to hide the fact that exception
 *  handling in Win32 looks almost the same as in OS/2 2.x.
Alexandre Julliard's avatar
Alexandre Julliard committed
29
 *  Another reason could be that Microsoft does not want others to write
30
 *  binary compatible implementations of the Win32 API (like us).
Alexandre Julliard's avatar
Alexandre Julliard committed
31
 *
32 33 34
 *  Whatever the reason, THIS SUCKS!! Ensuring portability or future
 *  compatibility may be valid reasons to keep some things undocumented.
 *  But exception handling is so basic to Win32 that it should be
Alexandre Julliard's avatar
Alexandre Julliard committed
35 36 37
 *  documented!
 *
 */
38 39
#include "config.h"
#include "wine/port.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
40

41
#include <stdarg.h>
42
#include <stdio.h>
43
#include "ntstatus.h"
44
#define WIN32_NO_STATUS
45
#include "windef.h"
46
#include "winbase.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
47
#include "winerror.h"
48
#include "winternl.h"
49 50
#include "wingdi.h"
#include "winuser.h"
51
#include "wine/exception.h"
52
#include "wine/library.h"
53
#include "excpt.h"
54
#include "wine/unicode.h"
55
#include "wine/debug.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
56

57
WINE_DEFAULT_DEBUG_CHANNEL(seh);
Alexandre Julliard's avatar
Alexandre Julliard committed
58

59 60
static PTOP_LEVEL_EXCEPTION_FILTER top_filter;

61 62
typedef INT (WINAPI *MessageBoxA_funcptr)(HWND,LPCSTR,LPCSTR,UINT);
typedef INT (WINAPI *MessageBoxW_funcptr)(HWND,LPCWSTR,LPCWSTR,UINT);
Alexandre Julliard's avatar
Alexandre Julliard committed
63 64

/*******************************************************************
65
 *         RaiseException  (KERNEL32.@)
Alexandre Julliard's avatar
Alexandre Julliard committed
66
 */
67
void WINAPI RaiseException( DWORD code, DWORD flags, DWORD nbargs, const ULONG_PTR *args )
Alexandre Julliard's avatar
Alexandre Julliard committed
68
{
69
    EXCEPTION_RECORD record;
Alexandre Julliard's avatar
Alexandre Julliard committed
70

71 72
    /* Compose an exception record */

73 74 75 76 77
    record.ExceptionCode    = code;
    record.ExceptionFlags   = flags & EH_NONCONTINUABLE;
    record.ExceptionRecord  = NULL;
    record.ExceptionAddress = RaiseException;
    if (nbargs && args)
Alexandre Julliard's avatar
Alexandre Julliard committed
78
    {
79 80 81 82 83
        if (nbargs > EXCEPTION_MAXIMUM_PARAMETERS) nbargs = EXCEPTION_MAXIMUM_PARAMETERS;
        record.NumberParameters = nbargs;
        memcpy( record.ExceptionInformation, args, nbargs * sizeof(*args) );
    }
    else record.NumberParameters = 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
84

85
    RtlRaiseException( &record );
Alexandre Julliard's avatar
Alexandre Julliard committed
86 87
}

Alexandre Julliard's avatar
Alexandre Julliard committed
88

89 90 91
/*******************************************************************
 *         format_exception_msg
 */
92
static int format_exception_msg( const EXCEPTION_POINTERS *ptr, char *buffer, int size )
93 94
{
    const EXCEPTION_RECORD *rec = ptr->ExceptionRecord;
95
    int len,len2;
96 97 98 99

    switch(rec->ExceptionCode)
    {
    case EXCEPTION_INT_DIVIDE_BY_ZERO:
100
        len = snprintf( buffer, size, "Unhandled division by zero" );
101 102
        break;
    case EXCEPTION_INT_OVERFLOW:
103
        len = snprintf( buffer, size, "Unhandled overflow" );
104 105
        break;
    case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
106
        len = snprintf( buffer, size, "Unhandled array bounds" );
107 108
        break;
    case EXCEPTION_ILLEGAL_INSTRUCTION:
109
        len = snprintf( buffer, size, "Unhandled illegal instruction" );
110 111
        break;
    case EXCEPTION_STACK_OVERFLOW:
112
        len = snprintf( buffer, size, "Unhandled stack overflow" );
113 114
        break;
    case EXCEPTION_PRIV_INSTRUCTION:
Andreas Mohr's avatar
Andreas Mohr committed
115
        len = snprintf( buffer, size, "Unhandled privileged instruction" );
116 117 118
        break;
    case EXCEPTION_ACCESS_VIOLATION:
        if (rec->NumberParameters == 2)
119
            len = snprintf( buffer, size, "Unhandled page fault on %s access to 0x%08lx",
120 121 122
                            rec->ExceptionInformation[0] == EXCEPTION_WRITE_FAULT ? "write" :
                            rec->ExceptionInformation[0] == EXCEPTION_EXECUTE_FAULT ? "execute" : "read",
                            rec->ExceptionInformation[1]);
123
        else
124
            len = snprintf( buffer, size, "Unhandled page fault");
125 126
        break;
    case EXCEPTION_DATATYPE_MISALIGNMENT:
127
        len = snprintf( buffer, size, "Unhandled alignment" );
128 129
        break;
    case CONTROL_C_EXIT:
130
        len = snprintf( buffer, size, "Unhandled ^C");
131
        break;
132
    case STATUS_POSSIBLE_DEADLOCK:
133
        len = snprintf( buffer, size, "Critical section %08lx wait failed",
134 135 136
                 rec->ExceptionInformation[0]);
        break;
    case EXCEPTION_WINE_STUB:
137 138 139 140 141 142
        if (HIWORD(rec->ExceptionInformation[1]))
            len = snprintf( buffer, size, "Unimplemented function %s.%s called",
                            (char *)rec->ExceptionInformation[0], (char *)rec->ExceptionInformation[1] );
        else
            len = snprintf( buffer, size, "Unimplemented function %s.%ld called",
                            (char *)rec->ExceptionInformation[0], rec->ExceptionInformation[1] );
143
        break;
144 145 146
    case EXCEPTION_WINE_ASSERTION:
        len = snprintf( buffer, size, "Assertion failed" );
        break;
147
    case EXCEPTION_VM86_INTx:
148
        len = snprintf( buffer, size, "Unhandled interrupt %02lx in vm86 mode",
149 150 151
                 rec->ExceptionInformation[0]);
        break;
    case EXCEPTION_VM86_STI:
152
        len = snprintf( buffer, size, "Unhandled sti in vm86 mode");
153 154
        break;
    case EXCEPTION_VM86_PICRETURN:
155
        len = snprintf( buffer, size, "Unhandled PIC return in vm86 mode");
156 157
        break;
    default:
158
        len = snprintf( buffer, size, "Unhandled exception 0x%08lx", rec->ExceptionCode);
159 160
        break;
    }
161 162
    if ((len<0) || (len>=size))
        return -1;
163
#ifdef __i386__
164
    if (ptr->ContextRecord->SegCs != wine_get_cs())
165
        len2 = snprintf(buffer+len, size-len, " at address 0x%04lx:0x%08lx",
166 167
                        ptr->ContextRecord->SegCs,
                        (DWORD)ptr->ExceptionRecord->ExceptionAddress);
168 169
    else
#endif
170
        len2 = snprintf(buffer+len, size-len, " at address %p",
171
                        ptr->ExceptionRecord->ExceptionAddress);
172 173 174
    if ((len2<0) || (len>=size-len))
        return -1;
    return len+len2;
175 176 177
}


178 179 180 181
/******************************************************************
 *		start_debugger
 *
 * Does the effective debugger startup according to 'format'
Alexandre Julliard's avatar
Alexandre Julliard committed
182
 */
183
static BOOL	start_debugger(PEXCEPTION_POINTERS epointers, HANDLE hEvent)
Alexandre Julliard's avatar
Alexandre Julliard committed
184
{
185 186
    OBJECT_ATTRIBUTES attr;
    UNICODE_STRING nameW;
187
    char *cmdline, *env, *p;
188
    HANDLE		hDbgConf;
189
    DWORD		bAuto = FALSE;
190 191
    PROCESS_INFORMATION	info;
    STARTUPINFOA	startup;
192 193
    char*		format = NULL;
    BOOL		ret = FALSE;
194
    char buffer[256];
Alexandre Julliard's avatar
Alexandre Julliard committed
195

196 197 198 199 200 201 202 203 204
    static const WCHAR AeDebugW[] = {'M','a','c','h','i','n','e','\\',
                                     'S','o','f','t','w','a','r','e','\\',
                                     'M','i','c','r','o','s','o','f','t','\\',
                                     'W','i','n','d','o','w','s',' ','N','T','\\',
                                     'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\',
                                     'A','e','D','e','b','u','g',0};
    static const WCHAR DebuggerW[] = {'D','e','b','u','g','g','e','r',0};
    static const WCHAR AutoW[] = {'A','u','t','o',0};

205 206
    format_exception_msg( epointers, buffer, sizeof(buffer) );
    MESSAGE("wine: %s (thread %04lx), starting debugger...\n", buffer, GetCurrentThreadId());
Andreas Mohr's avatar
Andreas Mohr committed
207

208 209 210 211 212 213 214
    attr.Length = sizeof(attr);
    attr.RootDirectory = 0;
    attr.ObjectName = &nameW;
    attr.Attributes = 0;
    attr.SecurityDescriptor = NULL;
    attr.SecurityQualityOfService = NULL;
    RtlInitUnicodeString( &nameW, AeDebugW );
215

216 217 218 219
    if (!NtOpenKey( &hDbgConf, KEY_ALL_ACCESS, &attr ))
    {
        char buffer[64];
        KEY_VALUE_PARTIAL_INFORMATION *info;
220
        DWORD format_size = 0;
221

222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
        RtlInitUnicodeString( &nameW, DebuggerW );
        if (NtQueryValueKey( hDbgConf, &nameW, KeyValuePartialInformation,
                             NULL, 0, &format_size ) == STATUS_BUFFER_OVERFLOW)
        {
            char *data = HeapAlloc(GetProcessHeap(), 0, format_size);
            NtQueryValueKey( hDbgConf, &nameW, KeyValuePartialInformation,
                             data, format_size, &format_size );
            info = (KEY_VALUE_PARTIAL_INFORMATION *)data;
            RtlUnicodeToMultiByteSize( &format_size, (WCHAR *)info->Data, info->DataLength );
            format = HeapAlloc( GetProcessHeap(), 0, format_size+1 );
            RtlUnicodeToMultiByteN( format, format_size, NULL,
                                    (WCHAR *)info->Data, info->DataLength );
            format[format_size] = 0;

            if (info->Type == REG_EXPAND_SZ)
            {
                char* tmp;

                /* Expand environment variable references */
                format_size=ExpandEnvironmentStringsA(format,NULL,0);
                tmp=HeapAlloc(GetProcessHeap(), 0, format_size);
                ExpandEnvironmentStringsA(format,tmp,format_size);
                HeapFree(GetProcessHeap(), 0, format);
                format=tmp;
            }
            HeapFree( GetProcessHeap(), 0, data );
        }
249

250 251 252
        RtlInitUnicodeString( &nameW, AutoW );
        if (!NtQueryValueKey( hDbgConf, &nameW, KeyValuePartialInformation,
                              buffer, sizeof(buffer)-sizeof(WCHAR), &format_size ))
253
       {
254 255 256 257 258 259 260 261
           info = (KEY_VALUE_PARTIAL_INFORMATION *)buffer;
           if (info->Type == REG_DWORD) memcpy( &bAuto, info->Data, sizeof(DWORD) );
           else if (info->Type == REG_SZ)
           {
               WCHAR *str = (WCHAR *)info->Data;
               str[info->DataLength/sizeof(WCHAR)] = 0;
               bAuto = atoiW( str );
           }
262
       }
263 264 265
       else bAuto = TRUE;

       NtClose(hDbgConf);
266 267 268 269 270 271 272 273 274 275 276
    }

    if (format)
    {
        cmdline = HeapAlloc(GetProcessHeap(), 0, strlen(format) + 2*20);
        sprintf(cmdline, format, GetCurrentProcessId(), hEvent);
        HeapFree(GetProcessHeap(), 0, format);
    }
    else
    {
        cmdline = HeapAlloc(GetProcessHeap(), 0, 80);
277
        sprintf(cmdline, "winedbg --auto %ld %ld",
278
                GetCurrentProcessId(), (ULONG_PTR)hEvent);
279 280
    }

281 282
    if (!bAuto)
    {
283 284 285 286 287 288
	HMODULE			mod = GetModuleHandleA( "user32.dll" );
	MessageBoxA_funcptr	pMessageBoxA = NULL;

	if (mod) pMessageBoxA = (MessageBoxA_funcptr)GetProcAddress( mod, "MessageBoxA" );
	if (pMessageBoxA)
	{
289
            static const char msg[] = ".\nDo you wish to debug it?";
290
	    char buffer[256];
291 292 293

            format_exception_msg( epointers, buffer, sizeof(buffer)-sizeof(msg) );
            strcat( buffer, msg );
294 295 296
	    if (pMessageBoxA( 0, buffer, "Exception raised", MB_YESNO | MB_ICONHAND ) == IDNO)
	    {
		TRACE("Killing process\n");
297
		goto EXIT;
298 299
	    }
	}
300
    }
301

302
    /* make WINEDEBUG empty in the environment */
303 304 305 306 307
    env = GetEnvironmentStringsA();
    for (p = env; *p; p += strlen(p) + 1)
    {
        if (!memcmp( p, "WINEDEBUG=", sizeof("WINEDEBUG=")-1 ))
        {
308 309
            char *next = p + strlen(p);
            char *end = next + 1;
310
            while (*end) end += strlen(end) + 1;
311
            memmove( p + sizeof("WINEDEBUG=") - 1, next, end + 1 - next );
312 313 314 315
            break;
        }
    }

316 317 318 319 320
    TRACE("Starting debugger %s\n", debugstr_a(cmdline));
    memset(&startup, 0, sizeof(startup));
    startup.cb = sizeof(startup);
    startup.dwFlags = STARTF_USESHOWWINDOW;
    startup.wShowWindow = SW_SHOWNORMAL;
321 322
    ret = CreateProcessA(NULL, cmdline, NULL, NULL, TRUE, 0, env, NULL, &startup, &info);
    FreeEnvironmentStringsA( env );
323 324 325 326 327

    if (ret) WaitForSingleObject(hEvent, INFINITE);  /* wait for debugger to come up... */
    else ERR("Couldn't start debugger (%s) (%ld)\n"
             "Read the Wine Developers Guide on how to set up winedbg or another debugger\n",
             debugstr_a(cmdline), GetLastError());
328
EXIT:
329
    HeapFree(GetProcessHeap(), 0, cmdline);
330
    return ret;
331 332 333 334 335
}

/******************************************************************
 *		start_debugger_atomic
 *
336
 * starts the debugger in an atomic way:
337
 *	- either the debugger is not started and it is started
338 339
 *	- or the debugger has already been started by another thread
 *	- or the debugger couldn't be started
340
 *
341
 * returns TRUE for the two first conditions, FALSE for the last
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
 */
static	int	start_debugger_atomic(PEXCEPTION_POINTERS epointers)
{
    static HANDLE	hRunOnce /* = 0 */;

    if (hRunOnce == 0)
    {
	OBJECT_ATTRIBUTES	attr;
	HANDLE			hEvent;

	attr.Length                   = sizeof(attr);
	attr.RootDirectory            = 0;
	attr.Attributes               = OBJ_INHERIT;
	attr.ObjectName               = NULL;
	attr.SecurityDescriptor       = NULL;
	attr.SecurityQualityOfService = NULL;

359 360
	/* ask for manual reset, so that once the debugger is started,
	 * every thread will know it */
361
	NtCreateEvent( &hEvent, EVENT_ALL_ACCESS, &attr, TRUE, FALSE );
362
	if (InterlockedCompareExchangePointer( (PVOID)&hRunOnce, hEvent, 0 ) == 0)
363 364 365 366 367 368 369 370 371 372 373 374
	{
	    /* ok, our event has been set... we're the winning thread */
	    BOOL	ret = start_debugger( epointers, hRunOnce );
	    DWORD	tmp;

	    if (!ret)
	    {
		/* so that the other threads won't be stuck */
		NtSetEvent( hRunOnce, &tmp );
	    }
	    return ret;
	}
375

376 377 378
	/* someone beat us here... */
	CloseHandle( hEvent );
    }
379

380 381
    /* and wait for the winner to have actually created the debugger */
    WaitForSingleObject( hRunOnce, INFINITE );
382 383 384
    /* in fact, here, we only know that someone has tried to start the debugger,
     * we'll know by reposting the exception if it has actually attached
     * to the current process */
385 386 387 388
    return TRUE;
}


389 390 391 392 393 394 395
/*******************************************************************
 *         check_resource_write
 *
 * Check if the exception is a write attempt to the resource data.
 * If yes, we unprotect the resources to let broken apps continue
 * (Windows does this too).
 */
396
inline static BOOL check_resource_write( void *addr )
397
{
398
    void *rsrc;
399 400 401 402
    DWORD size;
    MEMORY_BASIC_INFORMATION info;

    if (!VirtualQuery( addr, &info, sizeof(info) )) return FALSE;
Eric Pouech's avatar
Eric Pouech committed
403
    if (info.State == MEM_FREE || !(info.Type & MEM_IMAGE)) return FALSE;
404 405 406 407
    if (!(rsrc = RtlImageDirectoryEntryToData( (HMODULE)info.AllocationBase, TRUE,
                                              IMAGE_DIRECTORY_ENTRY_RESOURCE, &size )))
        return FALSE;
    if (addr < rsrc || (char *)addr >= (char *)rsrc + size) return FALSE;
408
    TRACE( "Broken app is writing to the resource data, enabling work-around\n" );
409 410 411 412 413
    VirtualProtect( rsrc, size, PAGE_WRITECOPY, NULL );
    return TRUE;
}


414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
/*******************************************************************
 *         check_no_exec
 *
 * Check for executing a protected area.
 */
inline static BOOL check_no_exec( void *addr )
{
    MEMORY_BASIC_INFORMATION info;

    if (!VirtualQuery( addr, &info, sizeof(info) )) return FALSE;
    if (info.State == MEM_FREE) return FALSE;

    /* prot |= PAGE_EXECUTE would be a lot easier, but MS developers
     * apparently don't grasp the notion of protection bits */
    switch(info.Protect)
    {
    case PAGE_READONLY: info.Protect = PAGE_EXECUTE_READ; break;
    case PAGE_READWRITE: info.Protect = PAGE_EXECUTE_READWRITE; break;
    case PAGE_WRITECOPY: info.Protect = PAGE_EXECUTE_WRITECOPY; break;
    default: return FALSE;
    }
    /* FIXME: we should probably have a per-app option, and maybe a message box */
    FIXME( "No-exec fault triggered at %p, enabling work-around\n", addr );
    return VirtualProtect( addr, 1, info.Protect, NULL );
}


441 442 443
/*******************************************************************
 *         UnhandledExceptionFilter   (KERNEL32.@)
 */
444
LONG WINAPI UnhandledExceptionFilter(PEXCEPTION_POINTERS epointers)
445
{
446 447 448 449 450 451
    const EXCEPTION_RECORD *rec = epointers->ExceptionRecord;

    if (rec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && rec->NumberParameters >= 2)
    {
        switch(rec->ExceptionInformation[0])
        {
452
        case EXCEPTION_WRITE_FAULT:
453 454 455
            if (check_resource_write( (void *)rec->ExceptionInformation[1] ))
                return EXCEPTION_CONTINUE_EXECUTION;
            break;
456
        case EXCEPTION_EXECUTE_FAULT:
457 458 459 460 461
            if (check_no_exec( (void *)rec->ExceptionInformation[1] ))
                return EXCEPTION_CONTINUE_EXECUTION;
            break;
        }
    }
462

463
    if (!NtCurrentTeb()->Peb->BeingDebugged)
464
    {
465
        if (rec->ExceptionCode == CONTROL_C_EXIT)
466 467 468 469
        {
            /* do not launch the debugger on ^C, simply terminate the process */
            TerminateProcess( GetCurrentProcess(), 1 );
        }
470

471 472
        if (top_filter)
        {
473
            LONG ret = top_filter( epointers );
474 475
            if (ret != EXCEPTION_CONTINUE_SEARCH) return ret;
        }
476

477
        /* FIXME: Should check the current error mode */
478

479 480
        if (!start_debugger_atomic( epointers ) || !NtCurrentTeb()->Peb->BeingDebugged)
            return EXCEPTION_EXECUTE_HANDLER;
481
    }
482
    return EXCEPTION_CONTINUE_SEARCH;
Alexandre Julliard's avatar
Alexandre Julliard committed
483 484
}

Alexandre Julliard's avatar
Alexandre Julliard committed
485

486
/***********************************************************************
487
 *            SetUnhandledExceptionFilter   (KERNEL32.@)
Alexandre Julliard's avatar
Alexandre Julliard committed
488
 */
Alexandre Julliard's avatar
Alexandre Julliard committed
489
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(
Alexandre Julliard's avatar
Alexandre Julliard committed
490 491
                                          LPTOP_LEVEL_EXCEPTION_FILTER filter )
{
492 493
    LPTOP_LEVEL_EXCEPTION_FILTER old = top_filter;
    top_filter = filter;
Alexandre Julliard's avatar
Alexandre Julliard committed
494
    return old;
Alexandre Julliard's avatar
Alexandre Julliard committed
495
}
496 497


498
/**************************************************************************
499
 *           FatalAppExitA   (KERNEL32.@)
500 501 502
 */
void WINAPI FatalAppExitA( UINT action, LPCSTR str )
{
503 504 505
    HMODULE mod = GetModuleHandleA( "user32.dll" );
    MessageBoxA_funcptr pMessageBoxA = NULL;

506
    WARN("AppExit\n");
507 508 509 510

    if (mod) pMessageBoxA = (MessageBoxA_funcptr)GetProcAddress( mod, "MessageBoxA" );
    if (pMessageBoxA) pMessageBoxA( 0, str, NULL, MB_SYSTEMMODAL | MB_OK );
    else ERR( "%s\n", debugstr_a(str) );
511 512 513 514 515
    ExitProcess(0);
}


/**************************************************************************
516
 *           FatalAppExitW   (KERNEL32.@)
517 518 519
 */
void WINAPI FatalAppExitW( UINT action, LPCWSTR str )
{
520 521 522
    static const WCHAR User32DllW[] = {'u','s','e','r','3','2','.','d','l','l',0};

    HMODULE mod = GetModuleHandleW( User32DllW );
523 524
    MessageBoxW_funcptr pMessageBoxW = NULL;

525
    WARN("AppExit\n");
526 527 528 529

    if (mod) pMessageBoxW = (MessageBoxW_funcptr)GetProcAddress( mod, "MessageBoxW" );
    if (pMessageBoxW) pMessageBoxW( 0, str, NULL, MB_SYSTEMMODAL | MB_OK );
    else ERR( "%s\n", debugstr_w(str) );
530 531
    ExitProcess(0);
}
Robert Shearman's avatar
Robert Shearman committed
532 533 534 535 536 537 538 539 540 541


/**************************************************************************
 *           FatalExit   (KERNEL32.@)
 */
void WINAPI FatalExit(int ExitCode)
{
    WARN("FatalExit\n");
    ExitProcess(ExitCode);
}