|
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.
|