OLE32 Stack Walking

Go to Home Page

In Vista & 7 (but not above), OLE32 contains a family of threadsafe COM classes that wrap the symbol functions in imagehlp.dll. These interfaces provide encapsulation of stack walking and address to symbol translation

Classes

CLSID_StackWalker - 00000349-0000-0000-c000-000000000046 - {0x349, 0, 0, 0xC0,0,0,0,0,0,0,0x46}
As well as IUnknown (naturally), the main parent class implements the IStackWalker interface. This is defined as:

MIDL_INTERFACE("00000159-0000-0000-c000-000000000046")
IStackWalker : IUnknown
{
    // hProcess can be NULL, which means the current process
    //
    // This function has to be called first, or the others will fail
    STDMETHOD(Attach)(HANDLE hProcess);

    // Walks the stack of hThread using pContext as a starting point
    // hThread can be NULL, whic indicates the calling thread
    // The flags control address resolution:
    // 0 = default (resolve addresses to name)
    // 1 = don't resolve address names
    STDMETHOD_(IStackWalkerStack*, CreateStackTrace)(CONTEXT* pContext, HANDLE hThread, ULONG flags);

    // Returns the symbol for the specified address (if any)
    STDMETHOD_(IStackWalkerSymbol*, ResolveAddress)(ULONG64 address);
};

Attach calls SymInitialize passing hProcess unmodified (or as GetCurrentProcess in the NULL case). It passes NULL & FALSE as the other two parameters, so the symbol server will only be used if the _NT_SYMBOL_PATH/_NT_ALTERNATE_SYMBOL_PATH environment variables are set before calling.
Attach also calls SymSetOptions with 0 as the options value, turning off all the options. You can reset these by calling SymSetOptions yourself.

Other Interfaces

The other functions of IStackWalker return other interfaces directly instead of HRESULTS. This is a theme which continues into these other interfaces, which are defined as:

MIDL_INTERFACE("00000158-0000-0000-c000-000000000046")
IStackWalkerStack : IUnknown
{
    // returns the interface for the top symbol of this stack trace
    STDMETHOD_(IStackWalkerSymbol*, TopSymbol)(void);

    // Returns the number of characters required hold 'numSymbols' lines of the stack trace
    // the return size is the required buffer size to pass to GetStack to get this many lines
    STDMETHOD_(ULONG, Size)(ULONG numSymbols);

    // Retrieves 'numSymbols' lines of the textual stack trace
    // starting from the top. 'bufferChars' is the size of 'pNameBuffer'
    // in characters
    STDMETHOD_(BOOL, GetStack)(ULONG bufferChars, PWSTR pNameBuffer, ULONG numSymbols);
};

MIDL_INTERFACE("00000157-0000-0000-c000-000000000046")
IStackWalkerSymbol : IUnknown
{
    // These strings are owned by the interface, do not 
    // attempt to free them by any means
    STDMETHOD_(PCWSTR, ModuleName)(void);
    STDMETHOD_(PCWSTR, SymbolName)(void);
    STDMETHOD_(ULONG64, Address)(void);
    STDMETHOD_(ULONG64, Displacement)(void);

    // this returns the aymbol underneath this one in the stack
    // calling this in s loop on the returned interfaces allows 
    // you to enumerate the stack trace. A NULL return means
    // this is bottom of the stack
    STDMETHOD_(IStackWalkerSymbol*, NextSymbol)(void);
};

Usage Example

This example creates the interface, causes an exception to get a valid context and then prints the stack trace. Then it looks up a simple address to demonstrate that too!

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <ole2.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

#define DEFINE_A_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
        EXTERN_C const GUID DECLSPEC_SELECTANY name \
                = { l, w1, w2, { b1, b2,  b3,  b4,  b5,  b6,  b7,  b8 } }

#define DEFINE_OLE_GUID(name, l) DEFINE_A_GUID(name, l, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46)

MIDL_INTERFACE("00000157-0000-0000-c000-000000000046")
IStackWalkerSymbol : IUnknown
{
    // These strings are owned by the interface, do not 
    // attempt to free them by any means
    STDMETHOD_(PCWSTR, ModuleName)(void);
    STDMETHOD_(PCWSTR, SymbolName)(void);
    STDMETHOD_(ULONG64, Address)(void);
    STDMETHOD_(ULONG64, Displacement)(void);

    // this returns the aymbol underneath this one in the stack
    // calling this in s loop on the returned interfaces allows 
    // you to enumerate the stack trace. A NULL return means
    // this is bottom of the stack
    STDMETHOD_(IStackWalkerSymbol*, NextSymbol)(void);
};

DEFINE_OLE_GUID(IID_IStackWalkerSymbol, 0x157);

MIDL_INTERFACE("00000158-0000-0000-c000-000000000046")
IStackWalkerStack : IUnknown
{
    // returns the interface for the top symbol of this stack trace
    STDMETHOD_(IStackWalkerSymbol*, TopSymbol)(void);

    // Returns the number of characters required hold 'numSymbols' 
    // lines of the stack trace
    // the return size is the required buffer size to pass to GetStack to get this many lines
    STDMETHOD_(ULONG, Size)(ULONG numSymbols);

    // Retrieves 'numSymbols' lines of the textual stack trace
    // starting from the top. 'bufferChars' is the size of 'pNameBuffer'
    // in characters
    STDMETHOD_(BOOL, GetStack)(ULONG bufferChars, PWSTR pNameBuffer, ULONG numSymbols);
};

DEFINE_OLE_GUID(IID_IStackWalkerStack, 0x158);

MIDL_INTERFACE("00000159-0000-0000-c000-000000000046")
IStackWalker : IUnknown
{
    // hProcess can be NULL, which means the current process
    //
    // This function has to be called first, or the others will fail
    STDMETHOD(Attach)(HANDLE hProcess);

    // Walks the stack of hThread using pContext as a starting point
    // hThread can be NULL, whic indicates the calling thread
    // The flags control address resolution:
    // 0 = default (resolve addresses to name)
    // 1 = don't resolve address names
    STDMETHOD_(IStackWalkerStack*, CreateStackTrace)(CONTEXT* pContext, HANDLE hThread, ULONG flags);

    // Returns the symbol for the specified address (if any)
    STDMETHOD_(IStackWalkerSymbol*, ResolveAddress)(ULONG64 address);
};

DEFINE_OLE_GUID(IID_IStackWalker, 0x159);

class DECLSPEC_UUID("00000349-0000-0000-c000-000000000046")
StackWalker;

DEFINE_OLE_GUID(CLSID_StackWalker, 0x349);

void PrintStack(IStackWalker* pStackWalker)
{
    IStackWalkerStack* pStack = NULL;
    CONTEXT ctx = {0};
    __try
    {
        int* p = NULL;
        *p = 0;
    }
    __except((memcpy(&ctx, (GetExceptionInformation())->ContextRecord, sizeof(ctx))), EXCEPTION_EXECUTE_HANDLER)
    {
        ;
    }
    pStack = pStackWalker->CreateStackTrace(&ctx, GetCurrentThread(), 0);
    if(!pStack)
    {
        _putws(L"Failed to create StackTrace!");
        return;
    }
    ULONG charsReq = pStack->Size(15);

    WCHAR* pBuffer = (WCHAR*)_alloca((charsReq + 1) * sizeof(*pBuffer));

    if(!pStack->GetStack(charsReq, pBuffer, 15))
    {
        _putws(L"Failed to get StackTrace text!");
        pStack->Release();
        return;
    }
    wprintf(L"Stack in PrintStack (GetStack method) is:\n%s\n", pBuffer);

    wprintf(L"Stack in PrintStack (TopSymbol method) is:\n");
    IStackWalkerSymbol* pSymIter = pStack->TopSymbol();
    while(pSymIter)
    {
        IStackWalkerSymbol* pTemp = pSymIter;
        wprintf(
            L"%#I64x - %s (%s+%#I64x)\n", 
            pSymIter->Address(), 
            pSymIter->ModuleName(), 
            pSymIter->SymbolName(), 
            pSymIter->Displacement()
        );
        pSymIter = pSymIter->NextSymbol();
        pTemp->Release();
    }
    pStack->Release();
}

int __cdecl main(int argc, char** argv)
{
    CoInitializeEx(NULL, COINIT_DISABLE_OLE1DDE | COINIT_APARTMENTTHREADED);
    // SetEnvironmentVariable(L"_NT_SYMBOL_PATH", L"srv*G:\symbols*http://msdl.microsoft.com/download/symbols");
    IStackWalker* pStackWalker = NULL;
    HRESULT hr = CoCreateInstance(CLSID_StackWalker, NULL, CLSCTX_INPROC_SERVER, IID_IStackWalker, (PVOID*)(&pStackWalker));
    if(FAILED(hr))
    {
        // this will probably be E_NOINTERFACE
        return wprintf(L"CoCreateInstance failed with error %#x\n", hr);
    }
    hr = pStackWalker->Attach(NULL);
    if(FAILED(hr))
    {
        return wprintf(L"Failed to attach to current process: %#x\n", hr);
    }
    PrintStack(pStackWalker);

    IStackWalkerSymbol* pSym = pStackWalker->ResolveAddress((ULONG64)(&main));
    wprintf(
        L"For address of main\nModuleName = %ws\nSymName = %ws\nAddress: %#I64x\nDisplacement: %#I64x\n",
        pSym->ModuleName(),
        pSym->SymbolName(),
        pSym->Address(),
        pSym->Displacement()
    );
    pSym->Release();
    pStackWalker->Release();
    CoUninitialize();
    return 0;
}

Issues

If you compile and run the above code, the first thing you'll notice is that it prints "Failed to create StackTrace!" It'll do that no matter which thread handle or however you construct the CONTEXT you pass to CreateStackTrace. This is because there is a bug within CreateStackTrace that causes StackWalk64 to fail everytime:

; Windows 7 SP0 OLE32 disassembly
.text:7266FEC6                 push    3
.text:7266FEC8                 pop     eax
...
.text:7266FECA                 mov     dword ptr [ebp+StackFrame.AddrPC.Offset], ecx
...
.text:7266FED7                 mov     dword ptr [ebp+StackFrame.AddrStack.Offset], ecx
...
.text:7266FEEB                 mov     [ebp+StackFrame.AddrPC.Mode], eax
.text:7266FEF1                 mov     [ebp+StackFrame.AddrStack.Mode], eax
...
.text:7266FF1A                 mov     dword ptr [ebp+StackFrame.AddrPC.Offset+4], esi
...
.text:7266FF25                 mov     dword ptr [ebp+StackFrame.AddrStack.Offset+4], esi
.text:7266FF2B                 mov     dword ptr [ebp+StackFrame.AddrFrame.Offset], ecx
.text:7266FF31                 mov     dword ptr [ebp+StackFrame.AddrFrame.Offset+4], esi

When setting up the StackFrame64 structure for the first call, the address of the stack (esp), frame (ebp) and program counter (eip) are saved into it. As StackWalk can handle walking through 16-bit code, you also have to tell it which address mode the offsets are in. The push/pop to eax shows this to be AddrModeFlat (which has the value 3), and the mov's into the Mode members show these being initialized correctly. But as you can see, there are three sets of offsets written, but only two modes. This leaves the mode for the Frame as 0, which is AddrMode1616. Since the flat address is invalid as a 1616 address, this causes StackWalk64 to fail.

This issue doesn't affect IStackWalker::ResolveAddress, so that still works for looking up individual address symbols, little consolation though that is.