Sunday, August 30, 2015

Debugging released versions

When releasing software to the world, it can be difficult to track down and fix problems that may happen on people's machines. In particular, problems that are not reproducible on the developer's machine. A good way to deal with this is to implement a mechanism for capturing and saving program state when an exception occurs. In addition, this mechanism should work in the "Release" version of the software, which is distributed to clients and should have no dependency on run-time developer libraries. Luckily, the VC runtime allows us to do this, on windows platforms.

Enable custom exception handler


The first thing to do is to plug a custom exception handler into the exception handling mechanism. This will be used to capture the necessary information needed for debugging. You want to do this at the very beginning of your program.

 #include <Dbghelp.h>  
 #pragma comment(lib, "DbgHelp")  
 LONG WINAPI CustomExceptionHandler(EXCEPTION_POINTERS* info)  
 {  
      shoot::Log.Print("GOTCHA!\n");  
      MakeMiniDump(info);  
      return EXCEPTION_CONTINUE_SEARCH;  
 }  
 int main(int argc, char** argv)  
 {  
      SetUnhandledExceptionFilter(CustomExceptionHandler);  
     // ...  
 }  

Record crash information


The next thing is to extract the crash information from the exception structure received by our handler. On windows platforms, this consists in producing a "MiniDump" file, which is recognized by visual studio and can be opened like any regular VS project. When opened, Visual Studio will conveniently extract the program state and present a call-stack for each thread of the process, local variables, and can even let you peek into the heap of the process, depending on the MiniDump type. If you're familiar with debugging a visual studio project, this will feel no different.

 void MakeMiniDump(EXCEPTION_POINTERS* e)  
 {  
      HANDLE hFile = CreateFile("minidump.dmp", GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);  
      if (hFile == INVALID_HANDLE_VALUE)  
           return;  
      MINIDUMP_EXCEPTION_INFORMATION exceptionInfo;  
      exceptionInfo.ThreadId = GetCurrentThreadId();  
      exceptionInfo.ExceptionPointers = e;  
      exceptionInfo.ClientPointers = FALSE;  
      MiniDumpWriteDump(  
           GetCurrentProcess(),  
           GetCurrentProcessId(),  
           hFile,  
           MINIDUMP_TYPE(MiniDumpNormal),  // Explore other types for a full dump
           e ? &exceptionInfo : NULL,  
           NULL,  
           NULL);  
      if (hFile)  
      {  
           CloseHandle(hFile);  
           hFile = NULL;  
      }  
      return;  
 }  

That's it! Now you can distribute your software with less worries, and receive some juicy call-stacks whenever your program crashes on a user machine.

No comments:

Post a Comment