/* ** Command & Conquer Generals Zero Hour(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ //////////////////////////////////////////////////////////////////////////////// // // // (c) 2001-2003 Electronic Arts Inc. // // // //////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #if defined(_DEBUG) || defined(_INTERNAL) || defined(IG_DEBUG_STACKTRACE) #pragma pack(push, 8) #pragma comment(linker, "/defaultlib:Dbghelp.lib") #include "Common/StackDump.h" #include "Common/Debug.h" //***************************************************************************** // Prototypes //***************************************************************************** BOOL InitSymbolInfo(void); void UninitSymbolInfo(void); void MakeStackTrace(DWORD myeip,DWORD myesp,DWORD myebp, int skipFrames, void (*callback)(const char*)); void GetFunctionDetails(void *pointer, char*name, char*filename, unsigned int* linenumber, unsigned int* address); void WriteStackLine(void*address, void (*callback)(const char*)); //***************************************************************************** // Mis-named globals :-) //***************************************************************************** static CONTEXT gsContext; static Bool gsInit=FALSE; BOOL (__stdcall *gsSymGetLineFromAddr)( IN HANDLE hProcess, IN DWORD dwAddr, OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE Line ); //***************************************************************************** //***************************************************************************** void StackDumpDefaultHandler(const char*line) { DEBUG_LOG((line)); } //***************************************************************************** //***************************************************************************** void StackDump(void (*callback)(const char*)) { if (callback == NULL) { callback = StackDumpDefaultHandler; } InitSymbolInfo(); DWORD myeip,myesp,myebp; _asm { MYEIP1: mov eax, MYEIP1 mov dword ptr [myeip] , eax mov eax, esp mov dword ptr [myesp] , eax mov eax, ebp mov dword ptr [myebp] , eax } MakeStackTrace(myeip,myesp,myebp, 2, callback); } //***************************************************************************** //***************************************************************************** void StackDumpFromContext(DWORD eip,DWORD esp,DWORD ebp, void (*callback)(const char*)) { if (callback == NULL) { callback = StackDumpDefaultHandler; } InitSymbolInfo(); MakeStackTrace(eip,esp,ebp, 0, callback); } //***************************************************************************** //***************************************************************************** BOOL InitSymbolInfo() { if (gsInit == TRUE) return TRUE; gsInit = TRUE; atexit(UninitSymbolInfo); // See if we have the line from address function // We use GetProcAddress to stop link failures at dll loadup HINSTANCE hInstDebugHlp = GetModuleHandle("dbghelp.dll"); gsSymGetLineFromAddr = (BOOL (__stdcall *)( IN HANDLE,IN DWORD,OUT PDWORD,OUT PIMAGEHLP_LINE)) GetProcAddress(hInstDebugHlp , "SymGetLineFromAddr"); char pathname[_MAX_PATH+1]; char drive[10]; char directory[_MAX_PATH+1]; HANDLE process; ::SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME | SYMOPT_LOAD_LINES | SYMOPT_OMAP_FIND_NEAREST); process = GetCurrentProcess(); //Get the apps name ::GetModuleFileName(NULL, pathname, _MAX_PATH); // turn it into a search path _splitpath(pathname, drive, directory, NULL, NULL); sprintf(pathname, "%s:\\%s", drive, directory); // append the current directory to build a search path for SymInit ::lstrcat(pathname, ";.;"); if(::SymInitialize(process, pathname, FALSE)) { // regenerate the name of the app ::GetModuleFileName(NULL, pathname, _MAX_PATH); if(::SymLoadModule(process, NULL, pathname, NULL, 0, 0)) { //Load any other relevant modules (ie dlls) here return TRUE; } ::SymCleanup(process); } return(FALSE); } //***************************************************************************** //***************************************************************************** void UninitSymbolInfo(void) { if (gsInit == FALSE) { return; } gsInit = FALSE; ::SymCleanup(GetCurrentProcess()); } //***************************************************************************** //***************************************************************************** void MakeStackTrace(DWORD myeip,DWORD myesp,DWORD myebp, int skipFrames, void (*callback)(const char*)) { STACKFRAME stack_frame; BOOL b_ret = TRUE; HANDLE thread = GetCurrentThread(); HANDLE process = GetCurrentProcess(); memset(&gsContext, 0, sizeof(CONTEXT)); gsContext.ContextFlags = CONTEXT_FULL; memset(&stack_frame, 0, sizeof(STACKFRAME)); stack_frame.AddrPC.Mode = AddrModeFlat; stack_frame.AddrPC.Offset = myeip; stack_frame.AddrStack.Mode = AddrModeFlat; stack_frame.AddrStack.Offset = myesp; stack_frame.AddrFrame.Mode = AddrModeFlat; stack_frame.AddrFrame.Offset = myebp; { /* if(GetThreadContext(thread, &gsContext)) { memset(&stack_frame, 0, sizeof(STACKFRAME)); stack_frame.AddrPC.Mode = AddrModeFlat; stack_frame.AddrPC.Offset = gsContext.Eip; stack_frame.AddrStack.Mode = AddrModeFlat; stack_frame.AddrStack.Offset = gsContext.Esp; stack_frame.AddrFrame.Mode = AddrModeFlat; stack_frame.AddrFrame.Offset = gsContext.Ebp; */ //{ callback("Call Stack\n**********\n"); // Skip some ? unsigned int skip = skipFrames; while (b_ret&&skip) { b_ret = StackWalk( IMAGE_FILE_MACHINE_I386, process, thread, &stack_frame, NULL, //&gsContext, NULL, SymFunctionTableAccess, SymGetModuleBase, NULL); skip--; } skip = 30; while(b_ret&&skip) { b_ret = StackWalk( IMAGE_FILE_MACHINE_I386, process, thread, &stack_frame, NULL, //&gsContext, NULL, SymFunctionTableAccess, SymGetModuleBase, NULL); if (b_ret) WriteStackLine((void *) stack_frame.AddrPC.Offset, callback); skip--; } } } //***************************************************************************** //***************************************************************************** void GetFunctionDetails(void *pointer, char*name, char*filename, unsigned int* linenumber, unsigned int* address) { InitSymbolInfo(); if (name) { strcpy(name, ""); } if (filename) { strcpy(filename, ""); } if (linenumber) { *linenumber = 0xFFFFFFFF; } if (address) { *address = 0xFFFFFFFF; } ULONG displacement = 0; HANDLE process = ::GetCurrentProcess(); char symbol_buffer[512 + sizeof(IMAGEHLP_SYMBOL)]; memset(symbol_buffer, 0, sizeof(symbol_buffer)); PIMAGEHLP_SYMBOL psymbol = (PIMAGEHLP_SYMBOL)symbol_buffer; psymbol->SizeOfStruct = sizeof(symbol_buffer); psymbol->MaxNameLength = 512; if (SymGetSymFromAddr(process, (DWORD) pointer, &displacement, psymbol)) { if (name) { strcpy(name, psymbol->Name); strcat(name, "();"); } // Get line now if (gsSymGetLineFromAddr) { // Unsupported for win95/98 at least with my current dbghelp.dll IMAGEHLP_LINE line; memset(&line,0,sizeof(line)); line.SizeOfStruct = sizeof(line); if (gsSymGetLineFromAddr(process, (DWORD) pointer, &displacement, &line)) { if (filename) { strcpy(filename, line.FileName); } if (linenumber) { *linenumber = (unsigned int)line.LineNumber; } if (address) { *address = (unsigned int)line.Address; } } } } } //***************************************************************************** // Gets last x addresses from the stack //***************************************************************************** void FillStackAddresses(void**addresses, unsigned int count, unsigned int skip) { InitSymbolInfo(); STACKFRAME stack_frame; HANDLE thread = GetCurrentThread(); HANDLE process = GetCurrentProcess(); memset(&gsContext, 0, sizeof(CONTEXT)); gsContext.ContextFlags = CONTEXT_FULL; DWORD myeip,myesp,myebp; _asm { MYEIP2: mov eax, MYEIP2 mov dword ptr [myeip] , eax mov eax, esp mov dword ptr [myesp] , eax mov eax, ebp mov dword ptr [myebp] , eax xor eax,eax } memset(&stack_frame, 0, sizeof(STACKFRAME)); stack_frame.AddrPC.Mode = AddrModeFlat; stack_frame.AddrPC.Offset = myeip; stack_frame.AddrStack.Mode = AddrModeFlat; stack_frame.AddrStack.Offset = myesp; stack_frame.AddrFrame.Mode = AddrModeFlat; stack_frame.AddrFrame.Offset = myebp; { /* if(GetThreadContext(thread, &gsContext)) { memset(&stack_frame, 0, sizeof(STACKFRAME)); stack_frame.AddrPC.Mode = AddrModeFlat; stack_frame.AddrPC.Offset = gsContext.Eip; stack_frame.AddrStack.Mode = AddrModeFlat; stack_frame.AddrStack.Offset = gsContext.Esp; stack_frame.AddrFrame.Mode = AddrModeFlat; stack_frame.AddrFrame.Offset = gsContext.Ebp; */ Bool stillgoing = TRUE; // unsigned int cd = count; // Skip some? while (stillgoing&&skip) { stillgoing = StackWalk(IMAGE_FILE_MACHINE_I386, process, thread, &stack_frame, NULL, //&gsContext, NULL, SymFunctionTableAccess, SymGetModuleBase, NULL) != 0; skip--; } while(stillgoing&&count) { stillgoing = StackWalk(IMAGE_FILE_MACHINE_I386, process, thread, &stack_frame, NULL, //&gsContext, NULL, SymFunctionTableAccess, SymGetModuleBase, NULL) != 0; if (stillgoing) { *addresses = (void*)stack_frame.AddrPC.Offset; addresses++; count--; } } // Fill remainder while (count) { *addresses = NULL; addresses++; count--; } } /* else { memset(addresses,NULL,count*sizeof(void*)); } */ } //***************************************************************************** // Do full stack dump using an address array //***************************************************************************** void StackDumpFromAddresses(void**addresses, unsigned int count, void (*callback)(const char *)) { if (callback == NULL) { callback = StackDumpDefaultHandler; } InitSymbolInfo(); while ((count--) && (*addresses!=NULL)) { WriteStackLine(*addresses,callback); addresses++; } } AsciiString g_LastErrorDump; //***************************************************************************** //***************************************************************************** void WriteStackLine(void*address, void (*callback)(const char*)) { static char line[MAX_PATH]; static char function_name[512]; static char filename[MAX_PATH]; unsigned int linenumber; unsigned int addr; GetFunctionDetails(address, function_name, filename, &linenumber, &addr); sprintf(line, " %s(%d) : %s 0x%08p", filename, linenumber, function_name, address); if (g_LastErrorDump.isNotEmpty()) { g_LastErrorDump.concat(line); g_LastErrorDump.concat("\n"); } callback(line); callback("\n"); } //***************************************************************************** //***************************************************************************** void DumpExceptionInfo( unsigned int u, EXCEPTION_POINTERS* e_info ) { DEBUG_LOG(( "\n********** EXCEPTION DUMP ****************\n" )); /* ** List of possible exceptions */ g_LastErrorDump.clear(); static const unsigned int _codes[] = { EXCEPTION_ACCESS_VIOLATION, EXCEPTION_ARRAY_BOUNDS_EXCEEDED, EXCEPTION_BREAKPOINT, EXCEPTION_DATATYPE_MISALIGNMENT, EXCEPTION_FLT_DENORMAL_OPERAND, EXCEPTION_FLT_DIVIDE_BY_ZERO, EXCEPTION_FLT_INEXACT_RESULT, EXCEPTION_FLT_INVALID_OPERATION, EXCEPTION_FLT_OVERFLOW, EXCEPTION_FLT_STACK_CHECK, EXCEPTION_FLT_UNDERFLOW, EXCEPTION_ILLEGAL_INSTRUCTION, EXCEPTION_IN_PAGE_ERROR, EXCEPTION_INT_DIVIDE_BY_ZERO, EXCEPTION_INT_OVERFLOW, EXCEPTION_INVALID_DISPOSITION, EXCEPTION_NONCONTINUABLE_EXCEPTION, EXCEPTION_PRIV_INSTRUCTION, EXCEPTION_SINGLE_STEP, EXCEPTION_STACK_OVERFLOW, 0xffffffff }; /* ** Information about each exception type. */ static char const * _code_txt[] = { "Error code: EXCEPTION_ACCESS_VIOLATION\nDescription: The thread tried to read from or write to a virtual address for which it does not have the appropriate access.", "Error code: EXCEPTION_ARRAY_BOUNDS_EXCEEDED\nDescription: The thread tried to access an array element that is out of bounds and the underlying hardware supports bounds checking.", "Error code: EXCEPTION_BREAKPOINT\nDescription: A breakpoint was encountered.", "Error code: EXCEPTION_DATATYPE_MISALIGNMENT\nDescription: The thread tried to read or write data that is misaligned on hardware that does not provide alignment. For example, 16-bit values must be aligned on 2-byte boundaries; 32-bit values on 4-byte boundaries, and so on.", "Error code: EXCEPTION_FLT_DENORMAL_OPERAND\nDescription: One of the operands in a floating-point operation is denormal. A denormal value is one that is too small to represent as a standard floating-point value.", "Error code: EXCEPTION_FLT_DIVIDE_BY_ZERO\nDescription: The thread tried to divide a floating-point value by a floating-point divisor of zero.", "Error code: EXCEPTION_FLT_INEXACT_RESULT\nDescription: The result of a floating-point operation cannot be represented exactly as a decimal fraction.", "Error code: EXCEPTION_FLT_INVALID_OPERATION\nDescription: Some strange unknown floating point operation was attempted.", "Error code: EXCEPTION_FLT_OVERFLOW\nDescription: The exponent of a floating-point operation is greater than the magnitude allowed by the corresponding type.", "Error code: EXCEPTION_FLT_STACK_CHECK\nDescription: The stack overflowed or underflowed as the result of a floating-point operation.", "Error code: EXCEPTION_FLT_UNDERFLOW\nDescription: The exponent of a floating-point operation is less than the magnitude allowed by the corresponding type.", "Error code: EXCEPTION_ILLEGAL_INSTRUCTION\nDescription: The thread tried to execute an invalid instruction.", "Error code: EXCEPTION_IN_PAGE_ERROR\nDescription: The thread tried to access a page that was not present, and the system was unable to load the page. For example, this exception might occur if a network connection is lost while running a program over the network.", "Error code: EXCEPTION_INT_DIVIDE_BY_ZERO\nDescription: The thread tried to divide an integer value by an integer divisor of zero.", "Error code: EXCEPTION_INT_OVERFLOW\nDescription: The result of an integer operation caused a carry out of the most significant bit of the result.", "Error code: EXCEPTION_INVALID_DISPOSITION\nDescription: An exception handler returned an invalid disposition to the exception dispatcher. Programmers using a high-level language such as C should never encounter this exception.", "Error code: EXCEPTION_NONCONTINUABLE_EXCEPTION\nDescription: The thread tried to continue execution after a noncontinuable exception occurred.", "Error code: EXCEPTION_PRIV_INSTRUCTION\nDescription: The thread tried to execute an instruction whose operation is not allowed in the current machine mode.", "Error code: EXCEPTION_SINGLE_STEP\nDescription: A trace trap or other single-instruction mechanism signaled that one instruction has been executed.", "Error code: EXCEPTION_STACK_OVERFLOW\nDescription: The thread used up its stack.", "Error code: ?????\nDescription: Unknown exception." }; DEBUG_LOG( ("Dump exception info\n") ); CONTEXT *context = e_info->ContextRecord; /* ** The following are set for access violation only */ int access_read_write=-1; unsigned long access_address = 0; AsciiString msg; // DOUBLE_DEBUG does a DEBUG_LOG, and concats to g_LastErrorDump. jba. #define DOUBLE_DEBUG(x) { msg.format x; g_LastErrorDump.concat(msg); DEBUG_LOG( x ); } if ( e_info->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION ) { DOUBLE_DEBUG (("Exception is access violation\n")); access_read_write = e_info->ExceptionRecord->ExceptionInformation[0]; // 0=read, 1=write access_address = e_info->ExceptionRecord->ExceptionInformation[1]; } else { DOUBLE_DEBUG (("Exception code is %x\n", e_info->ExceptionRecord->ExceptionCode)); } Int *winMainAddr = (Int *)WinMain; DOUBLE_DEBUG(("WinMain at %x\n", winMainAddr)); /* ** Match the exception type with the error string and print it out */ for ( int i=0 ; _codes[i] != 0xffffffff ; i++ ) { if ( _codes[i] == e_info->ExceptionRecord->ExceptionCode ) { DEBUG_LOG ( ("Found exception description\n") ); break; } } DOUBLE_DEBUG( ("%s\n", _code_txt[i])); /** For access violations, print out the violation address and if it was read or write. */ if ( e_info->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION ) { if ( access_read_write ) { DOUBLE_DEBUG( ("Access address:%08X was written to.\n", access_address)); } else { DOUBLE_DEBUG( ("Access address:%08X was read from.\n", access_address)); } } DOUBLE_DEBUG (("\nStack Dump:\n")); StackDumpFromContext(context->Eip, context->Esp, context->Ebp, NULL); DOUBLE_DEBUG (("\nDetails:\n")); DOUBLE_DEBUG (("Register dump...\n")); /* ** Dump the registers. */ DOUBLE_DEBUG ( ( "Eip:%08X\tEsp:%08X\tEbp:%08X\n", context->Eip, context->Esp, context->Ebp)); DOUBLE_DEBUG ( ( "Eax:%08X\tEbx:%08X\tEcx:%08X\n", context->Eax, context->Ebx, context->Ecx)); DOUBLE_DEBUG ( ( "Edx:%08X\tEsi:%08X\tEdi:%08X\n", context->Edx, context->Esi, context->Edi)); DOUBLE_DEBUG ( ( "EFlags:%08X \n", context->EFlags)); DOUBLE_DEBUG ( ( "CS:%04x SS:%04x DS:%04x ES:%04x FS:%04x GS:%04x\n", context->SegCs, context->SegSs, context->SegDs, context->SegEs, context->SegFs, context->SegGs)); /* ** Dump the bytes at EIP. This will make it easier to match the crash address with later versions of the game. */ char scrap[512]; DOUBLE_DEBUG ( ("EIP bytes dump...\n")); wsprintf (scrap, "\nBytes at CS:EIP (%08X) : ", context->Eip); unsigned char *eip_ptr = (unsigned char *) (context->Eip); char bytestr[32]; for (int c = 0 ; c < 32 ; c++) { if (IsBadReadPtr(eip_ptr, 1)) { lstrcat (scrap, "?? "); } else { sprintf (bytestr, "%02X ", *eip_ptr); strcat (scrap, bytestr); } eip_ptr++; } strcat (scrap, "\n"); DOUBLE_DEBUG ( ( (scrap))); DEBUG_LOG(( "********** END EXCEPTION DUMP ****************\n\n" )); } #pragma pack(pop) #endif