Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Get the call stack when an exception is being caught

4.76/5 (7 votes)
31 Aug 2009CPOL3 min read 51.3K   2.2K  
Provides a utility class to return the call stack when an exception is being caught, using Windows SEH.

StackTraceExample

Introduction

StackTracer is a C++ class which enables you to get the call stack conveniently using Windows SEH, and then you can either show the exception message or log it to help you diagnose the problem. It works both in Debug and Release modes as long as you have the PDB files. (It might be not that accurate in Release mode as functions might be optimized out.)

Background

We use Windows' SEH (Structured Exception Handling) to handle exceptions in our application, using the __try, __except statements. We do this to protect our applications from crashing accidently. But this also hides the details of the problem, as we can only see the external operation that failed, but don't know what happened inside without debugging into the code.

With this StackTracer class integrated with the __try, __except statements, we can now pinpoint the problem with the callstack quickly. Whenever there is an exception, the detailed information will be logged, and we just need to open the log file, and then everything will be clear.

Using the code

We use the StackTracer as a static class; it has four public members:

static LONG ExceptionFilter(LPEXCEPTION_POINTERS e);

This function should be called in the __except clause; it takes the EXCEPTION_POINTERS structure as the context and works through the stack frame to get the call stack. Then, it will just return EXCEPTION_EXECUTE_HANDLER to execute the exception handler - the __except block.

static std::string GetExceptionMsg();

Return the exception information (exception code, call stack) with the format this class provides: as you can see at the beginning of the page.

static DWORD GetExceptionCode();
static std::vector<FunctionCall> GetExceptionCallStack();

There are two functions that return the data of the exception. If you don't like the default format provided by GetExceptionMsg, you can make your own.

The call stack depth is set to 6, as I believe it will give us enough information to diagnose the problem, but you can also change following line to get deeper:

const int CALLSTACK_DEPTH = 6;

To use this class, you can integrate it with any important function you want to monitor, or simply, just wrap the Main function, thus all the functions will be detected.

Here is how you use it:

Suppose you have a function which accomplishes a large operation:

void LargeOperation()
{ 
   // Large Operations
}

You want to protect this function using SEH, but also want to get the call stack when there is an exception. You can integrate it with StackTracer.

C++
// Pop up the exception information
void PopupCallStack()
{
    std::string str = StackTracer::GetExceptionMsg();
    AfxMessageBox(CString(str.c_str()));
}

// Log the exception information
void LogCallStack()
{
    std::string str = StackTracer::GetExceptionMsg();
    Log(str.c_str());
}
void LargeOperation()
{
    __try
    {
        // Large Operations
    }
    __except(StackTracer::ExceptionFilter(GetExceptionInformation()))
    {
        PopupCallStack();
        //LogCallStack(); 
    }
}

Explanation

This class take the EXCEPTION_POINTERS structure that is generated when an exception is raised, then works through the stack frames in the exception context to get the call stack. It needs symbol files to get the detailed information, such as function name and line number. Below is a skeleton of the core idea:

<a href="http://msdn.microsoft.com/en-us/library/ms681351%28VS.85%29.aspx">SymInitialize</a>(...); 
STACKFRAME64 sf;
While(<a href="http://msdn.microsoft.com/en-us/library/ms680650%28VS.85%29.aspx">StackWalk64</a>(...))
{
    SYMBOL_INFO symbolInfo;
    <a href="http://msdn.microsoft.com/en-us/library/ms681323%28VS.85%29.aspx">SymFromAddr</a>(...);

    IMAGEHLP_LINE64 lineInfo;
    <a href="http://msdn.microsoft.com/en-us/library/ms681330%28VS.85%29.aspx">SymGetLineFromAddr64</a>(...); 
}
<a href="http://msdn.microsoft.com/en-us/library/ms680696%28VS.85%29.aspx">SymCleanup</a>();

First, it initializes the symbol handler, loads the necessary symbol files, and then it works though the stack frame upwards, from the most recent to the older ones. StackWalk64 returns true if it gets the previous stack frame successfully. Then, SymFromAddr and SymGetLineFromAddr64 will get the function name and the line number in the frame.

History

  • 8/30/2009: Initial version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)