Hello, I am Manish Vasani, an SDET on the VC++ compiler backend team. In this series of posts, I will talk about call stacks and how to use the DIA SDK for implementing a stack walking client that builds a call stack. If you are interested in knowing how debuggers build the call stack or want to write an application that dumps the call stack for an executing process, you may want to continue reading further.
In this post I’ll start with a brief introduction on stack walking, discuss the DIA APIs for implementing it, and then walkthrough a code sample for a DIA based stack walker. Those who are familiar with stack walking would know that apart from the DIA SDK, there are two other commonly used stack walking APIs: StackWalk64 (found in DbgHelp) and RtlVirtualUnwind (found in kernel32.lib). I’ll touch upon these two APIs at the end of the post.
Introduction:
When the Operating system executes a process, it calls into the program’s entry point (or the starting function) and allocates a set of resources to the process. Some of these resources such as stack memory and register context are allocated per thread basis. Every new function call in the thread is allocated a part of this stack memory and has a new register context. A stack frame or an activation record is essentially this runtime state information for an active function call. Typical attributes associated with a stack frame are:
1) Function associated with the frame
2) Module (binary) associated with the frame
3) Return address
4) Amount of stack memory allocated for arguments and locals for the function
5) Base pointer (BP) for referencing the arguments passed to the function call
6) Base pointer (BP) for referencing the local variables in the function (most of the time this is same as 5)
7) Program counter or the Instruction Pointer for currently executing instruction (IP)
8) Stack Pointer (SP)
9) Other Register context
A sequence of active stack frames in an executing thread is generally referred to as a call stack. Call stacks help developers to debug runtime program errors and also denote the program state at a particular execution point. The process of building this call stack is called stack walking. Stack walking clients (such as debuggers) use the stack frame attributes to retrieve the values of function arguments, locals, etc.
DIA APIs for St ack walking:
The primary functionality of the DIA SDK is to provide access to the debug information in PDB files. Apart from that, the DIA SDK also provides a set of interfaces that can be used to implement a platform independent stack walker. These are:
1) IDiaStackWalker: This is the primary initialization interface for the DIA stack walker.
2) IDiaEnumStackFrames: This is the stack frame enumerator interface that exposes the primary API to perform a stack walk to the next frame.
3) IDiaStackFrame: This interface represents a stack frame and exposes attributes of the stack frame.
4) IDiaStackWalkHelper: This is a helper interface for the DIA stack walker. To understand its primary purpose, let us briefly mention the two primary types of inputs required by the stack walker:
i. Run-time entities: This includes the runtime aspects such as thread’s register context, properties of loaded image files such as LOADED_IMAGE, executable binary (or module) corresponding to a given virtual address, etc.
ii. Compile-time generated entities: This includes the PDB records that provide static attributes for frames such as memory allocated for locals/arguments, whether the function corresponding to the frame contains SEH/C++ EH, the unwind program to execute to walk to the next frame, etc.
DIA stack walker uses other DIA interfaces to read the compile time generated entities, i.e. PDB records. It delegates fetching the runtime entities to the DIA clients. The primary reason for this being that DIA doesn’t maintain the runtime state information for the process. For example, a given virtual address in process’ address space might belong to any one of the modules (executable binaries) loaded in the process. Each module would have a different pdb file and hence a different DIA session to read the pdb. DIA isn’t aware about the loaded modules for the process and hence expects its clients to provide this mapping. Most DIA stack walker clients, such as debuggers, already need to fetch and store this runtime information. Hence, it is logical to expect the clients to provide this information to DIA through the implementation of this helper interface.
Code Sample Walkthrough:
I will first give the class design for the DIA based stack walker sample and then walk you through its execution flow.
Class design:
1) MyDiaStackWalker: This is the primary DIA stack walker class. It maintains a handle to the initialization interface (IDiaStackWalker) and the stack frame enumerator (IDiaEnumStackFrames). It also contains few helper methods for other DIA operation s.
2) MyDiaStackWalkHelper: This class implements the IDiaStackWalkHelper interface. I will discuss each of its method in detail later on.
3) Debugger: This is a Debugger class with a simple debugging engine. It launches the specified executable with debugging enabled and dumps the call stack at every debug break event in the executable. (Debug break events could be trigged by use of __debugbreak() intrinsic in your source code).
4) ContextManager: This class manages the register context associated with the current stack frame of the current debuggee thread (debuggee is the process being debugged)
5) ModuleManager: This class maintains the list of modules (executable binaries) loaded in the process along with their attributes.
Execution Flow:
1) Initialization:
The first step in the application is to initialize the DIA stack walker. This is done through the IDiaStackWalker interface. IDiaStackWalker provides two platform specific APIs for initializing the DIA client: getEnumFrames (for x86) and getEnumFrames2 (for x64 and ia64 platforms). Each of these APIs take a pointer to the class that implements IDiaStackWalkHelper (MyDiaStackWalkHelper) and returns the stack frame enumerator (IDiaEnumStackFrames). You may refer to the method “MyDiaStackWalker::Initialize” in MyDiaStackWalker.cpp to see the initialization logic.
2) Walking the stack:
After the initialization, we launch the specified executable under the debugger. On hitting a debug break event, we initialize the register context and loaded modules for the debuggee. Than we call “MyDiaStackWalker::WalkCallStack” method to walk the call stack and retrieve attributes such as module name, function name, Instruction pointer (IP), etc. for each stack frame:
// method to walk all the stack frames in the call stack
void MyDiaStackWalker::WalkCallStack()
{
wprintf(L“nWalking call stack…n”);
IDiaSymbol *pFuncSym = NULL;
DWORD count = 0;
BSTR szFunctionName;
ULONGLONG ip = 0;
do {
// Get IP for current stack frame
&n bsp; pContextManager->get_currentIP(&ip);
if (!ip) {
break;
}
// Get module for stack frame
Module *pModule = pModuleManager->FindModuleByVA(ip);
// Get DIA function symbol for stack frame
pMyDiaStackWalkHelper->symbolForVA(ip, &pFuncSym);
// Display stack frame attributes
if (pFuncSym == NULL) {
// function symbol could be null if symbols for the module were not loaded.
// just dump the IP for the stack frame
wprintf(L“Stack Frame %d: ‘%ws!%#x()’n”, ++count, pModule->name, ip);
continue;
}
if (pFuncSym->get_name(&szFunctionName) == S_OK) {
wprintf(L“Stack Frame %d: ‘%ws!%ws’n”, ++count, pModule->name, szFunctionName);
SysFreeString(szFunctionName);
}
pFuncSym->Release();
pFuncSym = NULL;
// Walk to next stack frame
} while (WalkStackFrame() == S_OK);
}
“WalkCallStack” method calls into “WalkStackFrame” to walk each stack frame. WalkStackFrame uses the stack frame enumerator API (IDiaEnumStackFrames::Next) to do the stack walk to next frame. Following snippet shows this:
// MyDiaStackWalker.cpp
// method to walk to the next stack frame
HRESULT MyDiaStackWalker::WalkStackFrame(IDiaStackFrame **ppStackFrame)
{
IFVERBOSE {
wprintf(L“MyDiaStackWalker::WalkStackFramen”);
}
ULONG celtFetched = 0;
IDiaStackFrame *pStackFrame = NULL;
This looks like it got cut off. And mangled in general.