SkillAgentSearch skills...

StackWalker

Walking the callstack in windows applications

Install / Use

/learn @JochenKalmbach/StackWalker
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

StackWalker - Walking the callstack

This article describes the (documented) way to walk a callstack for any thread (own, other and remote). It has an abstraction layer, so the calling app does not need to know the internals.

This project was initially published on Codeproject (http://www.codeproject.com/KB/threads/StackWalker.aspx). But it is hard to maintain the article and the source on codeproject, so I was pushed to publish the source code on an "easier to modify" platform. Therefor I have chosen "codeplex" ;(

But time goes by, and codeplex went away ;)

So I now migrated to GitHub ;)

Latest Build status

build result

Documentation

Introduction

In some cases you need to display the callstack of the current thread or you are just interested in the callstack of other threads / processes. Therefore I wrote this project.

The goal for this project was the following:

  • Simple interface to generate a callstack
  • C++ based to allow overwrites of several methods
  • Hiding the implementation details (API) from the class interface
  • Support of x86, x64 and IA64 architecture
  • Default output to debugger-output window (but can be customized)
  • Support of user-provided read-memory-function
  • Support of the widest range of development-IDEs (VC5-VC8)
  • Most portable solution to walk the callstack

Background

To walk the callstack there is a documented interface: StackWalk64 Starting with Win9x/W2K, this interface is in the dbghelp.dll library (on NT, it is in imagehlp.dll). But the function name (StackWalk64) has changed starting with W2K (before it was called StackWalk (without the 64))! This project only supports the newer Xxx64-functions. If you need to use it on older systems, you can download the redistributable for NT/W9x.

The latest dbghelp.dll can always be downloaded with the Debugging Tools for Windows. This also contains the symsrv.dll which enables the use of the public Microsoft symbols-server (can be used to retrieve debugging information for system-files; see below).

Build

mkdir build-dir
cd build-dir

# batch
cmake -G "Visual Studio 17 2022" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=%cd%/root ..
# powershell
cmake -G "Visual Studio 17 2022" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX="$($(get-location).Path)/root" ..

cmake --build . --config RelWithDebInfo
ctest.exe -V -C RelWithDebInfo
cmake --build . --target install --config RelWithDebInfo

Using the code

The usage of the class is very simple. For example if you want to display the callstack of the current thread, just instantiate a StackWalk object and call the ShowCallstack member:

#include <windows.h>
#include "StackWalker.h"

void Func5() { StackWalker sw; sw.ShowCallstack(); }
void Func4() { Func5(); }
void Func3() { Func4(); }
void Func2() { Func3(); }
void Func1() { Func2(); }

int main()
{
    Func1();
    return 0;
}

This produces the following output in the debugger-output window:

[...] (output stripped)
d:\privat\Articles\stackwalker\stackwalker.cpp (736): StackWalker::ShowCallstack
d:\privat\Articles\stackwalker\main.cpp (4): Func5
d:\privat\Articles\stackwalker\main.cpp (5): Func4
d:\privat\Articles\stackwalker\main.cpp (6): Func3
d:\privat\Articles\stackwalker\main.cpp (7): Func2
d:\privat\Articles\stackwalker\main.cpp (8): Func1
d:\privat\Articles\stackwalker\main.cpp (13): main
f:\vs70builds\3077\vc\crtbld\crt\src\crt0.c (259): mainCRTStartup
77E614C7 (kernel32): (filename not available): _BaseProcessStart@4

You can now double-click on a line and the IDE will automatically jump to the desired file/line.

Providing own output-mechanism

If you want to direct the output to a file or want to use some other output-mechanism, you simply need to derive from the StackWalker class. You have two options to do this: only overwrite the OnOutput method or overwrite each OnXxx-function. The first solution (OnOutput) is very easy and uses the default-implementation of the other OnXxx-functions (which should be enough for most of the cases). To output also to the console, you need to do the following:

class MyStackWalker : public StackWalker
{
public:
    MyStackWalker() : StackWalker() {}
protected:
    virtual void OnOutput(LPCSTR szText) {
        printf(szText); StackWalker::OnOutput(szText);
    }
};

Retrieving detailed callstack info

If you want detailed info about the callstack (like loaded-modules, addresses, errors, ...) you can overwrite the corresponding methods. The following methods are provided:

class StackWalker
{
protected:
    virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName);
    virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size,
                              DWORD result, LPCSTR symType, LPCSTR pdbName,
                              ULONGLONG fileVersion);
    virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry);
    virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr);
};

These methods are called during the generation of the callstack.

Various kinds of callstacks

In the constructor of the class, you need to specify if you want to generate callstacks for the current process or for another process. The following constructors are available:

{
public:
    StackWalker(int options = OptionsAll,
                LPCSTR szSymPath = NULL,
                DWORD dwProcessId = GetCurrentProcessId(),
                HANDLE hProcess = GetCurrentProcess());
    
    // Just for other processes with default-values for options and symPath
    StackWalker(DWORD dwProcessId, HANDLE hProcess);
    
    // For showing stack trace after __except or catch
    StackWalker(ExceptType extype, int options = OptionsAll, PEXCEPTION_POINTERS exp = NULL);
};

To do the actual stack-walking you need to call the following functions:

class StackWalker
{
public:
    BOOL ShowCallstack(HANDLE hThread = GetCurrentThread(), CONTEXT *context = NULL,
                       PReadProcessMemoryRoutine readMemoryFunction = NULL, LPVOID pUserData = NULL);
};

Displaying the callstack of an exception

With this StackWalker you can also display the callstack inside an structured exception handler. You only need to write a filter-function which does the stack-walking:

// The exception filter function:
LONG WINAPI ExpFilter(EXCEPTION_POINTERS* pExp, DWORD dwExpCode)
{
    StackWalker sw;
    sw.ShowCallstack(GetCurrentThread(), pExp->ContextRecord);
    return EXCEPTION_EXECUTE_HANDLER;
}

// This is how to catch an exception:
__try
{
    // do some ugly stuff...
}
__except (ExpFilter(GetExceptionInformation(), GetExceptionCode()))
{
}

Display the callstack inside an C++ exception handler (two ways):

// This is how to catch an exception:
try
{
    // do some ugly stuff...
}
catch (std::exception & ex)
{
    StackWalker sw;
    sw.ShowCallstack(GetCurrentThread(), sw.GetCurrentExceptionContext());
}
catch (...)
{
    StackWalker sw(StackWalker::AfterCatch);
    sw.ShowCallstack();
}

Points of Interest

Context and callstack

To walk the callstack of a given thread, you need at least two facts:

The context of the thread

The context is used to retrieve the current Instruction Pointer and the values for the Stack Pointer (SP) and sometimes the Frame Pointer (FP). The difference between SP and FP is in short: SP points to the latest address on the stack. FP is used to reference the arguments for a function. See also Difference Between Stack Pointer and Frame Pointer. But only the SP is essential for the processor. The FP is only used by the compiler. You can also disable the usage of FP (see: (/Oy Frame-Pointer Omission).

The callstack

The callstack is a memory-region which contains all the data/addresses of the callers. This data must be used to retrieve the callstack (as the name says). The most important part is: this data-region must not change until the stack-walking is finished! This is also the reason why the thread must be in the Suspended state to retrieve a valid callstack. If you want to do a stack-walking for the current thread, then you must not change the callstack memory after the addresses which are declared in the context record.

Initializing the STACKFRAME64

To successfully walk the callstack with StackWalk64, you need to initialize the STACKFRAME64-structure with meaningful values. In the documentation of StackWalk64 there is only a small note about this structure:

  • The first call to this function will fail if the AddrPC and AddrFrame members of the STACKFRAME64 structure passed in the StackFrame parameter are not initialized.

According to this documentation, most programs only initialize AddrPC and AddrFrame and this had worked until the newest dbghelp.dll (v5.6.3.7). Now, you also need to initialize AddrStack. After having some trouble with this (and other problems) I talked to the dbghelp-team and got the following answer (2005-08-02; my own comments are written in italics!):

  • AddrStack should always be set to the stack pointer value for all

Related Skills

View on GitHub
GitHub Stars897
CategoryDevelopment
Updated19h ago
Forks190

Languages

C++

Security Score

95/100

Audited on Mar 27, 2026

No findings