See what's going on with flipcode!




This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

 

  Traceable Callstacks with C++
  Submitted by



Way back when (OK, so a few months ago), when I started learning C++, I took to browsing open source code to get an idea of how people structured their code in the language. I looked through the UT source code and came across some funny macros called guard() and unguard() at the beginning and end of every function. Curious as to what their purpose was, I dug deeper and discovered some very cool new functionality that I could add to my arsenal of debugging tools.

Here's the case. Wouldn't it be nice if you could do the following...



   void DoSomeMoreStuff(void)
   {
      // Error occured somewhere in here, throw an exception
      throw;
   }

void DoSomeStuff(void) { DoSomeMoreStuff(); }

int main(int argc, char *argv[]) { try { DoSomeStuff(); } catch (...) { // Do something about any errors }

return (0); }




When the error occurs, your program prints out the callstack...



"Callstack: main, DoSomeStuff, DoSomeMoreStuff, EXCEPTION!" 






You can achieve this by using C++ exception handling to trace the exact path of function calls directly up to the exception. This is done by the use of two macros, guard and unguard (I call mine infunc and outfunc), which you place at the beginning and end of any function you write...



   void Function(void)
   {
      infunc(Function);

// Do some stuff outfunc; }




This measure might seem a bit extreme but after using it for a while, I find it a little difficult to write functions without putting the macros in due to force of habit. The macros themselves are defined as follows...



   #define infunc(func) static const char *__FUNC_NAME__ = #func; try {
   #define outfunc      } catch (...) { CSAddFunction(__FUNC_NAME__); throw; } 




If they are expanded into the above function it can be seen more clearly what they do...



   void Function(void)
   {
      // The # is called the "stringiser" operator, here it "stringises" the function name
      static const char *__FUNC_NAME__ = "Function";

try { // Do some stuff }

// Catch an exception of any type catch (...) { // Add this function name to the callstack list CSAddFunction(__FUNC_NAME__);

// Re-throw the exception to be caught again by the calling function throw; } }






In your main function, just print out the function names...



   int main(int argc, char *argv[])
   {
      try
      {
         // Do some stuff (yet again)
      }

catch (...) { const char *string_ptr;

printf("Callstack: main");

// Just loop through the list of functions (added with CSAddFunction) if (CSUsed()) while (string_ptr = CSEnumFunctions()) printf(", %s", string_ptr);

printf(", EXCEPTION!\n"); }

return (0); }






All that's left is implementation of the CSx functions...



   vector<const char * CallStack;
   int EnumPosition = 0;
   int Used = 0;

void CSAddFunction(const char *func_name) { // The callstack has been used! Used = 1;

// Add the function name to the list CallStack.push_back(func_name); }

int CSUsed(void) { return (Used); }

const char *CSEnumFunctions(void) { // Clamp and wrap the pointer return if (EnumPosition == CallStack.size()) { EnumPosition = 0; return (NULL); }

// Return the current string return (CallStack[EnumPosition++]); }






And that's all there is to it. It does use an itsy bit of STL so make sure you include and use the "std" namespace with MSVC. Here's a few tips to help you get more out of exception handling...



  • Instead of throwing anonymous exceptions when you get an error, use the va_list functions in to throw exceptions with string parameters similar to printf and print the string out along with the callstack.
  • If you are creating a module based application (EXEs and DLLs) put your callstack functions (CSx) in a seperate, load-time linked DLL instead of a .LIB file. If you don't do this, you'll find that each module that uses the .LIB will have it's own instance of the callstack leading to a severance of the callstack list as it enters another module.
  • Add to the power of this by using Win32 Structured Exception Handling (SEH - information in MSDN) so you can get the exact address and cpu state when an abnormally generated exception occurs (int *ptr = 0; *ptr = 5;). Save code builds so that you can trace the crash to the exact line of code -- you'll have a callstack leading to the exception as well as a register value list allowing you to determine what caused the crash (such as EAX = 0 for those blasted NULL pointers). Just break- point at the beginning of your program and use "Edit|Go To...|Address" in MSVC to get to the location, cross referencing register usage with the current state they were in while running. Consider these when you've read a bit about SEH...

  • Don't try writing to a file in a _finally(F()) statement - it won't work. Instead, just copy your EXCEPTION_RECORD and CONTEXT structures to somewhere temporary so that you can reference them in the _finally(F()) code block and output to a file there.



  • When de-initialising Direct3D after recovering from an exception I've found that some video card drivers will fail to launch MessageBox or even create another window so that you can alert the user of the error. Try writing your exception information to a temporary file and launching another app you've written (use the spawn functions) which will read the file, delete it, display a nicely formatted error message box (instead of that ugly Close/Debug/Details Win32 one), and write the exception information to a time-formatted file.



  • Put conditional #defines around your infunc/outfunc macros (#ifdef CALLSTACK_ENABED, f.e.). You can then get rid of all the code introduced by using callstack tracing for your final build (well, final build number 47, that is). You'll also need this for using the MSVC debugger since exception handling plays havoc with it.



  • Hope this helps!
    - Don

    The zip file viewer built into the Developer Toolbox made use of the zlib library, as well as the zlibdll source additions.

     

    Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
    Please read our Terms, Conditions, and Privacy information.