@ -5,6 +5,7 @@
# include <cstring>
# include <cstring>
# include <ctime>
# include <ctime>
# include <filesystem>
# include <filesystem>
# include <fmt/format.h>
# include <windows.h>
# include <windows.h>
# include "yuzu/mini_dump.h"
# include "yuzu/mini_dump.h"
# include "yuzu/startup_checks.h"
# include "yuzu/startup_checks.h"
@ -12,6 +13,8 @@
// dbghelp.h must be included after windows.h
// dbghelp.h must be included after windows.h
# include <dbghelp.h>
# include <dbghelp.h>
namespace MiniDump {
void CreateMiniDump ( HANDLE process_handle , DWORD process_id , MINIDUMP_EXCEPTION_INFORMATION * info ,
void CreateMiniDump ( HANDLE process_handle , DWORD process_id , MINIDUMP_EXCEPTION_INFORMATION * info ,
EXCEPTION_POINTERS * pep ) {
EXCEPTION_POINTERS * pep ) {
char file_name [ 255 ] ;
char file_name [ 255 ] ;
@ -23,7 +26,7 @@ void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_
CREATE_ALWAYS , FILE_ATTRIBUTE_NORMAL , nullptr ) ;
CREATE_ALWAYS , FILE_ATTRIBUTE_NORMAL , nullptr ) ;
if ( file_handle = = nullptr | | file_handle = = INVALID_HANDLE_VALUE ) {
if ( file_handle = = nullptr | | file_handle = = INVALID_HANDLE_VALUE ) {
std: : fprintf ( stderr , " CreateFileA failed. Error: %d \n " , GetLastError ( ) ) ;
fmt: : print ( stderr , " CreateFileA failed. Error: {} " , GetLastError ( ) ) ;
return ;
return ;
}
}
@ -34,9 +37,9 @@ void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_
dump_type , ( pep ! = 0 ) ? info : 0 , 0 , 0 ) ;
dump_type , ( pep ! = 0 ) ? info : 0 , 0 , 0 ) ;
if ( write_dump_status ) {
if ( write_dump_status ) {
std: : fprintf ( stderr , " MiniDump created: %s \n " , file_name ) ;
fmt: : print ( stderr , " MiniDump created: {} " , file_name ) ;
} else {
} else {
std: : fprintf ( stderr , " MiniDumpWriteDump failed. Error: %d \n " , GetLastError ( ) ) ;
fmt: : print ( stderr , " MiniDumpWriteDump failed. Error: {} " , GetLastError ( ) ) ;
}
}
// Close the file
// Close the file
@ -48,15 +51,15 @@ void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) {
HANDLE thread_handle = OpenThread ( THREAD_GET_CONTEXT , false , deb_ev . dwThreadId ) ;
HANDLE thread_handle = OpenThread ( THREAD_GET_CONTEXT , false , deb_ev . dwThreadId ) ;
if ( thread_handle = = nullptr ) {
if ( thread_handle = = nullptr ) {
std : : fprintf ( stderr , " OpenThread failed (%d) \n " , GetLastError ( ) ) ;
fmt : : print ( stderr , " OpenThread failed ({}) " , GetLastError ( ) ) ;
return ;
}
}
// Get child process context
// Get child process context
CONTEXT context ;
CONTEXT context = { } ;
std : : memset ( & context , 0 , sizeof ( context ) ) ;
context . ContextFlags = CONTEXT_ALL ;
context . ContextFlags = CONTEXT_ALL ;
if ( ! GetThreadContext ( thread_handle , & context ) ) {
if ( ! GetThreadContext ( thread_handle , & context ) ) {
std: : fprintf ( stderr , " GetThreadContext failed (%d) \n " , GetLastError ( ) ) ;
fmt: : print ( stderr , " GetThreadContext failed ({}) " , GetLastError ( ) ) ;
return ;
return ;
}
}
@ -73,7 +76,7 @@ void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) {
CreateMiniDump ( pi . hProcess , pi . dwProcessId , & info , & ep ) ;
CreateMiniDump ( pi . hProcess , pi . dwProcessId , & info , & ep ) ;
if ( CloseHandle ( thread_handle ) = = 0 ) {
if ( CloseHandle ( thread_handle ) = = 0 ) {
std: : fprintf ( stderr , " error: CloseHandle(thread_handle) failed ( %d)\n " , GetLastError ( ) ) ;
fmt: : print ( stderr , " error: CloseHandle(thread_handle) failed ( {}) " , GetLastError ( ) ) ;
}
}
}
}
@ -86,67 +89,22 @@ bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) {
}
}
if ( ! SpawnChild ( arg0 , & pi , 0 ) ) {
if ( ! SpawnChild ( arg0 , & pi , 0 ) ) {
std: : fprintf ( stderr , " warning: continuing without crash dumps \n " ) ;
fmt: : print ( stderr , " warning: continuing without crash dumps " ) ;
return false ;
return false ;
}
}
const bool can_debug = DebugActiveProcess ( pi . dwProcessId ) ;
const bool can_debug = DebugActiveProcess ( pi . dwProcessId ) ;
if ( ! can_debug ) {
if ( ! can_debug ) {
std: : fprintf ( stderr ,
fmt: : print ( stderr ,
" warning: DebugActiveProcess failed ( %d), continuing without crash dumps\n " ,
" warning: DebugActiveProcess failed ( {}), continuing without crash dumps " ,
GetLastError ( ) ) ;
GetLastError ( ) ) ;
return false ;
return false ;
}
}
return true ;
return true ;
}
}
void DebugDebuggee ( PROCESS_INFORMATION & pi ) {
static const char * ExceptionName ( DWORD exception ) {
DEBUG_EVENT deb_ev ;
std : : memset ( & deb_ev , 0 , sizeof ( deb_ev ) ) ;
while ( deb_ev . dwDebugEventCode ! = EXIT_PROCESS_DEBUG_EVENT ) {
const bool wait_success = WaitForDebugEvent ( & deb_ev , INFINITE ) ;
if ( ! wait_success ) {
std : : fprintf ( stderr , " error: WaitForDebugEvent failed (%d) \n " , GetLastError ( ) ) ;
return ;
}
switch ( deb_ev . dwDebugEventCode ) {
case OUTPUT_DEBUG_STRING_EVENT :
case CREATE_PROCESS_DEBUG_EVENT :
case CREATE_THREAD_DEBUG_EVENT :
case EXIT_PROCESS_DEBUG_EVENT :
case EXIT_THREAD_DEBUG_EVENT :
case LOAD_DLL_DEBUG_EVENT :
case RIP_EVENT :
case UNLOAD_DLL_DEBUG_EVENT :
// Continue on all other debug events
ContinueDebugEvent ( deb_ev . dwProcessId , deb_ev . dwThreadId , DBG_CONTINUE ) ;
break ;
case EXCEPTION_DEBUG_EVENT :
EXCEPTION_RECORD & record = deb_ev . u . Exception . ExceptionRecord ;
// We want to generate a crash dump if we are seeing the same exception again.
if ( ! deb_ev . u . Exception . dwFirstChance ) {
std : : fprintf ( stderr , " Creating MiniDump on ExceptionCode: 0x%08x %s \n " ,
record . ExceptionCode , ExceptionName ( record . ExceptionCode ) ) ;
DumpFromDebugEvent ( deb_ev , pi ) ;
}
// Continue without handling the exception.
// Lets the debuggee use its own exception handler.
// - If one does not exist, we will see the exception once more where we make a minidump
// for. Then when it reaches here again, yuzu will probably crash.
// - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an
// infinite loop of exceptions.
ContinueDebugEvent ( deb_ev . dwProcessId , deb_ev . dwThreadId , DBG_EXCEPTION_NOT_HANDLED ) ;
break ;
}
}
}
const char * ExceptionName ( DWORD exception ) {
switch ( exception ) {
switch ( exception ) {
case EXCEPTION_ACCESS_VIOLATION :
case EXCEPTION_ACCESS_VIOLATION :
return " EXCEPTION_ACCESS_VIOLATION " ;
return " EXCEPTION_ACCESS_VIOLATION " ;
@ -193,6 +151,52 @@ const char* ExceptionName(DWORD exception) {
case EXCEPTION_INVALID_HANDLE :
case EXCEPTION_INVALID_HANDLE :
return " EXCEPTION_INVALID_HANDLE " ;
return " EXCEPTION_INVALID_HANDLE " ;
default :
default :
return nullptr ;
return " unknown exception type " ;
}
}
}
}
void DebugDebuggee ( PROCESS_INFORMATION & pi ) {
DEBUG_EVENT deb_ev = { } ;
while ( deb_ev . dwDebugEventCode ! = EXIT_PROCESS_DEBUG_EVENT ) {
const bool wait_success = WaitForDebugEvent ( & deb_ev , INFINITE ) ;
if ( ! wait_success ) {
fmt : : print ( stderr , " error: WaitForDebugEvent failed ({}) " , GetLastError ( ) ) ;
return ;
}
switch ( deb_ev . dwDebugEventCode ) {
case OUTPUT_DEBUG_STRING_EVENT :
case CREATE_PROCESS_DEBUG_EVENT :
case CREATE_THREAD_DEBUG_EVENT :
case EXIT_PROCESS_DEBUG_EVENT :
case EXIT_THREAD_DEBUG_EVENT :
case LOAD_DLL_DEBUG_EVENT :
case RIP_EVENT :
case UNLOAD_DLL_DEBUG_EVENT :
// Continue on all other debug events
ContinueDebugEvent ( deb_ev . dwProcessId , deb_ev . dwThreadId , DBG_CONTINUE ) ;
break ;
case EXCEPTION_DEBUG_EVENT :
EXCEPTION_RECORD & record = deb_ev . u . Exception . ExceptionRecord ;
// We want to generate a crash dump if we are seeing the same exception again.
if ( ! deb_ev . u . Exception . dwFirstChance ) {
fmt : : print ( stderr , " Creating MiniDump on ExceptionCode: 0x{:08x} {} \n " ,
record . ExceptionCode , ExceptionName ( record . ExceptionCode ) ) ;
DumpFromDebugEvent ( deb_ev , pi ) ;
}
// Continue without handling the exception.
// Lets the debuggee use its own exception handler.
// - If one does not exist, we will see the exception once more where we make a minidump
// for. Then when it reaches here again, yuzu will probably crash.
// - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an
// infinite loop of exceptions.
ContinueDebugEvent ( deb_ev . dwProcessId , deb_ev . dwThreadId , DBG_EXCEPTION_NOT_HANDLED ) ;
break ;
}
}
}
} // namespace MiniDump