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.

7 Responses to Generating a Stack Crawl

  1. Pingback: Generating a Minidump | Ruminations

  2. Mitchell says:

    Mr. Ballman, I’m trying to make a function that wraps around iostream to also print out the class that is printing to the console so that I can trace back any messages that are printed. In java this is quite easy, but in C/C++ its a lot more complicated. I looked at the code you posted and the msdl link in the source is no longer in service. The original post was in 2011. Are there any working links for the code you posted, and/or are there more modern solutions that have come up since that time?

  3. Aaron Ballman says:

    I believe https://msdl.microsoft.com/download/symbols is still up and working — what issues are you running into?

    As for the dead link, sorry about that, here’s a link to the original content from the wayback machine: https://web.archive.org/web/20110405124931/http://www.stackhash.com/blog/post/Setting-up-a-Symbol-Server.aspx

    Microsoft also has some information: https://docs.microsoft.com/en-us/windows/desktop/dxtecharts/debugging-with-symbols

  4. Wojciech Sterna says:

    I have a question regarding size of the buffer:
    (sizeof( SYMBOL_INFO ) + 1024 + sizeof( ULONG64 ) – 1) / sizeof( ULONG64 )
    I get that the part “sizeof( ULONG64 ) – 1” serves for rounding up. But what reasoning stands behind sizeof( SYMBOL_INFO )/sizeof( ULONG64 ) and also adding 1024/sizeof( ULONG64 )?

  5. Aaron Ballman says:

    The 1024 represents the length of the name, but I don’t recall why I did that crazy stuff with the ULONG64 though. I cannot think of why that would be needed and am guessing it’s a mistake rather than purposeful.

  6. Wojciech Sterna says:

    Ahh it’s an array of ULONG64s, not bytes. Now I know why there is div by sizeof(ULONG64) :)

  7. Aaron Ballman says:

    Yeah, but why that route instead of bytes? Maybe I was worried about alignment.

    I suspect that after 8 years I’d probably try to find a better way to write that code. :-D

Leave a Reply

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