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
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.