Generating a Stack Crawl

When developing an application, it is sometimes useful to add logging functionality to help track down bugs. One of the tricks I like to pull out once in a while is automated bug reporting. When the application gets into an unexpected state, it’s sometimes nice to construct a report that users can send to you to help you track the problem down.

One piece of information that is especially important is the stack trace. Here’s a snippet of code that helps you to generate a stack crawl on Windows:

static bool GetStackWalk( std::string &outWalk )
{
	// Set up the symbol options so that we can gather information from the current
	// executable's PDB files, as well as the Microsoft symbol servers.  We also want
	// to undecorate the symbol names we're returned.  If you want, you can add other
	// symbol servers or paths via a semi-colon separated list in SymInitialized.
	::SymSetOptions( SYMOPT_DEFERRED_LOADS | SYMOPT_INCLUDE_32BIT_MODULES | SYMOPT_UNDNAME );
	if (!::SymInitialize( ::GetCurrentProcess(), "http://msdl.microsoft.com/download/symbols", TRUE )) return false;

	// Capture up to 25 stack frames from the current call stack.  We're going to
	// skip the first stack frame returned because that's the GetStackWalk function
	// itself, which we don't care about.
	PVOID addrs[ 25 ] = { 0 };
	USHORT frames = CaptureStackBackTrace( 1, 25, addrs, NULL );

	for (USHORT i = 0; i < frames; i++) {
		// Allocate a buffer large enough to hold the symbol information on the stack and get 
		// a pointer to the buffer.  We also have to set the size of the symbol structure itself
		// and the number of bytes reserved for the name.
		ULONG64 buffer[ (sizeof( SYMBOL_INFO ) + 1024 + sizeof( ULONG64 ) - 1) / sizeof( ULONG64 ) ] = { 0 };
		SYMBOL_INFO *info = (SYMBOL_INFO *)buffer;
		info->SizeOfStruct = sizeof( SYMBOL_INFO );
		info->MaxNameLen = 1024;

		// Attempt to get information about the symbol and add it to our output parameter.
		DWORD64 displacement = 0;
		if (::SymFromAddr( ::GetCurrentProcess(), (DWORD64)addrs[ i ], &displacement, info )) {
			outWalk.append( info->Name, info->NameLen );
			outWalk.append( "\n" );
		}
	}

        ::SymCleanup( ::GetCurrentProcess() );

	return true;
}

The code requires you to link against the DbgHelp.lib library, as well as #include <DbgHelp.h> in order to compile.

There are really only three interesting parts of the snippet. The call to SymSetOptions/SymInitialize only need to happen once per process (though in the snippet, they happen each time the function is called, which isn’t the end of the world). These calls initialize the DebugHlp symbol engine with the options we choose, such as the symbol servers to query, whether to undecorate names or not, etc. If you are creating a release build of your application, you likely are not shipping the build with PDBs, and so it’s likely your application will not be able to get symbols. I would highly recommend setting up a symbol server for your product, and putting the path to it in your call to SymInitialize.

The call to CaptureStackBackTrace is the second interesting part of the snippet — it’s a kernel call that walks the stack for you, and returns a list of function addresses. You tell the API how many stack frames to query, as well as how many stack frames to skip. The snippet skips a single stack frame, so the each call doesn’t come back with GetStackWalk as the top of the stack.

The third point of interest is the SymFromAddr call, which is another DebugHlp call that maps function addresses back to debugging symbols. These debugging symbols are where we get the function names from so that we can add them to the method output.

Getting a stack crawl for debugging output can really help you narrow down where bugs live in your application. However, it’s also not something I advocate these days. Stack crawls are only a small portion of information a programmer needs while debugging, and very few users would like to see a stack crawl of the application they’re using. Instead, I recommend using stack crawls only as supplementary information — something that comes along with an automated report that perhaps helps you to categorize the problem. Next time, I’ll cover a more in-depth automated reporting practice: mini-dumps.

This entry was posted in Win32 and tagged , . Bookmark the permalink.

One Response to Generating a Stack Crawl

  1. Pingback: Generating a Minidump | Ruminations

Leave a Reply

Your email address will not be published. Required fields are marked *