Browse Source

Adds a warning comment to Game to prevent false memory leaks from being reported in the future.
Adds documentation to the displaySplash macro.
Adds the ability to track stack traces in DebugMem mode.

Chris Culy 13 years ago
parent
commit
35f2d5128c
4 changed files with 182 additions and 7 deletions
  1. 148 1
      gameplay/src/DebugNew.cpp
  2. 14 0
      gameplay/src/DebugNew.h
  3. 6 5
      gameplay/src/Game.cpp
  4. 14 1
      gameplay/src/Game.h

+ 148 - 1
gameplay/src/DebugNew.cpp

@@ -5,6 +5,15 @@
 #include <cstdio>
 #include <cstdio>
 #include <cstdarg>
 #include <cstdarg>
 
 
+#ifdef WIN32
+#include <windows.h>
+#include <dbghelp.h>
+#pragma comment(lib,"dbghelp.lib")
+
+#define MAX_STACK_FRAMES 16
+bool __trackStackTrace = false;
+#endif
+
 struct MemoryAllocationRecord
 struct MemoryAllocationRecord
 {
 {
     unsigned long address;          // address returned to the caller after allocation
     unsigned long address;          // address returned to the caller after allocation
@@ -13,6 +22,10 @@ struct MemoryAllocationRecord
     int line;                       // source line of the allocation request
     int line;                       // source line of the allocation request
     MemoryAllocationRecord* next;
     MemoryAllocationRecord* next;
     MemoryAllocationRecord* prev;
     MemoryAllocationRecord* prev;
+#ifdef WIN32
+    bool trackStackTrace;
+    unsigned int pc[MAX_STACK_FRAMES];
+#endif
 };
 };
 
 
 MemoryAllocationRecord* __memoryAllocations = 0;
 MemoryAllocationRecord* __memoryAllocations = 0;
@@ -99,6 +112,54 @@ void* debugAlloc(std::size_t size, const char* file, int line)
     rec->next = __memoryAllocations;
     rec->next = __memoryAllocations;
     rec->prev = 0;
     rec->prev = 0;
 
 
+    // Capture the stack frame (up to MAX_STACK_FRAMES) if we 
+    // are running on Windows and the user has enabled it.
+#if defined(WIN32)
+    rec->trackStackTrace = __trackStackTrace;
+    if (rec->trackStackTrace)
+    {
+        static bool initialized = false;
+        if (!initialized)
+        {
+            if (!SymInitialize(GetCurrentProcess(), NULL, true))
+                gameplay::printError("Stack trace tracking will not work.");
+            initialized = true;
+        }
+    
+        // Get the current context (state of EBP, EIP, ESP registers).
+        static CONTEXT context;
+        RtlCaptureContext(&context);
+    
+        static STACKFRAME64 stackFrame;
+        memset(&stackFrame, 0, sizeof(STACKFRAME64));
+
+        // Initialize the stack frame based on the machine architecture.
+#ifdef _M_IX86
+        static const DWORD machineType = IMAGE_FILE_MACHINE_I386;
+        stackFrame.AddrPC.Offset = context.Eip;
+        stackFrame.AddrPC.Mode = AddrModeFlat;
+        stackFrame.AddrFrame.Offset = context.Ebp;
+        stackFrame.AddrFrame.Mode = AddrModeFlat;
+        stackFrame.AddrStack.Offset = context.Esp;
+        stackFrame.AddrStack.Mode = AddrModeFlat;
+#else
+#error "Machine architecture not supported!"
+#endif
+
+        // Walk up the stack and store the program counters.
+        memset(rec->pc, 0, sizeof(rec->pc));
+        for (int i = 0; i < MAX_STACK_FRAMES; i++)
+        {
+            rec->pc[i] = stackFrame.AddrPC.Offset;
+            if (!StackWalk64(machineType, GetCurrentProcess(), GetCurrentThread(), &stackFrame,
+                &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
+            {
+                break;
+            }
+        }
+    }
+#endif
+
     if (__memoryAllocations)
     if (__memoryAllocations)
         __memoryAllocations->prev = rec;
         __memoryAllocations->prev = rec;
     __memoryAllocations = rec;
     __memoryAllocations = rec;
@@ -137,6 +198,70 @@ void debugFree(void* p)
     free(mem);
     free(mem);
 }
 }
 
 
+#ifdef WIN32
+void printStackTrace(MemoryAllocationRecord* rec)
+{
+    const unsigned int bufferSize = 512;
+
+    // Resolve the program counter to the corresponding function names.
+    unsigned int pc;
+    for (int i = 0; i < MAX_STACK_FRAMES; i++)
+    {
+        // Check to see if we are at the end of the stack trace.
+        pc = rec->pc[i];
+        if (pc == 0)
+            break;
+
+        // Get the function name.
+        unsigned char buffer[sizeof(IMAGEHLP_SYMBOL64) + bufferSize];
+        IMAGEHLP_SYMBOL64* symbol = (IMAGEHLP_SYMBOL64*)buffer;
+        DWORD64 displacement;
+        memset(symbol, 0, sizeof(IMAGEHLP_SYMBOL64) + bufferSize);
+        symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
+        symbol->MaxNameLength = bufferSize;
+        if (!SymGetSymFromAddr64(GetCurrentProcess(), pc, &displacement, symbol))
+        {
+            gameplay::printError("[memory] STACK TRACE: <unknown location>");
+        }
+        else
+        {
+            symbol->Name[bufferSize - 1] = '\0';
+
+            // Check if we need to go further up the stack.
+            if (strncmp(symbol->Name, "operator new", 12) == 0)
+            {
+                // In operator new or new[], keep going...
+            }
+            else
+            {
+                // Get the file and line number.
+                if (pc != 0)
+                {
+                    IMAGEHLP_LINE64 line;
+                    DWORD displacement;
+                    memset(&line, 0, sizeof(line));
+                    line.SizeOfStruct = sizeof(line);
+                    if (!SymGetLineFromAddr64(GetCurrentProcess(), pc, &displacement, &line))
+                    {
+                        gameplay::printError("[memory] STACK TRACE: %s - <unknown file>:<unknown line number>", symbol->Name);
+                    }
+                    else
+                    {
+                        const char* file = strrchr(line.FileName, '\\');
+                        if(!file) 
+                            file = line.FileName;
+                        else
+                            file++;
+                        
+                        gameplay::printError("[memory] STACK TRACE: %s - %s:%d", symbol->Name, file, line.LineNumber);
+                    }
+                }
+            }
+        }
+    }
+}
+#endif
+
 extern void printMemoryLeaks()
 extern void printMemoryLeaks()
 {
 {
     // Dump general heap memory leaks
     // Dump general heap memory leaks
@@ -150,10 +275,32 @@ extern void printMemoryLeaks()
         MemoryAllocationRecord* rec = __memoryAllocations;
         MemoryAllocationRecord* rec = __memoryAllocations;
         while (rec)
         while (rec)
         {
         {
-            gameplay::printError("[memory] LEAK: HEAP allocation leak of size %d leak from line %d in file '%s'.", rec->size, rec->line, rec->file);
+#ifdef WIN32
+            if (rec->trackStackTrace)
+            {
+                gameplay::printError("[memory] LEAK: HEAP allocation leak at address %#x of size %d:", rec->address, rec->size);
+                printStackTrace(rec);
+            }
+            else
+                gameplay::printError("[memory] LEAK: HEAP allocation leak at address %#x of size %d from line %d in file '%s'.", rec->address, rec->size, rec->line, rec->file);
+#else
+            gameplay::printError("[memory] LEAK: HEAP allocation leak at address %#x of size %d from line %d in file '%s'.", rec->address, rec->size, rec->line, rec->file);
+#endif
             rec = rec->next;
             rec = rec->next;
         }
         }
     }
     }
 }
 }
 
 
+#if defined(WIN32)
+void setTrackStackTrace(bool trackStackTrace)
+{
+    __trackStackTrace = trackStackTrace;
+}
+
+void toggleTrackStackTrace()
+{
+    __trackStackTrace = !__trackStackTrace;
+}
+#endif
+
 #endif
 #endif

+ 14 - 0
gameplay/src/DebugNew.h

@@ -89,4 +89,18 @@ T* bullet_new(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9)
 #endif
 #endif
 }
 }
 
 
+#if defined(WIN32)
+/**
+ * Sets whether stack traces are tracked on memory allocations or not.
+ * 
+ * @param trackStackTrace Whether to track the stack trace on memory allocations.
+ */
+void setTrackStackTrace(bool trackStackTrace);
+
+/**
+ * Toggles stack trace tracking on memory allocations.
+ */
+void toggleTrackStackTrace();
+#endif
+
 #endif
 #endif

+ 6 - 5
gameplay/src/Game.cpp

@@ -21,6 +21,7 @@ Game::Game()
 {
 {
     assert(__gameInstance == NULL);
     assert(__gameInstance == NULL);
     __gameInstance = this;
     __gameInstance = this;
+    _timeEvents = new std::priority_queue<TimeEvent, std::vector<TimeEvent>, std::less<TimeEvent> >();
 }
 }
 
 
 Game::Game(const Game& copy)
 Game::Game(const Game& copy)
@@ -31,7 +32,7 @@ Game::~Game()
 {
 {
     // Do not call any virtual functions from the destructor.
     // Do not call any virtual functions from the destructor.
     // Finalization is done from outside this class.
     // Finalization is done from outside this class.
-
+    delete _timeEvents;
 #ifdef GAMEPLAY_MEM_LEAK_DETECTION
 #ifdef GAMEPLAY_MEM_LEAK_DETECTION
     Ref::printLeaks();
     Ref::printLeaks();
     printMemoryLeaks();
     printMemoryLeaks();
@@ -280,7 +281,7 @@ void Game::schedule(long timeOffset, TimeListener* timeListener, void* cookie)
 {
 {
     assert(timeListener);
     assert(timeListener);
     TimeEvent timeEvent(getGameTime() + timeOffset, timeListener, cookie);
     TimeEvent timeEvent(getGameTime() + timeOffset, timeListener, cookie);
-    _timeEvents.push(timeEvent);
+    _timeEvents->push(timeEvent);
 }
 }
 
 
 bool Game::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
 bool Game::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
@@ -304,15 +305,15 @@ void Game::updateOnce()
 
 
 void Game::fireTimeEvents(long frameTime)
 void Game::fireTimeEvents(long frameTime)
 {
 {
-    while (_timeEvents.size() > 0)
+    while (_timeEvents->size() > 0)
     {
     {
-        const TimeEvent* timeEvent = &_timeEvents.top();
+        const TimeEvent* timeEvent = &_timeEvents->top();
         if (timeEvent->time > frameTime)
         if (timeEvent->time > frameTime)
         {
         {
             break;
             break;
         }
         }
         timeEvent->listener->timeEvent(frameTime - timeEvent->time, timeEvent->cookie);
         timeEvent->listener->timeEvent(frameTime - timeEvent->time, timeEvent->cookie);
-        _timeEvents.pop();
+        _timeEvents->pop();
     }
     }
 }
 }
 
 

+ 14 - 1
gameplay/src/Game.h

@@ -392,7 +392,9 @@ private:
     AudioController* _audioController;          // Controls audio sources that are playing in the game.
     AudioController* _audioController;          // Controls audio sources that are playing in the game.
     PhysicsController* _physicsController;      // Controls the simulation of a physics scene and entities.
     PhysicsController* _physicsController;      // Controls the simulation of a physics scene and entities.
     AudioListener* _audioListener;              // The audio listener in 3D space.
     AudioListener* _audioListener;              // The audio listener in 3D space.
-    std::priority_queue<TimeEvent, std::vector<TimeEvent>, std::less<TimeEvent> > _timeEvents; // Contains the scheduled time events.
+    std::priority_queue<TimeEvent, std::vector<TimeEvent>, std::less<TimeEvent> >* _timeEvents; // Contains the scheduled time events.
+
+    // Note: Do not add STL object member variables on the stack; this will cause false memory leaks to be reported.
 
 
     friend class SplashDisplayer;
     friend class SplashDisplayer;
 };
 };
@@ -425,6 +427,17 @@ private:
     long _startTime;
     long _startTime;
 };
 };
 
 
+/**
+ * Displays a splash screen using the {@link Game#renderOnce} mechanism for at least the given amount
+ * of time. This function is intended to be called at the beginning of a block of code that is be 
+ * executed while the splash screen is displayed (i.e. Game#initialize). This function will block 
+ * at the end of the block of code in which it is called for the amount of time that has not yet elapsed.
+ * 
+ * @param instance See {@link Game#renderOnce}.
+ * @param method See {@link Game#renderOnce}.
+ * @param cookie See {@link Game#renderOnce}.
+ * @param time The minimum amount of time to display the splash screen (in milliseconds).
+ */
 #define displaySplash(instance, method, cookie, time) \
 #define displaySplash(instance, method, cookie, time) \
     SplashDisplayer __##instance##SplashDisplayer; \
     SplashDisplayer __##instance##SplashDisplayer; \
     __##instance##SplashDisplayer.run(instance, method, cookie, time)
     __##instance##SplashDisplayer.run(instance, method, cookie, time)