/* ** Command & Conquer Renegade(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 . */ /*********************************************************************************************** *** Confidential - Westwood Studios *** *********************************************************************************************** * * * Project Name : Commando * * * * $Archive:: /Commando/Code/Combat/debug.cpp $* * * * $Author:: Bhayes $* * * * $Modtime:: 2/16/02 8:44p $* * * * $Revision:: 90 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "debug.h" #include "input.h" #include "ww3dtrig.h" #include "wwphystrig.h" #include "timemgr.h" #include "ww3d.h" #include "mono.h" #include "registry.h" #include #include "wwaudio.h" #include "combat.h" #include "wwmemlog.h" #include "fastallocator.h" #ifndef STEVES_NEW_CATCHER #define LOG_MEMORY 1 // enable this to turn on memory logging #endif //STEVES_NEW_CATCHER /* ** */ int DebugManager::EnabledDevices; int DebugManager::EnabledTypes; int DebugManager::EnabledOptions; bool DebugManager::EnableFileLogging; bool DebugManager::EnableDiagLogging; bool DebugManager::LoadDebugScripts; int DebugManager::VersionNumber = 0; bool DebugManager::IsSlave = false; bool DebugManager::AllowCinematicKeys = false; CriticalSectionClass DebugManager::CriticalSection; DebugDisplayHandlerClass * DebugManager::DisplayHandler = NULL; #define DEFAULT_LOGFILE_NAME "_logfile.txt" LPSTR DebugManager::LOGFILE = DEFAULT_LOGFILE_NAME; char DebugManager::LogfileNameBuffer[256]; MonoClass ScrollingScreen; /* ** local prototypes */ void wwdebug_message_handler(DebugType type, const char * message); void wwdebug_assert_handler(const char * message); bool wwdebug_trigger_handler(int trigger_num); void wwdebug_profile_start_handler( const char * title ); void wwdebug_profile_stop_handler( const char * title ); char DefaultRegistryModifier[1024] = {""}; /* ** */ void DebugManager::Init( void ) { // Enable Mono Screen ScrollingScreen.Enable(); // Install message handler functions for the WWDebug messages // and assertion failures. WWDebug_Install_Message_Handler(wwdebug_message_handler); WWDebug_Install_Assert_Handler(wwdebug_assert_handler); WWDebug_Install_Trigger_Handler(wwdebug_trigger_handler); WWDebug_Install_Profile_Start_Handler(wwdebug_profile_start_handler); WWDebug_Install_Profile_Stop_Handler(wwdebug_profile_stop_handler); // Clear all of the debug devices, types, and options EnabledDevices = -1; EnabledTypes = -1; EnabledOptions = 0; EnableFileLogging = false; EnableDiagLogging = false; LoadDebugScripts = false; // // By default let us turn these off // Disable_Device(DEBUG_DEVICE_SCREEN); Disable_Type(DEBUG_TYPE_NETWORK_PROLIFIC); // // Clear the logfile // Init_Logfile(); Debug_Say(( "\n" )); } /* ** */ void DebugManager::Shutdown( void ) { // Remove message handler functions for the WWDebug messages // and assertion failures. WWDebug_Install_Message_Handler(NULL); WWDebug_Install_Assert_Handler(NULL); WWDebug_Install_Trigger_Handler(NULL); WWDebug_Install_Profile_Start_Handler(NULL); WWDebug_Install_Profile_Stop_Handler(NULL); // Disable mono screen ScrollingScreen.Disable(); } /* ** */ void DebugManager::Update( void ) { // Tell the profiler that a frame has passed if ( Input::Get_State( INPUT_FUNCTION_MAKE_SCREEN_SHOT) ) { WW3D::Make_Screen_Shot(); } if ( Input::Get_State( INPUT_FUNCTION_TOGGLE_MOVIE_CAPTURE ) ) { #ifdef WWDEBUG WW3D::Toggle_Movie_Capture(); #endif } #if 0 // Single Step code if (WWDEBUG_TRIGGER(0x53 /*S*/)) { while (!WWDEBUG_TRIGGER(0x20/*SPACE*/)) Input::Update(); while (WWDEBUG_TRIGGER(0x20/*SPACE*/)) Input::Update(); } #endif } void DebugManager::Load_Registry_Settings( const char * sub_key ) { RegistryClass registry( sub_key ); if ( registry.Is_Valid() ) { EnabledDevices = registry.Get_Int( "EnabledDevices", EnabledDevices ); EnabledTypes = registry.Get_Int( "EnabledTypes", EnabledTypes ); EnabledOptions = registry.Get_Int( "EnabledOptions", EnabledOptions ); EnableFileLogging = registry.Get_Bool( "EnableFileLogging", EnableFileLogging ); EnableDiagLogging = registry.Get_Bool( "EnableDiagLogging", EnableDiagLogging ); LoadDebugScripts = registry.Get_Bool( "LoadDebugScripts", LoadDebugScripts ); AllowCinematicKeys = registry.Get_Bool( "AllowCinematicKeys", AllowCinematicKeys ); } #ifdef LOG_MEMORY Debug_Say(( "*** Memory Logging Enabled ***\n" )); #endif } void DebugManager::Save_Registry_Settings( const char * sub_key ) { RegistryClass registry( sub_key ); if ( registry.Is_Valid() ) { registry.Set_Int( "EnabledDevices", EnabledDevices ); registry.Set_Int( "EnabledTypes", EnabledTypes ); registry.Set_Int( "EnabledOptions", EnabledOptions ); registry.Set_Bool( "EnableFileLogging", EnableFileLogging ); registry.Set_Bool( "EnableDiagLogging", EnableDiagLogging ); registry.Set_Bool( "LoadDebugScripts", LoadDebugScripts ); registry.Set_Bool( "AllowCinematicKeys", AllowCinematicKeys ); } } /* ** */ void DebugManager::Display( char const *buffer ) { CriticalSectionClass::LockClass lock(CriticalSection); if ( EnabledDevices & DEBUG_DEVICE_SCREEN ) { Display_Text( buffer ); } if ( EnabledDevices & DEBUG_DEVICE_MONO ) { ScrollingScreen.Printf( buffer ); } #ifdef WWDEBUG if ( EnabledDevices & DEBUG_DEVICE_DBWIN32 ) { WWDebug_DBWin32_Message_Handler( buffer ); } #endif // WWDEBUG if ( EnabledDevices & DEBUG_DEVICE_LOG ) { Write_To_File(buffer); } if ( EnabledDevices & DEBUG_DEVICE_WINDOWS ) { OutputDebugString( buffer ); // puts it in the MSVC debug window } } // // // void DebugManager::Display_Script( char const *text, ... ) { if ( !(EnabledTypes & DEBUG_TYPE_SCRIPT) ) return; va_list va; char buffer[256]; va_start(va, text); vsprintf(buffer, text, va); buffer[sizeof(buffer)-1] = '\0'; char buffer2[256]; sprintf( buffer2, "SCRIPT:%s", buffer ); Display( buffer2 ); va_end(va); } /* ** */ void DebugManager::Display_Network_Admin(char const *text, ...) { if (!(EnabledTypes & DEBUG_TYPE_NETWORK_ADMIN)) { return; } va_list va; char buffer[1024]; va_start(va, text); vsprintf(buffer, text, va); buffer[sizeof(buffer)-1] = '\0'; char buffer2[1024]; sprintf(buffer2, "NET ADMIN:%s\n", buffer); Display(buffer2); va_end(va); } /* ** */ void DebugManager::Display_Network_Basic(char const *text, ...) { if (!(EnabledTypes & DEBUG_TYPE_NETWORK_BASIC)) { return; } va_list va; char buffer[1024]; va_start(va, text); vsprintf(buffer, text, va); buffer[sizeof(buffer)-1] = '\0'; char buffer2[1024]; sprintf(buffer2, "NET BASIC:%s\n", buffer); Display(buffer2); va_end(va); } /* ** */ void DebugManager::Display_Network_Prolific(char const *text, ...) { if (!(EnabledTypes & DEBUG_TYPE_NETWORK_PROLIFIC)) { return; } va_list va; char buffer[1024]; va_start(va, text); vsprintf(buffer, text, va); buffer[sizeof(buffer)-1] = '\0'; char buffer2[1024]; sprintf(buffer2, "NET PROLIFIC:%s\n", buffer); Display(buffer2); va_end(va); } /* ** */ void DebugManager::Measure_Frame_Textures( void ) { // WW3D::Flush_Texture_Cache(); } /* ** */ void wwdebug_message_handler(DebugType type, const char * message) { /* ** Hand the message off to the scrolling debug screen */ if ( !DebugManager::Is_Type_Enabled( (DebugManager::DebugType)(1<Display_Text( string, color ); } } void DebugManager::Display_Text( const char * string, const Vector3 & color ) { if (DisplayHandler != NULL) { DisplayHandler->Display_Text( string, Vector4(color[0],color[1],color[2],1.0f) ); } } void DebugManager::Display_Text( const WideStringClass & string, const Vector4 & color ) { if (DisplayHandler != NULL) { DisplayHandler->Display_Text( string, color ); } } void DebugManager::Display_Text( const WideStringClass & string, const Vector3 & color ) { if (DisplayHandler != NULL) { DisplayHandler->Display_Text( string, Vector4(color[0],color[1],color[2],1.0f) ); } } //--------------------------------------------------------------------------- void DebugManager::Init_Logfile(void) { if (IsSlave) { sprintf(LogfileNameBuffer, "%s%s", DefaultRegistryModifier, DEFAULT_LOGFILE_NAME); LOGFILE = LogfileNameBuffer; } // // Destroy contents // FILE * file = fopen(LOGFILE, "wt"); if ( file ) { fclose(file); } } //--------------------------------------------------------------------------- void DebugManager::Write_To_File(LPCSTR str) { // // I open/close for each write so as to maximize integrity of this file. // FILE * file = fopen(LOGFILE, "at"); if (file != NULL) { fwrite(str, 1, strlen(str), file); fclose(file); } } /***************************************************************************************************** ** ** WWMEMLOG support - replacement new and delete operators. See the wwmemlog modules in WWDEBUG.LIB ** for more info! ** *****************************************************************************************************/ /* ** Only install the custom new and delete handlers if WWDEBUG is enabled (debug and profile builds) ** AND _CRTDBG_MAP_ALLOC is not defined (this causes link errors). If you are using _CRTDBG_MAP_ALLOC, ** the memory log stuff cannot be used... */ //#ifdef LOG_MEMORY //#ifdef WWDEBUG #ifndef _CRTDBG_MAP_ALLOC #ifndef PARAM_EDITING_ON #ifndef STEVES_NEW_CATCHER extern "C" { void *gsimalloc(size_t size) { WWMEMLOG(MEM_BINK); return(WWMemoryLogClass::Allocate_Memory(size)); } void gsifree(void *ptr) { WWMEMLOG(MEM_BINK); WWMemoryLogClass::Release_Memory(ptr); } } void * ::operator new (size_t size) { void* memory=NULL; #ifdef LOG_MEMORY #ifdef WWDEBUG memory=WWMemoryLogClass::Allocate_Memory(size); #else memory=FastAllocatorGeneral::Get_Allocator()->Alloc(size); #endif #else memory=FastAllocatorGeneral::Get_Allocator()->Alloc(size); #endif return memory; } void ::operator delete (void *ptr) { #ifdef LOG_MEMORY #ifdef WWDEBUG WWMemoryLogClass::Release_Memory(ptr); #else FastAllocatorGeneral::Get_Allocator()->Free(ptr); #endif #else FastAllocatorGeneral::Get_Allocator()->Free(ptr); #endif } #endif //STEVES_NEW_CATCHER #endif //PARAM_EDITING_ON #endif //!_CRTDBG_MAP_ALLOC //#endif //WWDEBUG //#endif //LOG_MEMORY /* ** ** ** Added 'new' operator. Walks the stack to help track memory leaks. ** ** ** ** ** ** ** */ #include #define WALK_FRAMES 8 struct NewCallerStruct { unsigned long Addresses[WALK_FRAMES]; char AddressName[768]; int AddressLine; }; unsigned long ReturnAddresses[20]; int Stack_Walk(unsigned long *return_addresses, int num_addresses, CONTEXT *); bool Lookup_Symbol(void *code_ptr, char *symbol, int &displacement); void *NewMutex = NULL; #ifdef _DEBUG #ifdef STEVES_NEW_CATCHER extern _CRTIMP void * __cdecl operator new(unsigned int, int, const char *, int); /* ** List of addresses to 'watch'. You can stuff them in here and compile or manually poke them in with the debugger at run time. */ #define NUM_WATCH_ADDRESSES 8 unsigned long WatchAddresses[NUM_WATCH_ADDRESSES] = { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; /* ** This assumes no more than 100000 places in the code that call 'new' */ NewCallerStruct NewAddressList[16384]; /*********************************************************************************************** * operator new -- New operator overload to catch memory leaks. * * * * * * * * INPUT: size of block to allocate * * * * OUTPUT: ptr to allocated memory * * * * WARNINGS: None * * * * HISTORY: * * 6/12/2001 4:31PM ST : Created * *=============================================================================================*/ void* __cdecl operator new(unsigned int s) { static char _localstr[32]; static unsigned long _return_addr; static int _address_strings = 0; static int _index = -1; static int _i, _j; static char _temp_str[512]; static char _compose_str[512]; static int _displacement; static unsigned long _temp_addr; static char _spaces[33] = {" "}; static int _wa; //static CriticalSectionClass *_new_mutex = NULL; /* ** Length of all locals. */ static int _locals_size = 0; /* ** Wait for exclusive access. */ //if (_new_mutex == NULL) { // void *newmem = ::operator new(s, 1, __FILE__, __LINE__); // _new_mutex = new(newmem) CriticalSectionClass; // //} //CriticalSectionClass::LockClass mutex(*_new_mutex); if (NewMutex == NULL) { NewMutex = CreateMutex(NULL, false, NULL); assert(NewMutex != NULL); } if (NewMutex) { WaitForSingleObject(NewMutex, INFINITE); } /* ** Extract the return address. This is the default worst case amount of info we can get about the ** stack. */ __asm { mov eax,_locals_size mov eax,[ebp+eax+4] mov [_return_addr],eax } /* ** Try using imagehlp.dll to walk the stack and get real return addresses for multiple calls. */ memset(ReturnAddresses, 0, WALK_FRAMES * sizeof(ReturnAddresses[0])); int num_frames = 0; /* ** If this is an address we are looking for then do the stack walk. */ #ifdef ONLY_WATCH_ADDRESSES for (_wa = 0 ; _wa= ARRAY_SIZE(NewAddressList)) { void *newmem = ::operator new(s, 1, __FILE__, _address_strings); if (NewMutex) { ReleaseMutex(NewMutex); } return (newmem); } /* ** If we got no good stack info and just have the return address then print that into this ** entry. */ memcpy(NewAddressList[_index].Addresses, ReturnAddresses, WALK_FRAMES * sizeof(ReturnAddresses[0])); if (num_frames == 0) { //NewAddressList[_index].Address = _return_addr; NewAddressList[_index].AddressLine = ReturnAddresses[0]; sprintf(NewAddressList[_index].AddressName, "Addr: %08X ", ReturnAddresses[0]); } else { /* ** Otherwise try and get symbol info for each return address. */ char *ptr = NewAddressList[_index].AddressName; bool ok = true; for (_i=0 ; _i= sizeof(NewAddressList[_index].AddressName)) { break; } strcat(ptr, _compose_str); } } } assert(strlen(NewAddressList[_index].AddressName) < sizeof(NewAddressList[_index].AddressName)); /* ** Call the CRT new to actually allocate the memory. Pass in the string we composed. */ void *newmem = ::operator new(s, 1, NewAddressList[_index].AddressName, NewAddressList[_index].AddressLine); if (NewMutex) { ReleaseMutex(NewMutex); } return(newmem); //return (::operator new(s, 1, __FILE__, __LINE__)); } #endif //STEVES_NEW_CATCHER #endif //_DEBUG #if (0) /* ** Definitions to allow run-time linking to the Imagehlp.dll functions. ** */ typedef BOOL (WINAPI *SymCleanupType) (HANDLE hProcess); typedef BOOL (WINAPI *SymGetSymFromAddrType) (HANDLE hProcess, DWORD Address, LPDWORD Displacement, PIMAGEHLP_SYMBOL Symbol); typedef BOOL (WINAPI *SymInitializeType) (HANDLE hProcess, LPSTR UserSearchPath, BOOL fInvadeProcess); typedef BOOL (WINAPI *SymLoadModuleType) (HANDLE hProcess, HANDLE hFile, LPSTR ImageName, LPSTR ModuleName, DWORD BaseOfDll, DWORD SizeOfDll); typedef DWORD (WINAPI *SymSetOptionsType) (DWORD SymOptions); typedef BOOL (WINAPI *SymUnloadModuleType) (HANDLE hProcess, DWORD BaseOfDll); typedef BOOL (WINAPI *StackWalkType) (DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME StackFrame, LPVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE TranslateAddress); typedef LPVOID (WINAPI *SymFunctionTableAccessType) (HANDLE hProcess, DWORD AddrBase); typedef DWORD (WINAPI *SymGetModuleBaseType) (HANDLE hProcess, DWORD dwAddr); SymCleanupType _SymCleanup = NULL; SymGetSymFromAddrType _SymGetSymFromAddr = NULL; SymInitializeType _SymInitialize = NULL; SymLoadModuleType _SymLoadModule = NULL; SymSetOptionsType _SymSetOptions = NULL; SymUnloadModuleType _SymUnloadModule = NULL; StackWalkType _StackWalk = NULL; SymFunctionTableAccessType _SymFunctionTableAccess = NULL; SymGetModuleBaseType _SymGetModuleBase = NULL; static char const *ImagehelpFunctionNames[] = { "SymCleanup", "SymGetSymFromAddr", "SymInitialize", "SymLoadModule", "SymSetOptions", "SymUnloadModule", "StackWalk", "SymFunctionTableAccess", "SymGetModuleBaseType", NULL }; bool SymbolsAvailable = false; HINSTANCE ImageHelp = (HINSTANCE) -1; /*********************************************************************************************** * Load_Image_Helper -- Load imagehlp.dll and retrieve the programs symbols * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 6/12/2001 4:27PM ST : Created * *=============================================================================================*/ void Load_Image_Helper(void) { /* ** If this is the first time through then fix up the imagehelp function pointers since imagehlp.dll ** can't be statically linked. */ if (ImageHelp == (HINSTANCE)-1) { ImageHelp = LoadLibrary("IMAGEHLP.DLL"); if (ImageHelp != NULL) { char const *function_name = NULL; unsigned long *fptr = (unsigned long *) &_SymCleanup; int count = 0; do { function_name = ImagehelpFunctionNames[count]; if (function_name) { *fptr = (unsigned long) GetProcAddress(ImageHelp, function_name); fptr++; count++; } } while (function_name); } /* ** Retrieve the programs symbols if they are available. This can be a .pdb or a .dbg file. */ if (_SymSetOptions != NULL) { _SymSetOptions(SYMOPT_DEFERRED_LOADS); } int symload = 0; if (_SymInitialize != NULL && _SymInitialize(GetCurrentProcess(), NULL, FALSE)) { if (_SymSetOptions != NULL) { _SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME); } char exe_name[_MAX_PATH]; GetModuleFileName(NULL, exe_name, sizeof(exe_name)); if (_SymLoadModule != NULL) { symload = _SymLoadModule(GetCurrentProcess(), NULL, exe_name, NULL, 0, 0); } if (symload) { SymbolsAvailable = true; } else { //assert (_SymLoadModule != NULL); //DebugString ("SymLoad failed for module %s with code %d - %s\n", szModuleName, GetLastError(), Last_Error_Text()); } } } } /*********************************************************************************************** * Lookup_Symbol -- Get the symbol for a given code address * * * * * * * * INPUT: Address of code to get symbol for * * Ptr to buffer to return symbol in * * Reference to int to return displacement * * * * OUTPUT: True if symbol found * * * * WARNINGS: None * * * * HISTORY: * * 6/12/2001 4:47PM ST : Created * *=============================================================================================*/ bool Lookup_Symbol(void *code_ptr, char *symbol, int &displacement) { /* ** Locals. */ char symbol_struct_buf[1024]; IMAGEHLP_SYMBOL *symbol_struct_ptr = (IMAGEHLP_SYMBOL *)symbol_struct_buf; /* ** Set default values in case of early exit. */ displacement = 0; *symbol = '\0'; /* ** Make sure symbols are available. */ if (!SymbolsAvailable || _SymGetSymFromAddr == NULL) { return(false); } /* ** If it's a bad code pointer then there is no point in trying to match it with a symbol. */ if (IsBadCodePtr((FARPROC)code_ptr)) { strcpy(symbol, "Bad code pointer"); return(false); } /* ** Set up the parameters for the call to SymGetSymFromAddr */ memset (symbol_struct_ptr, 0, sizeof (symbol_struct_buf)); symbol_struct_ptr->SizeOfStruct = sizeof (symbol_struct_buf); symbol_struct_ptr->MaxNameLength = sizeof(symbol_struct_buf)-sizeof (IMAGEHLP_SYMBOL); symbol_struct_ptr->Size = 0; symbol_struct_ptr->Address = (unsigned long)code_ptr; /* ** See if we have the symbol for that address. */ if (_SymGetSymFromAddr(GetCurrentProcess(), (unsigned long)code_ptr, (unsigned long *)&displacement, symbol_struct_ptr)) { /* ** Copy it back into the buffer provided. */ strcpy(symbol, symbol_struct_ptr->Name); return(true); } return(false); } /*********************************************************************************************** * Stack_Walk -- Walk the stack and get the last n return addresses * * * * * * * * INPUT: Ptr to return address list * * Number of return addresses to fetch * * Ptr to optional context. NULL means use current * * * * OUTPUT: Number of return addresses found * * * * WARNINGS: None * * * * HISTORY: * * 6/12/2001 11:57AM ST : Created * *=============================================================================================*/ int Stack_Walk(unsigned long *return_addresses, int num_addresses, CONTEXT *context) { static HINSTANCE _imagehelp = (HINSTANCE) -1; /* ** If this is the first time through then fix up the imagehelp function pointers since imagehlp.dll ** can't be statically linked. */ if (ImageHelp == (HINSTANCE)-1) { Load_Image_Helper(); } /* ** If there is no debug support .dll available then we can't walk the stack. */ if (ImageHelp == NULL) { return(0); } /* ** Set up the stack frame structure for the start point of the stack walk (i.e. here). */ STACKFRAME stack_frame; memset(&stack_frame, 0, sizeof(stack_frame)); unsigned long reg_eip, reg_ebp, reg_esp; __asm { here: lea eax,here mov reg_eip,eax mov reg_ebp,ebp mov reg_esp,esp } stack_frame.AddrPC.Mode = AddrModeFlat; stack_frame.AddrPC.Offset = reg_eip; stack_frame.AddrStack.Mode = AddrModeFlat; stack_frame.AddrStack.Offset = reg_esp; stack_frame.AddrFrame.Mode = AddrModeFlat; stack_frame.AddrFrame.Offset = reg_ebp; /* ** Use the context struct if it was provided. */ if (context) { stack_frame.AddrPC.Offset = context->Eip; stack_frame.AddrStack.Offset = context->Esp; stack_frame.AddrFrame.Offset = context->Ebp; } int pointer_index = 0; /* ** Walk the stack by the requested number of return address iterations. */ for (int i = 0; i < num_addresses + 1; i++) { if (_StackWalk(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(), GetCurrentThread(), &stack_frame, NULL, NULL, _SymFunctionTableAccess, _SymGetModuleBase, NULL)) { /* ** First result will always be the return address we were called from. */ if (i==0 && context == NULL) { continue; } unsigned long return_address = stack_frame.AddrReturn.Offset; return_addresses[pointer_index++] = return_address; } else { break; } } return(pointer_index); } #endif //(0)