Debugging Basics


Before you learn some debugging tricks, you should know a little about how the debugger works and how to use it. Almost every computer has special assembly language instructions or CPU features that enable debugging. The Intel platform is no exception. A debugger works by stopping execution of a target program, and associating memory locations and values with variable names. This association is possible through symbolic information that is generated by the compiler. One human readable form of this information is a MAP file. Here's an example of a MAP file generated by the linker in Visual Studio.NET:

 Sample  Timestamp is 3c0020f3 (Sat Nov 24 16:36:35 2001)  Preferred load address is 00400000  Start         Length     Name                   Class  0001:00000000 000ab634H .text                   CODE  0001:000ab640 00008b5fH .text$AFX_AUX           CODE  0001:000b41a0 0000eec3H .text$AFX_CMNCTL        CODE  0002:00000000 000130caH .rdata                  DATA  0002:000130d0 00006971H .rdata$r                DATA  0002:000275d0 00000000H .edata                  DATA  0003:00000000 00000104H .CRT$XCA                DATA  0003:00000104 00000109H .CRT$XCC                DATA  0003:00001120 00026e6aH .data                   DATA  0003:00027f90 00011390H .bss                    DATA  0004:00000000 00000168H .idata$2                DATA  0004:00000168 00000014H .idata$3                DATA  0005:00000000 00000370H .rsrc$01                DATA  Address         Publics by Value              Rva+Base     Lib:Object  0001:00000b80       ??0GameApp@@QAE@XZ         00401b80 f   GameApp.obj  0001:00000ca0       ??_EGameApp@@UAEPAXI@Z     00401ca0 f i GameApp.obj  0001:00000ca0       ??_GGameApp@@UAEPAXI@Z     00401ca0 f i GameApp.obj  0001:00000d10       ??1GameApp@@UAE@XZ         00401d10 f   GameApp.obj  0001:00000e20       ?OnClose@GameApp@@UAEXXZ   00401e20 f   GameApp.obj  0001:00000ec0       ?OnRun@GameApp@@UAE_NXZ    00401ec0 f   GameApp.obj  0001:00001a10       ??0CFileStatus@@QAE@XZ     00402a10 f i GameApp.obj  0001:00001d00       ?OnIdle@GameApp@@UAEHJ@Z   00402d00 f   GameApp.obj  0001:00001e30       ?Update@GameApp@@UAEXK@Z   00402e30 f   GameApp.obj 

The file maps the entire contents of the process as it is loaded into memory. The first section describes global data. The second section, which is much more interesting and useful, describes the memory addresses of methods and functions in your game.

Notice first that the symbol names are "munged." These are the actual name of the methods after the C++ symbol manager incorporates the class names and variable types into the names. The number that appears right after the name is the actual memory address of the entry point of the code. For example, the last function in the MAP file, ?Update@GameApp@@UAEXK@Z, is loaded into memory addess 0x00402e30. You can use that information to track down crashes.

Have you ever seen a crash that reports the register contents? Usually you'll see the entire set of registers: EAX, EBX, and so on. You'll also see EIP, the extended instruction pointer. You many have thought that dialog box nothing more than an annoyance—a slap in the face that your program is flawed. Used with the MAP file, you can at least find the name of the function that caused the crash. Here's how:

  1. Assume the crash dialog reported an EIP of 0x00402d20.

  2. Look at the MAP file above, you'll see that GameApp::OnIdle has an entry point of 0x00402d00, and GameApp::Update has an entry point of 0x00402e30.

  3. The crash thus happened somewhere inside GameApp::OnIdle, since it is located in between those two entry points.

A debugger uses a much more complete symbol table. For example, Visual Studio.NET stores these symbols in a PDB file, or program database file. That's one of the reasons it's so huge: It stores symbolic information of every identifier in your program. The debugger can use this information to figure out how to display the contents of local and global variables and figure out what source code to display as you step through the code. This doesn't explain how the debugger stops the debugged application cold in its tracks, however. That trick requires a little help from the CPU and a special interrupt instruction. If you use Visual Studio, you can try this little program:

 void main() {    __asm int 3 } 

You may have never seen a line of code that looks like this. It is a special line of code that allows inline assembly. The assembly statement evokes the breakpoint interrupt. Without dragging you through all the gory details of interrupt tables, suffice it to say that a program with sufficient privileges can "trap" interrupts, so that when they are evoked, a special function is called. This is almost exactly like registering a callback function, but it happens at a hardware level. DOS-based games used to grab interrupts all the time to redirect functions such as the mouse or display system to their own evil ends. Debuggers trap the breakpoint interrupt, and whenever you set a breakpoint the debugger overwrites the opcodes, or the machine level instructions, at the breakpoint location with those that correspond to __asm int 3. When the breakpoint is hit, control is passed to the debugger and it puts the original instructions back. If you hit the "Step into" or "Step over" commands, the debugger finds the right locations for a new breakpoint and simply puts it there without you ever being the wiser.

Best Practice

I've found it useful to add hardcoded breakpoints, like the one in the earlier code example, to functions in the game. It can be convenient to set one to make sure that if control ever passes through that section of code, the debugger will always trap it. If a debugger is not present, it has no effect whatsoever.

So now you have the most basic understanding of how a debugger does its work. It has a mechanism to stop a running program in its tracks and it uses a compiler and linker generated data file to present symbolic information to programmers.

Using the Debugger

When you debug your code, you usually set a few breakpoints and watch the contents of variables. You have a pretty good idea of what should happen and you'll find bugs when you figure out why the effect of your logic isn't what you planned. This assumes a few things. First, you know where to set the breakpoints, and second, you can interpret the effect the logic has on the state of your game. These two things are by no means trivial in all cases. This problem is made difficult by the size and complexity of the logic.

Gotcha

It's not necessarily true that a screwed up sound effect has anything at all to do with the sound system. It could be a problem with the code that loads the sound from the game data files or it could be a random memory "trasher" that changed the sound effect after it was loaded. The problem might also be a bad sound driver or it might even be a bogus sound effect file from the original recording. Knowing where to look first has more to do with gut feeling than anything else, but good debugger skills can certainly speed up the process of traversing the fault tree—a catch phrase NASA uses to describe all possible permutations of a possible systems failure.

Debuggers like the one in Visual Studio.NET can present an amazing amount of information as shown in Figure 12.1.

click to expand
Figure 12.1: Using the Visual Studio.NET Debugger.

The debugger provides some important windows beyond the normal source code window you will use all of the time.

  • Call Stack: From bottom to top, this window shows the functions and parameters that were used to call them. The function at the top of the list is the one you are currently running. It's extremely useful to double click on any row of the call stack window; the location of the function call will be reflected in the source code window. This helps you understand how control passes from the caller to the called.

  • Watch/Locals/etc: These windows let you examine the contents of variables. Visual Studio.NET has some convenient windows like Locals and This that keep track of specific variables so you don't have to type them in yourself.

  • Breakpoints: This window shows the list of breakpoints. Sometimes you want to enable/disable every breakpoint in your game at once or perform other bits of homework.

  • Threads: This is probably the best addition to the debug window set in Visual Studio.NET. Most games run multiple threads to manage the sound system, resource caching, or perhaps the AI. If the debugger hits a breakpoint or is stopped, this window will show you what thread is running. This window is the only way to distinguish between different threads of execution and is critical to debugging multithreaded applications. If you double click on any line in this window, the source window will change to show the current execution position of that thread.

  • Disassembly: This is a window that shows the assembly code for the current function. Sometimes you need to break a C++ statement down into its component parts to debug it, or perhaps skip over a portion of the statement. I'll have more to say about those techniques later.

Beyond the windows, there are some actions that you'll need to know how to perform.

  • Set/Clear Breakpoints: A basic debugging skill.

  • Stepping the Instruction Pointer: These are usually controlled by hotkeys because they are so frequently used. Debuggers will let you execute code one line at a time, and either trace into functions or skip over them (F11 and F10). There's also a really useful command that will let you step out of a current function (Shift-F11) without having to watch each line execute.

  • Setting the Instruction Pointer: This takes a little care to use properly, since you can mess up the stack. I like to use it to skip over function calls or skip back to a previous line of code, so I can watch it execute again.

As we run through some debugging techniques I'll refer to these windows and actions. If you don't know how to do them in your debugger now is a good time to read the docs and figure it out.

Installing Windows Symbol Files

If you've ever had a program crash deep in some Windows API call, your call stack might look like this:

 ntdll.dll!77f60b6f() ntdll.dll!77fb4dbd() ntdll.dll!77f79b78() ntdll.dll!77fb4dbd() 

Useless, right? Yes, that call stack is useless, but only because you didn't install the Windows symbol files. Even though I write letters to Bill Gates every day, Microsoft still hasn't published the source code for pretty much anything they ever wrote. They have, in their infinite wisdom, graciously supplied the next best thing.

You can install the debug symbols for your operating system, and that indecipherable call stack will turn into something you and I can read. Here's the same debug stack after the debug symbols have been installed:

 ntdll.dll!_RtlDispatchException@8()  + 0x6 ntdll.dll!_KiUserExceptionDispatcher@8()  + 0xe 0003132.8() ntdll.dll!ExecuteHandler@20()  + 0x24 ntdll.dll!_KiUserExceptionDispatcher@8()  + 0xe 000316f4() ntdll.dll!ExecuteHandler@20()  + 0x24 ntdll.dll!_KiUserExceptionDispatcher@8()  + 0xe 00031ac0() 

You might not know exactly what that call stack represents, but now you have a name of a function to help you. You can search the Web or MSDN for help, whereas before you installed the debug symbols you had nothing but a number.

There are a few ways to install debug symbols. You can install them from the Visual Studio .NET CD-ROM or you can download them from MSDN. Search for "System Debug Symbols" and you're sure to find them. The last time I downloaded them they were more than 170Mb, so make sure you have reasonable bandwidth. Once you have the right symbols installed for your OS, the debugger will happily report when it finds them when you begin a debug session:

 'BCard2d.exe': Loaded 'C:\WINDOWS\system32\ntdll.dll', Symbols loaded. 'BCard2d.exe': Loaded 'C:\WINDOWS\system32\kernel32.dll', Symbols loaded. 'BCard2d.exe': Loaded 'C:\WINDOWS\system32\gdi32.dll', Symbols loaded. Etc, etc, 

The problem with this solution is that the symbols you install will eventually become stale since they won't reflect any changes in your operating system as you update it with service packs. You can find out why symbols aren't loading for any EXE or DLL with the help of DUMPBIN.EXE, a utility included with Visual Studio. Use the /PDBPATH:VERBOSE switch as shown here:

 Microsoft (R) COFF/PE Dumper Version 7.00.9466 Copyright (C) Microsoft Corporation.  All rights reserved. Dump of file c:\windows\system32\user32.dll File Type: DLL   PDB file 'c:\windows\system32\user32.pdb' checked.  (File not found)   PDB file 'user32.pdb' checked.  (File not found)   PDB file 'C:\WINDOWS\symbols\dll\user32.pdb' checked.  (PDB signature mismatch)   PDB file 'C:\WINDOWS\dll\user32.pdb' checked.  (File not found)   PDB file 'C:\WINDOWS\user32.pdb' checked.  (File not found)   Summary         2000 .data         4000 .reloc        2B000 .rsrc        54000 .text 

Do you see the PDB signature mismatch line about halfway down this output? That's what happens when the user32.pdb file is out of synch with the user32.dll image on your computer. It turns out this is easy to fix, mainly because Microsoft engineers had this problem multiplied by about 100,000. They have thousands of applications out there with sometimes hundreds of different builds. How could they ever hope to get the debug symbols straight for all these things? They came up with a neat solution called the

Microsoft Symbol Server. It turns out you can use this server too; here's how:

First, install the Microsoft Debugging Tools which can be found at www.microsoft.com/ddk/debugging. Use the SYMCHK utility to pull the latest symbol information from Microsoft that matches a single EXE or DLL, or all of them in your Windows directory. Don't grab them all, though, if you can help it because you'll be checking and downloading hundreds of files. Here's how to grab an individual file:

 C:\Program Files\Debugging Tools for Windows>symchk c:\windows\system32\user32.dll /s SRV*c:\windows\symbols*http://msdl.microsoft.com/download/symbols SYMCHK: FAILED files = 0 SYMCHK: PASSED + IGNORED files = 1 

This crazy utility doesn't actually put the new USER32.DLL where you asked: It actually stuck it in C:\WINDOWS\Symbols\user32.pdb\3DB6D4ED1, which VS.NET will never find. The reason it does this is to keep all the USER32.PDB files from different operating systems or different service packs apart. If you installed the Windows symbols from MSDN into the default location, you'll want to copy it back into the directory C:\Windows\Symbols\dll where VS.NET will find it.

You can also set up your own symbol server, and even include symbols for your own applications. To find out how to do this, go up to http://msdn.microsoft.com and search for "Microsoft Symbol Server."

Debugging Full Screen Games

As much work as the Microsoft DirectX team has put into the effort of helping you debug full screen games, this still doesn't work very well if you have a single monitor setup. This has nothing to do with the folks on DirectX; it has more to do with Visual Studio.NET not overriding exclusive mode of the display. One manifestation of the problem occurs when your game hits a breakpoint while it's in full-screen mode. The game stops cold but the computer doesn't switch focus to the debugger. Basically, the only thing you can do at this point is tap the F5 button to resume execution of the game.

If your game runs exclusively in full-screen mode, your only solution is a multi-monitor setup. At least one programmer should have two monitors: one for displaying the game screen and the other for displaying the debugger. DirectX will use the primary display for full-screen mode by default. It is possible to write code that enumerates the display devices so your game can choose the best display. This is a good idea because you can't count on players setting their display properties up in the way that benefits your game. If your game runs in windowed mode as well as full-screen mode you have a few more options, even in a single monitor setup.

Gotcha

Most of the bugs in full-screen mode happen as a result of switching from full-screen to windowed mode or vice versa. This happens because DirectX surfaces are lost and need to be restored after the switch, something that is easily forgotten by coders. Another problem that happens as a result of the switch is that surfaces can have the wrong pixel format. There's no guarantee that the full-screen pixel depth and format is identical to that of windowed mode. When the switch happens, lost or invalid surfaces refuse to draw and return errors. Your program might handle these errors by exiting or attempting to restore all the surfaces again. Of course, since the surface in question won't get restored in the first place, your game might get caught in a weird obsessive and repetitive attempt to fix something that can't be fixed.

It would be nice if you could simulate this problem entirely in windowed mode. To a large extent, you can. If you've followed the advice of the DirectX SDK, you always check your display surfaces to see if they have been lost before you perform any action on them. It turns out that if you change your display settings while your game is running in windowed mode you will essentially simulate the event of switching between windowed mode and full-screen mode. There are a few windows messages your game should handle to make this a little easier. You'll need to handle the message that is sent when the display changes and the one that signifies gain and loss of focus.

Best Practice

About 90% of all full-screen display bugs can be found and solved with a single monitor setup using windowed mode. The other 10% can only be solved with a multi-monitor setup or via remote debugging. It's much easier to debug these problems on a multi-monitor rig, so make sure that at least one programmer has two monitors.

Remote Debugging

One solution for debugging full screen only games is remote debugging. The game runs on one computer and communicates to your development box via your network. One interesting thing about this setup is that it is as close to a pristine runtime environment as you can get—another way of saying it's very close to the environment people have when actually playing the game. I don't know about you, but people like my Mom don't have a copy of Visual Studio lying around. The presence of a debugger can subtly change the runtime environment, something that can make the hardest, nastiest bugs very difficult to find.

Remote debugging is a pain in the butt, not because it's hard to set up but because you have to make sure that the most recent version of your game executable is easily available for the remote machine. Most debuggers have a mechanism for remote debugging, and Visual Studio.NET is no exception. The dirty secret is that Visual Studio.NET doesn't run on Win9x architectures, which includes Windows 95, Windows 98, and Windows ME. Not that you'd want to run on these finicky operating systems, but it is a little surprising that remote debugging is your only choice if you want to find OS-specific bugs in a VS.NET compiled application.

Best Practice

If you have a fast network, 100 BaseT Ethernet or better, you can share a directory on your development machine and have the Win9x machine read your game's executable and data files right where you develop. If your network is slower, such as a 10 BaseT Ethernet, it's going to be faster to copy the entire image of your game over to the test machine and run it from there. The only problem with this solution is that you have to constantly copy files from your development box over to the text machine, and it's easy to get confused regarding which files have been copied where. On a fast network you can also eliminate file copying by sharing your development directory so the remote machine can directly access the most recent build.

On the remote system you will run a little utility that serves as a communications conduit for your debugger. This utility for VS.NET is called MSVCMON.EXE. Run a search for this file where you installed VS.NET, and copy the contents of the entire directory to a shared folder on your primary development machine. The utility runs on the remote machine, and a convenient way to get it there is to place it in a shared spot on your development machine. MSVCMON.EXE requires some of the DLLs in that directory and it's small enough to just copy the whole thing to the remote machine.

Once you have all the files in place, here's how you set up the remote debugger under VS.NET:

  1. Go into your project's property pages by right clicking on your project in the solution window and select Properties.

  2. Click on the Debugging selection under Configuration.

  3. Under Remote Settings do the following:

    1. Set the Connection to Remote via TCP/IP.

    2. Set the Remote Machine to the IP address of the test machine. You can find the IP address of any machine by running "ipconfig" in a command line window.

    3. Set the Remote Command to the full path specification of the executable. For example, if you copied your game to a shared directory on the development machine this value might look something like this: \\mrmike8000\shared\game\bin\game.exe.

  4. Under Action do the following:

    • Set the Working Directory to the full path specification of the game's executable. Following the example above, this would be set to: \\mrmike8000\shared\game\bin.

  5. Enter a command window and run MSVCMON, with the -alluser switch as shown in Figure 12.2.

    click to expand
    Figure 12.2: Running MSVCMON with the -alluser Switch.

The remote machine is ready to start your game. Start the debugging session on your development machine (F5 in VS.NET) and you'll see your game initialize on the remote machine. When you find a bug and rebuild your application, make sure that the remote machine has access to the most recent bits.

Debugging Mini-Dumps

Unix programmers have had a special advantage over Win32 programmers since the beginning of time. When a Unix program crashes, the operating system copies the entire memory image of the crashed program to disk. This is called a core dump.

Needless to say, it is usually quite large. Unix debuggers can read the core dump and allow a programmer to look at the state of the process at the moment the crash occurred. Assuming the symbol files for the executable in question are available, they can see the entire call stack and even find the contents of local variables. This doesn't always expose the bug entirely, as some crashes can happen as a result of a bug's misbehavior in a completely separate part of the program, but this information is much better than a tester just telling you the game crashed.

Win32 dump files have been debuggable by a little known Windows debugger called WinDBG since the Windows NT days. These dump files were just as huge as the Unix core dumps. It didn't matter very much, since most Windows developers didn't even know that WinDBG even existed: They always used the debugger in Visual Studio.

If you've been running Windows XP lately you've also noticed an odd behavior when applications crash: A little dialog box appears asking you if you want to send the crash information to Microsoft. One button click and a few short seconds later and the dialog thanks you for your cooperation. What in the heck is going on here? Windows XP is sending a mini-dump of the crashed application to Microsoft. A mini-dump, as the name implies, is a tiny version of the Unix style core dump. You can generate one yourself by going into the Debug menu under Visual Studio.NET and select "Save Dump As..." when your application is sitting at a breakpoint. This tiny file stores enough information to give you some clues about the crash.

For Windows geeks, it's time to let you in on a little secret: Visual Studio.NET can debug these very same mini-dump files. Here's how to reload it, because it isn't exactly obvious. Double click on the mini-dump file in the Windows Explorer, and it will launch a surprisingly blank looking Visual Studio.NET. The trick is to execute the mini-dump by hitting F5. VS.NET will prompt you to save a solution file. Go ahead and save it alongside the mini-dump. Once you do that the last state of your debugged process will appear before your very eyes.

Gotcha

The mini-dump is really convenient but there are a few gotchas to using mini-dumps. First, you must ensure that the mini-dump file matches exactly with the source code and symbol tables that were used to build the executable that crashed. This means that for any version of the executable that goes into your test department, you must save a complete build tree with source code and PDB files or the mini-dump will be useless. Second, the mini-dump's SLN file might need a hint about where to find the symbols. If the source window shows up with nothing but assembler, it's likely that your source code tree can't be located. Open the properties page and you'll see only one item under the Configuration tree: Debugging. Set the Symbol Path to the directory containing your PDB files and you'll see the source tree.

The only thing left out of this discussion is how to have your game generate mini-dump files when bad goes to worse. You'll need to call the MiniDumpWriteDump in your general exception handler, which is one of the functions exported from DBGHELP.DLL. This call will generate a DMP file. You can add more information to the DMP file if you define a callback function that can insert more information into the dump file, such as some specific game state information that might give a programmer a leg up on investigating the crash.

A Tale from the Pixel Mines

In 2001, Microsoft introduced our team to using mini-dumps. Microsoft's Dr. Watson team has established a huge database of mini-dumps for applications like Office and XP. At first we were skeptical about using them. We thought that these dump files wouldn't provide enough information to diagnose crashes. We were wrong. After the first week, we were able to diagnose and solve some of the most elusive crashes in our game. Every Windows game should make use of this technology.

Here's a simple class you can use to write your own mini-dumps:

 #include "dbghelp.h" class MiniDumper { protected:    static MiniDumper *gpDumper;    static LONG WINAPI Handler( struct _EXCEPTION_POINTERS *pExceptionInfo );    _EXCEPTION_POINTERS *m_pExceptionInfo;    TCHAR m_szDumpPath[_MAX_PATH];    TCHAR m_szAppPath[_MAX_PATH];    TCHAR m_szAppBaseName[_MAX_PATH];    LONG WriteMiniDump(_EXCEPTION_POINTERS *pExceptionInfo );    virtual void VSetDumpFileName(void);    virtual MINIDUMP_USER_STREAM_INFORMATION *VGetUserStreamArray()             { return NULL; } public:    MiniDumper(void); }; // based on dbghelp.h typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess,    DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType,    CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,    CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,    CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam); MiniDumper *MiniDumper::gpDumper = NULL; MiniDumper::MiniDumper( ) {    // Detect if there is more than one MiniDumper.    assert( !gpDumper );    if (!gpDumper)    {      ::SetUnhandledExceptionFilter( Handler );      gpDumper = this;    } } LONG MiniDumper::Handler( _EXCEPTION_POINTERS *pExceptionInfo ) {    LONG retval = EXCEPTION_CONTINUE_SEARCH;    if (!gpDumper)    {      return retval;    }    return gpDumper->WriteMiniDump(pExceptionInfo); } LONG MiniDumper::WriteMiniDump(_EXCEPTION_POINTERS *pExceptionInfo ) {    LONG retval = EXCEPTION_CONTINUE_SEARCH;    m_pExceptionInfo = pExceptionInfo;    // You have to find the right dbghelp.dll. // Look next to the EXE first since the one in System32 might be old (Win2k) HMODULE hDll = NULL; TCHAR szDbgHelpPath[_MAX_PATH]; if (GetModuleFileName( NULL, m_szAppPath, _MAX_PATH )) {    TCHAR *pSlash = _tcsrchr( m_szAppPath, '\\' );    if (pSlash)    {        _tcscpy( m_szAppBaseName, pSlash + 1);        *(pSlash+1) = 0;    }    _tcscpy( szDbgHelpPath, m_szAppPath );    _tcscat( szDbgHelpPath, _T("DBGHELP.DLL") );    hDll = ::LoadLibrary( szDbgHelpPath ); } if (hDll==NULL) {    // If we haven't found it yet - try one more time.    hDll = ::LoadLibrary( _T("DBGHELP.DLL") ); } LPCTSTR szResult = NULL; if (hDll) {    MINIDUMPWRITEDUMP pMiniDumpWriteDump =        (MINIDUMPWRITEDUMP)::GetProcAddress( hDll, "MiniDumpWriteDump" );    if (pMiniDumpWriteDump)    {        TCHAR szScratch [_MAX_PATH];        VSetDumpFileName();        // ask the user if they want to save a dump file        if (::MessageBox( NULL, _T("There was an unexpected error, would you          like to save a diagnostic file?"), NULL, MB_YESNO )==IDYES)        {             // create the file             HANDLE hFile =                  ::CreateFile( m_szDumpPath, GENERIC_WRITE,FILE_SHARE_WRITE,                 NULL, CREATE_ALWAYS,                          FILE_ATTRIBUTE_NORMAL, NULL );        if (hFile!=INVALID_HANDLE_VALUE)        {          _MINIDUMP_EXCEPTION_INFORMATION ExInfo;          ExInfo.ThreadId = ::GetCurrentThreadId();          ExInfo.ExceptionPointers = pExceptionInfo;          ExInfo.ClientPointers = NULL;          // write the dump          BOOL bOK = pMiniDumpWriteDump(                  GetCurrentProcess(), GetCurrentProcessId(),                  hFile, MiniDumpNormal, &ExInfo,                  VGetUserStreamArray(), NULL );          if (bOK)          {          szResult = NULL;          retval = EXCEPTION_EXECUTE_HANDLER;          }          else          {          sprintf( szScratch, _T("Failed to save dump file to '%s'                  (error %d)"), m_szDumpPath, GetLastError() );          szResult = szScratch;          }          ::CloseHandle(hFile);        }        else        {          sprintf( szScratch, _T("Failed to create dump file '%s'                       (error %d)"), m_szDumpPath, GetLastError() );          szResult = szScratch;        }      }    }    else    {      szResult = _T("DBGHELP.DLL too old");    } } else {   szResult = __T("DBGHELP.DLL not found"); }    if (szResult)      ::MessageBox( NULL, szResult, NULL, MB_OK );    TerminateProcess(GetCurrentProcess(), 0);    return retval ; } void MiniDumper::VSetDumpFileName(void) {    _tcscpy( m_szDumpPath, m_szAppPath );    _tcscat( m_szDumpPath, m_szAppBaseName );    _tcscat( m_szDumpPath, _T(".dmp") ); } 

If you want to save the mini-dump file out with a different name, inherit from the MiniDump class and overload VSetDumpFileName.One thing you might consider doing is putting a time stamp in the dump file name, so that one mini-dump file doesn't overwrite another. If you'd like to include your own data stream, overload VGetUserStreamArray(). Here's an example of this class at work:

 MiniDumper gMiniDumper; int main() {    *(int *)0 = 12;            // CRASH!!!!!    return 0; } 

Just declare a global singleton of the MiniDumper, and when an unhandled exception comes along your player will get asked if he or she wants to write out some diagnostic information. The mini-dump file will appear in the directory as your executable, ready for debugging.




Game Coding Complete
Game Coding Complete
ISBN: 1932111751
EAN: 2147483647
Year: 2003
Pages: 139

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net