|
||||||||||||||||||
Add Forum to Favorites | Send Topic To a Friend | View Forum FAQ | Track this topic |
Last Thread Next Thread ![]() |
| Introduction to Debugging |
|
![]() Muhammad Haggag Moderator Member since: 9/22/2000 From: Redmond, WA, United States |
||||
|
|
||||
| An excellent article. We now have a place to point beginners to as the introduction to debugging. Great job. |
||||
|
||||
![]() ApochPiQ Moderator - General Programming Member since: 7/17/2002 From: Atlanta, GA, United States |
||||
|
|
||||
| Great material! I'd personally offer one minor addition that I feel is extremely important: the single most effective way to prevent bugs is eager failure. Eager failure is a simple principle: if something goes wrong, bring the entire system to a halt as soon as possible, but no sooner. For example, suppose you have a Square Root function, which is called from your AI code to do pathfinding. (This is kind of a bad example because you should just be using a library function, but the illustration is still valid.) Giving a negative number to SquareRoot is an error. There are two different ways you can deal with this: if SquareRoot detects that it has been passed a negative number, it can either return a "dummy value" like 0, or it can crash immediately. Which is better? Obviously nobody wants to see the game crash - but a crash is far easier to contain than the random behavior that you might get by using the dummy value method. If your pathfinding gets a bogus 0 back and doesn't double-check itself, your units might do all kinds of crazy things. Worse, they might not do anything immediately, leaving a bug that takes time to appear. Diagnosing these kinds of bugs is extremely hard and time consuming. In the long run, it's better for the program to "barf" as quickly as possible. Of course, be sure to provide helpful error messages. Maybe a bit closer to home: if your bank's software has a bug when calculating interest on your savings account, would you prefer for their software to crash and leave your account inaccessible for a few hours, or to have $100 a month mysteriously deducted from your account for no apparent reason? Using compiler warnings, strict compiler checks (like typesafe containers), and using assertions are all practices that follow the principle of eager failure. The best bug is one that causes a compiler error: you can fix it before you even run the code. The next best bug is one that fires a runtime error and leaves you an explicit error message to check into. The worst bugs are ones that just happen and rely on someone noticing that something is wrong - like money disappearing from your bank account. Dirge of the Derelict [Work - Egosoft] [Epoch Language] [Journal - Peek into my shattered mind] |
||||
|
||||
![]() FBMachine Member since: 11/25/2005 From: Rohnert Park, CA, United States |
||||
|
|
||||
| Good article, definetly one that needed to be written. :) Off the top of my head, here's a few more things I've seen being used ( whether I personally use them or not ) in debugging - The asm instruction int 3 when the program is attached to the debugger causes the program to break, so you can do something like this - #define ForceBreakpoint() __asm int 3 if you always want to break under some condition. You might for instance want to conditionally compile a special assert macro that always causes the debugger to break when an assertion fails on that line for whatever reason - #ifdef _BREAKONASSERT #define Assert(isTrue) _Assert( TEXT(__FILE__), __LINE__, (isTrue) ); ForceBreakpoint() Not something I generally use, but there you are.. If you want turn __LINE__ into a string to save yourself a step ( since in general you're always going to be using it as a string ), you can coax it into one like this: // cludge for getting the line number as a string #define STRINGIZE(x) #x // expands x then STRINGIZEs it #define EXPAND(x) STRINGIZE(x) // stringize line number by first expanding it #define LINE_STR EXPAND(__LINE__) Then use LINE_STR in place of __LINE__. This for some reason doesn't work correctly if your debug information type is set to edit/continue in MSVC for some reason though... If you want a call stack trace in error reports from testers or end users on exceptions ( or asserts assuming your asserts throw ), there's a few ways to do it, one I've seen was something like this: // hacky callstack trace on exception #define TRY(funcName) static string _message = TEXT(__FILE__) ## TEXT("(") ## TEXT(LINE_STR) ## TEXT(")") ## TEXT(" : stack unwinding in ") ## TEXT(#funcName) ## TEXT("\n"); try { #define CATCH } catch(...) { CallStackTrace.push_back( _message ); throw; } // set to decent initial size to avoid bad_alloc throw std::vector<string> CallStackTrace(2048); And then you can iterate through the vector and dump the strings in your outermost catch block. This of course requires placing try/catch around all of your functions in order to have a thorough report, but you can use a TRYd/CATCHd version for performance critical sections that will be compiled out in your final release. Another way is to use StackWalk64 in Dbghelp.h. Anyways, again it was a well written article, nice job. :) Daniel EDIT: It appears that even the FORUMS hate macros, and I can't get it to format quite right. :D |
||||
|
||||
![]() Evil Steve Moderator Member since: 6/30/2003 From: Glasgow, United Kingdom |
||||
|
|
||||
| As others have said, this is one of the articles that GDNet can really do with. On the topic of asserts, one thing that I found to be annoying about the standard assert() macro, is that when it pops up the dialog box, other threads keep running. I ended up writing my own version of Assert(), which suspends all threads in the current process except for the current thread. That stops everything else freaking out while the user decides what to do about the assert. For anyone who cares: Assert.h: //========================================================================== // Assert.h - Custom assert statement //========================================================================== #ifndef __ASSERT_H__ #define __ASSERT_H__ #ifdef _DEBUG # define Assert(exp) (void)((!!(exp)) || (DbgAssertFailed(#exp,__FILE__,__LINE__,__FUNCTION__), 0) ) #else # define Assert(exp) ((void)0) #endif void DbgAssertFailed(const char* szExp, const char* szFile, unsigned int nLine, const char* szFunc); #endif /* __ASSERT_H__ */ Assert.cpp: //========================================================================== // Assert.cpp - Custom assert statement //========================================================================== #include <string> #include <windows.h> #include <tlhelp32.h> void FreezeAllThreadsExceptThis() { THREADENTRY32 theThread; DWORD dwProcess = GetCurrentProcessId(); HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,dwProcess); if(hSnapshot == INVALID_HANDLE_VALUE) return; theThread.dwSize = sizeof(theThread); if(!Thread32First(hSnapshot,&theThread)) { CloseHandle(hSnapshot); return; } do { if(theThread.th32OwnerProcessID != dwProcess) continue; if(theThread.th32ThreadID != GetCurrentThreadId()) { HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME,FALSE,theThread.th32ThreadID); if(hThread) { SuspendThread(hThread); CloseHandle(hThread); } } theThread.dwSize = sizeof(theThread); } while(Thread32Next(hSnapshot,&theThread)); CloseHandle(hSnapshot); } void ResumeAllThreads() { THREADENTRY32 theThread; DWORD dwProcess = GetCurrentProcessId(); HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,dwProcess); if(hSnapshot == INVALID_HANDLE_VALUE) return; theThread.dwSize = sizeof(theThread); if(!Thread32First(hSnapshot,&theThread)) { CloseHandle(hSnapshot); return; } do { if(theThread.th32OwnerProcessID != dwProcess) continue; if(theThread.th32ThreadID != GetCurrentThreadId()) { HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME,FALSE,theThread.th32ThreadID); if(hThread) { ResumeThread(hThread); CloseHandle(hThread); } } theThread.dwSize = sizeof(theThread); } while(Thread32Next(hSnapshot,&theThread)); CloseHandle(hSnapshot); } void DbgAssertFailed(const char* szExp, const char* szFile, unsigned int nLine, const char* szFunc) { char szBuff[32]; if(!szFile) szFile = "??"; const char* szRealFile = strrchr(szFile,'\\'); if(!szRealFile) szRealFile = szFile; else szRealFile++; std::string str("Assertion failed:\nFile: "); sprintf(szBuff,"%d (",nLine); str += szRealFile; str += ":"; str += szBuff; str += szFunc; str += ")\nExpression:\n"; str += szExp; str += "\n\nDebug?"; FreezeAllThreadsExceptThis(); if(MessageBox(NULL,str.c_str(),"Assertion failed!",MB_YESNO|MB_ICONEXCLAMATION) == IDYES) _asm {int 3}; ResumeAllThreads(); } One thing to note is that it doesn't play nicely if you suspend threads in your application. I haven't needed to yet, so I haven't bothered implementing that work-around, but it'd be simple enough to build a std::list or std::vector of thread IDs to resume instead of resuming them all. |
||||
|
||||
![]() Emmanuel Deloget GDNet News Lead Member since: 8/27/2003 From: France |
||||
|
|
||||
A few tip for the watch window:
These are not documented very well, so I guess you'll find the list useful. Regards, -- Emmanuel D. [blog, in French] [blog, very bad googlized translation] [NEW: English version of teh blog! (WIP)] |
||||
|
||||
![]() jollyjeffers Moderator - DirectX and XNA Member since: 3/16/2000 From: London, United Kingdom |
||||
|
|
||||
Excellent - I've been waiting for this to appear... I even had a placeholder for it in the DX FAQ ready and waiting ![]() Keep up the good work! Jack Jack Hoxley [ Forum FAQ | Revised FAQ | MVP Profile | Developer Journal ] |
||||
|
||||
![]() soconne Member since: 6/23/2003 |
||||
|
|
||||
I found this section EXTREMELY useful.Quote: |
||||
|
||||
![]() swiftcoder Member since: 7/3/2003 From: Boston, MA, United States |
||||
|
|
||||
| Very nice article, good work! GameDev has needed an introduction to debuging for a while, finally we have a link to reffer newcomers to. |
||||
|
||||
![]() tksuoran Member since: 3/12/2004 From: Espoo, Finland |
||||
|
|
||||
Quote: Or you could simply use DebugBreak(). The article did not? mention memory trashing bugs. Those that appear to work properly on Debug builds but crash on release builds. Or the code might differently if you just attach debugger or add single debug print. These are the worst to debug IMHO. |
||||
|
||||
![]() superpig GDNet Technical Lead Member since: 5/26/2001 From: Oxford, United Kingdom |
||||
|
|
||||
Quote: The only minor inconvenience with DebugBreak, IIRC, is that it shows up on the call stack - so the frame you get given immediately after the break is almost always one call too deep compared to what you care about. __asm int 3, on the other hand, doesn't touch the stack, so you can insert it in the middle of a function and get the right stack out of it. |
||||
|
||||
![]() Spintz Member since: 2/18/2005 From: USA |
||||
|
|
||||
Quote: You'll find this more useful - http://www.nobugs.org/developer/win32/debug_crt_heap.html |
||||
|
||||
All times are ET (US)![]() |
Last Thread Next Thread ![]() |
|