Files
UnrealEngineUWP/Engine/Source/Runtime/Launch/Private/Windows/LaunchWindows.cpp
geoff evans d90ca97505 RenderingThread: Fix lack of logging the message and callstack for check() failures
### Notes
The check() message and callstack is actually printed in FWindowsErrorOutputDevice::HandleError which is called in the __except block after we RaiseException() inside ReportAssert(). However, RenderingThread's __except doesn't do this, so the contents of GErrorHist are lost to everyone except for CrashReporter. This change adds this necessary logging.

Also, add Windows Launch support for -IgnoreDebugger when choosing __try/__except or not (this precedes initialization of GIgnoreDebugger). This makes debugging easier for this class of issue.

### Testing
Repro steps are in the JIRA. Tested at that change (which has a reliable RenderingThread check() failure), and at #head where no defect exists. Tests fail and pass, respectively, as expected.

#rnx
#rb francis.hurteau
#jira UE-151056
#preflight 627b0b4ac42338be653745e6

[CL 20140980 by geoff evans in ue5-main branch]
2022-05-11 11:19:14 -04:00

344 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#if WINDOWS_USE_FEATURE_LAUNCH
#include "Misc/App.h"
#include "Misc/OutputDeviceError.h"
#include "LaunchEngineLoop.h"
#include "HAL/ExceptionHandling.h"
#include "HAL/PlatformMallocCrash.h"
#include "Windows/WindowsHWrapper.h"
#include <shellapi.h>
#if UE_BUILD_DEBUG
#include <crtdbg.h>
#endif
DEFINE_LOG_CATEGORY_STATIC(LogLaunchWindows, Log, All);
extern int32 GuardedMain( const TCHAR* CmdLine );
extern void LaunchStaticShutdownAfterError();
// http://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/OptimusRenderingPolicies.pdf
// The following line is to favor the high performance NVIDIA GPU if there are multiple GPUs
// Has to be .exe module to be correctly detected.
extern "C" { _declspec(dllexport) uint32 NvOptimusEnablement = 0x00000001; }
// And the AMD equivalent
// Also has to be .exe module to be correctly detected.
extern "C" { _declspec(dllexport) uint32 AmdPowerXpressRequestHighPerformance = 0x00000001; }
// Opt in to new D3D12 redist and tell the loader where to search for D3D12Core.dll.
// The D3D loader looks for these symbol exports in the .exe module.
// We only support this on x64 Windows Desktop platforms. Other platforms or non-redist-aware
// versions of Windows will transparently load default OS-provided D3D12 library.
#define USE_D3D12_REDIST (PLATFORM_DESKTOP && PLATFORM_CPU_X86_FAMILY && PLATFORM_64BITS && 1)
#if USE_D3D12_REDIST
extern "C" { _declspec(dllexport) extern const UINT D3D12SDKVersion = 602; }
extern "C" { _declspec(dllexport) extern const char* D3D12SDKPath = u8".\\D3D12\\"; }
#endif // USE_D3D12_REDIST
/**
* Maintain a named mutex to detect whether we are the first instance of this game
*/
HANDLE GNamedMutex = NULL;
/** Whether we should pause before exiting. used by UCC */
bool GShouldPauseBeforeExit;
void ReleaseNamedMutex( void )
{
if( GNamedMutex )
{
ReleaseMutex( GNamedMutex );
GNamedMutex = NULL;
}
}
bool MakeNamedMutex( const TCHAR* CmdLine )
{
bool bIsFirstInstance = false;
TCHAR MutexName[MAX_SPRINTF] = TEXT( "" );
FCString::Strcpy( MutexName, MAX_SPRINTF, TEXT( "UnrealEngine4" ) );
GNamedMutex = CreateMutex( NULL, true, MutexName );
if( GNamedMutex && GetLastError() != ERROR_ALREADY_EXISTS && !FParse::Param( CmdLine, TEXT( "NEVERFIRST" ) ) )
{
// We're the first instance!
bIsFirstInstance = true;
}
else
{
// Still need to release it in this case, because it gave us a valid copy
ReleaseNamedMutex();
// There is already another instance of the game running.
bIsFirstInstance = false;
}
return( bIsFirstInstance );
}
/**
* Handler for CRT parameter validation. Triggers error
*
* @param Expression - the expression that failed crt validation
* @param Function - function which failed crt validation
* @param File - file where failure occured
* @param Line - line number of failure
* @param Reserved - not used
*/
void InvalidParameterHandler(const TCHAR* Expression,
const TCHAR* Function,
const TCHAR* File,
uint32 Line,
uintptr_t Reserved)
{
UE_LOG(LogLaunchWindows, Fatal,TEXT("SECURE CRT: Invalid parameter detected.\nExpression: %s Function: %s. File: %s Line: %d\n"),
Expression ? Expression : TEXT("Unknown"),
Function ? Function : TEXT("Unknown"),
File ? File : TEXT("Unknown"),
Line );
}
/**
* Setup the common debug settings
*/
void SetupWindowsEnvironment( void )
{
// all crt validation should trigger the callback
_set_invalid_parameter_handler(InvalidParameterHandler);
#if UE_BUILD_DEBUG
// Disable the message box for assertions and just write to debugout instead
_CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_DEBUG );
// don't fill buffers with 0xfd as we make assumptions for FNames st we only use a fraction of the entire buffer
_CrtSetDebugFillThreshold( 0 );
#endif
}
/**
* The inner exception handler catches crashes/asserts in native C++ code and is the only way to get the correct callstack
* when running a 64-bit executable. However, XAudio2 doesn't always like this and it may result in no sound.
*/
#ifdef _WIN64
bool GEnableInnerException = true;
#else
bool GEnableInnerException = false;
#endif
/**
* The inner exception handler catches crashes/asserts in native C++ code and is the only way to get the correct callstack
* when running a 64-bit executable. However, XAudio2 doesn't like this and it may result in no sound.
*/
LAUNCH_API int32 GuardedMainWrapper( const TCHAR* CmdLine )
{
int32 ErrorLevel = 0;
if ( GEnableInnerException )
{
#if !PLATFORM_SEH_EXCEPTIONS_DISABLED
__try
#endif
{
// Run the guarded code.
ErrorLevel = GuardedMain( CmdLine );
}
#if !PLATFORM_SEH_EXCEPTIONS_DISABLED
__except( ReportCrash( GetExceptionInformation() ), EXCEPTION_CONTINUE_SEARCH )
{
// Deliberately do nothing but avoid warning C6322: Empty _except block.
(void)0;
}
#endif
}
else
{
// Run the guarded code.
ErrorLevel = GuardedMain( CmdLine );
}
return ErrorLevel;
}
/**
* The command-line invocation string, processed using the standard Windows CommandLineToArgvW implementation.
* This need to be a static global to avoid object unwinding errors in WinMain when SEH is enabled.
*/
static FString GSavedCommandLine;
bool ProcessCommandLine()
{
int argc = 0;
LPWSTR* argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
if (argv != nullptr)
{
// Reconstruct our command line string in a format suitable for consumption by the FParse class
GSavedCommandLine = "";
for (int32 Option = 1; Option < argc; Option++)
{
GSavedCommandLine += TEXT(" ");
// Inject quotes in the correct position to preserve arguments containing whitespace
FString Temp = argv[Option];
if (Temp.Contains(TEXT(" ")))
{
int32 Quote = 0;
if(Temp.StartsWith(TEXT("-")))
{
int32 Separator;
if (Temp.FindChar('=', Separator))
{
Quote = Separator + 1;
}
}
Temp = Temp.Left(Quote) + TEXT("\"") + Temp.Mid(Quote) + TEXT("\"");
}
GSavedCommandLine += Temp;
}
// Free memory allocated for CommandLineToArgvW() arguments
::LocalFree(argv);
return true;
}
return false;
}
LAUNCH_API int32 LaunchWindowsStartup( HINSTANCE hInInstance, HINSTANCE hPrevInstance, char*, int32 nCmdShow, const TCHAR* CmdLine )
{
TRACE_BOOKMARK(TEXT("WinMain.Enter"));
// Setup common Windows settings
SetupWindowsEnvironment();
int32 ErrorLevel = 0;
hInstance = hInInstance;
if (!CmdLine)
{
CmdLine = ::GetCommandLineW();
// Attempt to process the command-line arguments using the standard Windows implementation
// (This ensures behavior parity with other platforms where argc and argv are used.)
if ( ProcessCommandLine() )
{
CmdLine = *GSavedCommandLine;
}
}
// If we're running in unattended mode, make sure we never display error dialogs if we crash.
if ( FParse::Param( CmdLine, TEXT("unattended") ) )
{
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
}
#if !(UE_BUILD_SHIPPING && WITH_EDITOR)
// Named mutex we use to figure out whether we are the first instance of the game running. This is needed to e.g.
// make sure there is no contention when trying to save the shader cache.
GIsFirstInstance = MakeNamedMutex( CmdLine );
if ( FParse::Param( CmdLine,TEXT("crashreports") ) )
{
GAlwaysReportCrash = true;
}
#endif
bool bNoExceptionHandler = FParse::Param(CmdLine,TEXT("noexceptionhandler"));
(void)bNoExceptionHandler;
bool bIgnoreDebugger = FParse::Param(CmdLine, TEXT("IgnoreDebugger"));
(void)bIgnoreDebugger;
bool bIsDebuggerPresent = FPlatformMisc::IsDebuggerPresent() && !bIgnoreDebugger;
(void)bIsDebuggerPresent;
// Using the -noinnerexception parameter will disable the exception handler within native C++, which is call from managed code,
// which is called from this function.
// The default case is to have three wrapped exception handlers
// Native: WinMain() -> Native: GuardedMainWrapper().
// The inner exception handler in GuardedMainWrapper() catches crashes/asserts in native C++ code and is the only way to get the
// correct callstack when running a 64-bit executable. However, XAudio2 sometimes (?) don't like this and it may result in no sound.
#ifdef _WIN64
if ( FParse::Param(CmdLine,TEXT("noinnerexception")) || FApp::IsBenchmarking() || bNoExceptionHandler)
{
GEnableInnerException = false;
}
#endif
// When we're running embedded, assume that the outer application is going to be handling crash reporting
#if UE_BUILD_DEBUG
if (GUELibraryOverrideSettings.bIsEmbedded || !GAlwaysReportCrash)
#else
if (GUELibraryOverrideSettings.bIsEmbedded || bNoExceptionHandler || (bIsDebuggerPresent && !GAlwaysReportCrash))
#endif
{
// Don't use exception handling when a debugger is attached to exactly trap the crash. This does NOT check
// whether we are the first instance or not!
ErrorLevel = GuardedMain( CmdLine );
}
else
{
// Use structured exception handling to trap any crashes, walk the the stack and display a crash dialog box.
#if !PLATFORM_SEH_EXCEPTIONS_DISABLED
__try
#endif
{
GIsGuarded = 1;
// Run the guarded code.
ErrorLevel = GuardedMainWrapper( CmdLine );
GIsGuarded = 0;
}
#if !PLATFORM_SEH_EXCEPTIONS_DISABLED
__except( GEnableInnerException ? EXCEPTION_EXECUTE_HANDLER : ReportCrash( GetExceptionInformation( ) ) )
{
#if !(UE_BUILD_SHIPPING && WITH_EDITOR)
// Release the mutex in the error case to ensure subsequent runs don't find it.
ReleaseNamedMutex();
#endif
// Crashed.
ErrorLevel = 1;
if(GError)
{
GError->HandleError();
}
LaunchStaticShutdownAfterError();
FPlatformMallocCrash::Get().PrintPoolsUsage();
FPlatformMisc::RequestExit( true );
}
#endif
}
TRACE_BOOKMARK(TEXT("WinMain.Exit"));
return ErrorLevel;
}
LAUNCH_API void LaunchWindowsShutdown()
{
// Final shut down.
FEngineLoop::AppExit();
#if !(UE_BUILD_SHIPPING && WITH_EDITOR)
// Release the named mutex again now that we are done.
ReleaseNamedMutex();
#endif
// pause if we should
if (GShouldPauseBeforeExit)
{
Sleep(INFINITE);
}
}
int32 WINAPI WinMain(_In_ HINSTANCE hInInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ char* pCmdLine, _In_ int32 nCmdShow)
{
int32 Result = LaunchWindowsStartup(hInInstance, hPrevInstance, pCmdLine, nCmdShow, nullptr);
LaunchWindowsShutdown();
return Result;
}
#endif //WINDOWS_USE_FEATURE_LAUNCH