Walking the stack of the current thread

StackWalk64 provides a convenient and platform-independent way to walk the stack of a thread. Although useful and powerful indeed, it is definitively one of the less trivial to use functions. The additional fact that the MSDN documentation for StackWalk64 is rather light does not make things easier. There is, however, a decent article on CodeProject covering the usage of StackWalk64.

Although you can walk the stack of any thread using StackWalk64, this post only deals with walking the current thread’s stack. In order to walk the stack of the current thread, you first have to obtain the CONTEXT of the current thread. The naive way to obtain this context would be to call GetThreadContext( GetCurrentThread() ) — however, as the documentation for GetThreadContext clearly states, the result of this function is undefined if used on the current thread.

Obtaining a CONTEXT

64 Bit windows provides the function RtlCaptureContext for this purpose, so obtaining the context is trivial. 32 Bit Windows, however, does not implement this function, yet it is not hard to come up with a surregate.

One way to obtain the context is to set up a SEH frame, deliberately raise an exception and obtain the context in the exception filter using GetExceptionInformation. Although that works, it is probably the slowest way to do it. An easier and quicker solution is to use inline assembly (we only have to support 32 bit as we have RtlCaptureContext for x64/IA64, so using assembly is ok). Using appropriate instructions, we can manually populate the x86 CONTEXT structure. However, as eip cannot be accessed using a mov instruction, we have to use a little trick to obtain the value of the instruction pointer — use an (otherwise useless) label:

CONTEXT Context;
ZeroMemory( &Context, sizeof( CONTEXT ) );
Context.ContextFlags = CONTEXT_CONTROL;

__asm
{
Label:
  mov [Context.Ebp], ebp;
  mov [Context.Esp], esp;
  mov eax, [Label];
  mov [Context.Eip], eax;
}

Preparing to call StackWalk64

Having obtained the context, we have to initialize the STACKFRAME64 structure for the first call. This is trivial, but platform-specific.

In order to enable StackWalk64 to use symbol information, dbghelp has to be initialized by calling SymInitialize, passing the current process handle as parameter. Unless you plan to manually call SymLoadModule64 for all affected modules, it is crucial to pass TRUE for the fInvadeProcess argument. If you pass FALSE or forget to call SymInitialize altogether (After all, the documentation of StackWalk64 does not state that calling SymInitialize is required), you will notice that StackWalk64 works on x86 builds, but fails (with last error set to 0x1E7) on x64. Also, as dbghelp is not threadsafe, it is usually a good idea to protect any calls to dbghelp by a critical section

Calling StackWalk64

After these provisions have been made, calling StackWalk64 is a snap. The only unfortnate thing is that you cannot really distinguish between StackWalk64 returning FALSE because of a failure or because of the bottom of the stack having been reached as StackWalk64 does not reliably set the last Win32 error.

The following code puts it all together. Note that only a the minimal amount of stack trace information is gathered, i.e. only the return addresses are saved in the array.

typedef struct _STACKTRACE
{
  //
  // Number of frames in Frames array.
  //
  UINT FrameCount;

  //
  // PC-Addresses of frames. Index 0 contains the topmost frame.
  //
  ULONGLONG Frames[ ANYSIZE_ARRAY ];
} STACKTRACE, *PSTACKTRACE;

/*++
  Routine Description:
    Capture the stack trace based on the given CONTEXT.

  Parameters:
    Context    Thread context. If NULL, the current context is
               used.
    Event      Event structure to populate.
    MaxFrames  Max number of frames the structure can hold.
--*/
#ifdef _M_IX86
  //
  // Disable global optimization and ignore /GS waning caused by 
  // inline assembly.
  //
  #pragma optimize( "g", off )
  #pragma warning( push )
  #pragma warning( disable : 4748 )
#endif
HRESULT CaptureStackTrace(
  __in_opt CONST PCONTEXT InitialContext,
  __in PSTACKTRACE StackTrace,
  __in UINT MaxFrames 
  )
{
  DWORD MachineType;
  CONTEXT Context;
  HRESULT Hr;
  STACKFRAME64 StackFrame;

  ASSERT( StackTrace );
  ASSERT( MaxFrames > 0 );

  if ( InitialContext == NULL )
  {
    //
    // Use current context.
    //
    // N.B. GetThreadContext cannot be used on the current thread.
    // Capture own context - on i386, there is no API for that.
    //
#ifdef _M_IX86
    ZeroMemory( &Context, sizeof( CONTEXT ) );

    Context.ContextFlags = CONTEXT_CONTROL;
    
    //
    // Those three registers are enough.
    //
    __asm
    {
    Label:
      mov [Context.Ebp], ebp;
      mov [Context.Esp], esp;
      mov eax, [Label];
      mov [Context.Eip], eax;
    }
#else
    RtlCaptureContext( &Context );
#endif  
  }
  else
  {
    CopyMemory( &Context, InitialContext, sizeof( CONTEXT ) ); 
  }

  //
  // Set up stack frame.
  //
  ZeroMemory( &StackFrame, sizeof( STACKFRAME64 ) );
#ifdef _M_IX86
  MachineType                 = IMAGE_FILE_MACHINE_I386;
  StackFrame.AddrPC.Offset    = Context.Eip;
  StackFrame.AddrPC.Mode      = AddrModeFlat;
  StackFrame.AddrFrame.Offset = Context.Ebp;
  StackFrame.AddrFrame.Mode   = AddrModeFlat;
  StackFrame.AddrStack.Offset = Context.Esp;
  StackFrame.AddrStack.Mode   = AddrModeFlat;
#elif _M_X64
  MachineType                 = IMAGE_FILE_MACHINE_AMD64;
  StackFrame.AddrPC.Offset    = Context.Rip;
  StackFrame.AddrPC.Mode      = AddrModeFlat;
  StackFrame.AddrFrame.Offset = Context.Rsp;
  StackFrame.AddrFrame.Mode   = AddrModeFlat;
  StackFrame.AddrStack.Offset = Context.Rsp;
  StackFrame.AddrStack.Mode   = AddrModeFlat;
#elif _M_IA64
  MachineType                 = IMAGE_FILE_MACHINE_IA64;
  StackFrame.AddrPC.Offset    = Context.StIIP;
  StackFrame.AddrPC.Mode      = AddrModeFlat;
  StackFrame.AddrFrame.Offset = Context.IntSp;
  StackFrame.AddrFrame.Mode   = AddrModeFlat;
  StackFrame.AddrBStore.Offset= Context.RsBSP;
  StackFrame.AddrBStore.Mode  = AddrModeFlat;
  StackFrame.AddrStack.Offset = Context.IntSp;
  StackFrame.AddrStack.Mode   = AddrModeFlat;
#else
  #error "Unsupported platform"
#endif

  StackTrace->FrameCount = 0;

  //
  // Dbghelp is is singlethreaded, so acquire a lock.
  //
  // Note that the code assumes that 
  // SymInitialize( GetCurrentProcess(), NULL, TRUE ) has 
  // already been called.
  //
  EnterCriticalSection( &DbgHelpLock );

  while ( StackTrace->FrameCount < MaxFrames )
  {
    if ( ! StackWalk64(
      MachineType,
      GetCurrentProcess(),
      GetCurrentThread(),
      &StackFrame,
      MachineType == IMAGE_FILE_MACHINE_I386 
        ? NULL
        : &Context,
      NULL,
      SymFunctionTableAccess64,
      SymGetModuleBase64,
      NULL ) )
    {
      //
      // Maybe it failed, maybe we have finished walking the stack.
      //
      break;
    }

    if ( StackFrame.AddrPC.Offset != 0 )
    {
      //
      // Valid frame.
      //
      StackTrace->Frames[ StackTrace->FrameCount++ ] = 
        StackFrame.AddrPC.Offset;
    }
    else
    {
      //
      // Base reached.
      //
      break;
    }
  }
  
  LeaveCriticalSection( &DbgHelpLock );

  return S_OK;
}
#ifdef _M_IX86
  #pragma warning( pop )
  #pragma optimize( "g", on )
#endif
About these ads

8 Responses to “Walking the stack of the current thread”


  1. 1 KBaldwin April 10, 2008 at 4:34 pm

    Being unfamiliar with the Windows environment, I find myself at a loss trying to implement a stack dump method that does not employ the .NET framework.

    The application I am working on is built using makefiles rather than VisualStudio.NET projects. It is built for the Windows ix86 platform but it older legacy code that does NOT employ managed code or the .NET environment. But I find the MSDN references and John Robbins’ book all seem to employ the StackWalk64 / StackTrace / StackFrame64 methods and objects, and that these appear to require using the /cla build option and .NET framework. Am I mistaken in this impression? Can I use the nifty code shown in this article (very much like what I already implemented), *without* creating a dependency on the .NET framework and managed code for the application as a whole? Or is there some older, pre-.NET approach to performing a stack dump that I should be looking for in the archives?

  2. 2 jpassing April 10, 2008 at 8:05 pm

    If you want to generate stacktraces for native code, StackWalk64 is the right choice. Neither StackWalk64 itself nor the code in this post requires .Net and the /clr-switch – it is all native code.

    The only dependecy introduced by this approach is of course dbghelp.dll (along with symsrv.dll if you plan to use the symbol server), but again, none of this is managed code.

    — Johannes

  3. 3 n/a February 19, 2009 at 11:27 am

    32-bit windows:
    GetThreadContext()
    ?

  4. 4 sr April 17, 2010 at 10:57 pm

    Hello,

    I am new to windows programming environment and am running into a problem while trying to get the stack trace.

    What i want to do or am trying to do is:
    I have a dll written in C# (Managed code). This DLL is injected in a process and hooks a couple of function calls. I am interested in getting the stack trace when the hooked call occurs.

    I have created another dll written in C++ (Unmanaged) which exports the function to get the content of stack. The function basically implements code that has been explained in this blog. Now when my managed dll calls the function in unmanaged dll, i get

    System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

    Is this happening because of managed and unmanaged code ? Is it possible to overcome this problem ?

  5. 5 talarkancasen January 28, 2011 at 2:12 pm

    how are you, i found this place on google and i enjoy it so far :)

  6. 6 Mukesh Chauhan August 1, 2011 at 6:16 am

    Hello,

    Thank you for posting the article. It has really helped me in working with StackWalk64.

    However, I will put up a question about it. Is it possible to show the call stack from a particular process. For example, in my test program, it shows call stack after main also. I am only interested in call stack till main function not after that.

    Keep up the good work.
    Mukesh

  7. 7 Jacki August 6, 2011 at 6:14 pm

    Big help, big help. And sulpreative news of course.

  8. 8 Mario Vilas March 31, 2012 at 4:24 pm

    Hi! Great article :)

    One thing though: you can use GetThreadContext in both 32 and 64 bit versions of Windows. You don’t need to use the undocumented RtlCaptureContext functions.


Comments are currently closed.



Categories

Try Visual Assert, the unit testing add-in for Visual Studio (R)


NTrace: Function Boundary Tracing for Windows on IA-32

About me

Johannes Passing, M.Sc., living in Berlin, Germany.

Besides his consulting work, Johannes mainly focusses on Win32, COM, and NT kernel mode development, along with Java and .Net. He also is the author of cfix, a C/C++ unit testing framework for Win32 and NT kernel mode, Visual Assert, a Visual Studio Unit Testing-AddIn, and NTrace, a dynamic function boundary tracing toolkit for Windows NT/x86 kernel/user mode code.

Contact Johannes: jpassing (at) acm org

Johannes' GPG fingerprint is BBB1 1769 B82D CD07 D90A 57E8 9FE1 D441 F7A0 1BB1.

LinkedIn LinkedIn Profile
Xing Xing Profile
Twitter Follow me on Twitter (new)

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: