StackWalk64 on Windows - Get symbol name
Asked Answered
P

6

14

Alright, second question on SO in one day. Looks like Windows programming makes me happy... : S

I'm currently trying to get the function call stack on a Win32 executable.

This morning, I've also asked a question about this:

Win32 - Backtrace from C code

Now, I'm pretty sure that the StackWalk64 function is the key for this. I've read some articles on how to use it, as well as the MS documentation.

It actually displays frames on my test program, so it kinda work...

The problem is that I'm not able to retrieve the symbol name from the stack informations.

I'm using the SymGetSymFromAddr64 function for this, with UnDecorateSymbolName. But I only get junk characters.

Here's my code. Hope its not to messy, as I'm not used to Windows programming:

void printStack( void )
{
    BOOL                result;
    HANDLE              process;
    HANDLE              thread;
    CONTEXT             context;
    STACKFRAME64        stack;
    ULONG               frame;
    IMAGEHLP_SYMBOL64   symbol;
    DWORD64             displacement;
    char name[ 256 ];

    RtlCaptureContext( &context );
    memset( &stack, 0, sizeof( STACKFRAME64 ) );

    process                = GetCurrentProcess();
    thread                 = GetCurrentThread();
    displacement           = 0;
    stack.AddrPC.Offset    = context.Eip;
    stack.AddrPC.Mode      = AddrModeFlat;
    stack.AddrStack.Offset = context.Esp;
    stack.AddrStack.Mode   = AddrModeFlat;
    stack.AddrFrame.Offset = context.Ebp;
    stack.AddrFrame.Mode   = AddrModeFlat;

    for( frame = 0; ; frame++ )
    {
        result = StackWalk64
        (
            IMAGE_FILE_MACHINE_I386,
            process,
            thread,
            &stack,
            &context,
            NULL,
            SymFunctionTableAccess64,
            SymGetModuleBase64,
            NULL
        );

        symbol.SizeOfStruct  = sizeof( IMAGEHLP_SYMBOL64 );
        symbol.MaxNameLength = 255;

        SymGetSymFromAddr64( process, ( ULONG64 )stack.AddrPC.Offset, &displacement, &symbol );
        UnDecorateSymbolName( symbol.Name, ( PSTR )name, 256, UNDNAME_COMPLETE );

        printf
        (
            "Frame %lu:\n"
            "    Symbol name:    %s\n"
            "    PC address:     0x%08LX\n"
            "    Stack address:  0x%08LX\n"
            "    Frame address:  0x%08LX\n"
            "\n",
            frame,
            symbol.Name,
            ( ULONG64 )stack.AddrPC.Offset,
            ( ULONG64 )stack.AddrStack.Offset,
            ( ULONG64 )stack.AddrFrame.Offset
        );

        if( !result )
        {
            break;
        }
    }
}

The actual output is:

Frame 0:
    Symbol name:    ╠╠╠╠╠╠╠╠╠╠╠╠
    PC address:     0x00BA2763
    Stack address:  0x00000000
    Frame address:  0x0031F7E8

Frame 1:
    Symbol name:    ╠╠╠╠╠╠╠╠╠╠╠╠☺
    PC address:     0x00BB4FFF
    Stack address:  0x00000000
    Frame address:  0x0031F940

Frame 2:
    Symbol name:    ╠╠╠╠╠╠╠╠╠╠╠╠☻
    PC address:     0x00BB4E2F
    Stack address:  0x00000000
    Frame address:  0x0031F990

Frame 3:
    Symbol name:    ╠╠╠╠╠╠╠╠╠╠╠╠♥
    PC address:     0x75BE3677
    Stack address:  0x00000000
    Frame address:  0x0031F998

Frame 4:
    Symbol name:    ╠╠╠╠╠╠╠╠╠╠╠╠♦
    PC address:     0x770F9D72
    Stack address:  0x00000000
    Frame address:  0x0031F9A4

Frame 5:
    Symbol name:    ╠╠╠╠╠╠╠╠╠╠╠╠♣
    PC address:     0x770F9D45
    Stack address:  0x00000000
    Frame address:  0x0031F9E4

Frame 6:
    Symbol name:    ╠╠╠╠╠╠╠╠╠╠╠╠♠
    PC address:     0x770F9D45
    Stack address:  0x00000000
    Frame address:  0x0031F9E4

Seems weird that the stack address is always 0 by the way... Any help appreciated : )

Thanks to everyone!

EDIT

I'm looking for a plain C solution, without third party libraries...

Phenica answered 18/4, 2011 at 16:1 Comment(3)
Have you checked the return codes from SymGetSymFromAddr64 and UnDecorateSymbolName?Bottom
you have to call SymInitialize, as mentioned by MuqkerPryor
#76224883 I have similar question. I need to read crash dump and need to get crashed call stack from crash dump. In my Question you can see there are 2 scenario, I first scenario my code is working but in 2nd scenario it is not working. Please tell me if there is any other way.Shoup
B
8

You have set symbol.MaxNameLength to 255, but you allocated "symbol" on the stack with IMAGEHLP_SYMBOL64 symbol;. That type is defined as:

typedef struct _IMAGEHLP_SYMBOL64 {
  DWORD   SizeOfStruct;
  DWORD64 Address;
  DWORD   Size;
  DWORD   Flags;
  DWORD   MaxNameLength;
  TCHAR   Name[1];
} IMAGEHLP_SYMBOL64;

Notice that the Name field only has one character by default. If you want to store bigger names, you need to do something like:

 const int MaxNameLen = 255;
 IMAGEHLP_SYMBOL64* pSymbol = 
       malloc(sizeof(IMAGEHLP_SYMBOL64)+MaxNameLen*sizeof(TCHAR));
 pSymbol->MaxNameLength = MaxNameLen;

Otherwise, SymGetSymFromAddr64() is likely to overwrite memory. Here is what the help page for the structure says (emphasis added):

MaxNameLength: The maximum length of the string that the Name member can contain, in characters, not including the null-terminating character. Because symbol names can vary in length, this data structure is allocated by the caller. This member is used so the library knows how much memory is available for use by the symbol name.

Bottom answered 18/4, 2011 at 18:31 Comment(4)
Thanks for the tip. Seems the right way to use the IMAGEHLP_SYMBOL64 type. Still no luck however...Phenica
You also need to set the SizeOfStruct member to sizeof( IMAGEHLP_SYMBOL64 ), according to the documentation.Phenica
My code still doesn't work, but I was able to obtain the backtrace using the CaptureStackBackTrace function. It takes a SYMBOL_INFO struct as argument, which needs to be initialized the same way as IMAGEHLP_SYMBOL64 . So I'm accepting your answer. Thanks a lot for the help!Phenica
The correct way to allocate a properly sized structure would be malloc(offsetof(IMAGEHLP_SYMBOL64, Name[MaxNameLen])). Unlike adding the individual sizes, this accounts for padding and alignment.Bosket
I
6

Check out the Stackwalker project on codeplex - it's open source. Works nicely.

Insane answered 18/4, 2011 at 16:7 Comment(5)
Thanks for the answer : ) I should have say I don't want to use a third party library, and that I don't want to use C++...Phenica
Well you can view the source, and learn what works. There's not very much c++; in that project the core of code that demonstrates how to address the problem you raised, is just C code.Insane
Tried it... No luck so far... : (Phenica
Tried it - it works on my development laptop but the same .exe does not work on any other laptop - it fails to find the symbols.Esse
I think this is the latest source code link: github.com/JochenKalmbach/StackWalkerBreakwater
R
2

I used your code and it also didn't work at first, until I noticed in the documentation that you first need to call SymInitialize, like SymInitialize(process, NULL, TRUE) . You can call this before RtlCaptureContext.

Ricks answered 18/5, 2012 at 9:59 Comment(0)
C
0

There are two problems to address first:

1) Name needs to be preallocated as pointed out by AShelly. You don't need malloc to do it:

#define MY_MAX_SYM_LEN 255
printStack()
{
    struct sym_pack_tag {
        IMAGEHLP_SYMBOL64  sym;
        char               name[MY_MAX_SYM_LEN];
    } sym_pack;
    IMAGEHLP_SYMBOL64     *symbol = &sym_pack.sym;
    ...
    symbol->SizeOfStruct  = sizeof(IMAGEHLP_SYMBOL64 );
    symbol->MaxNameLength = MY_MAX_SYM_LEN;
    if (!SymGetSymFromAddr64( process, stack.AddrPC.Offset, &displacement, symbol )) ...

2) Its not ok to use RtlCaptureContext() to get context in 32-bit builds. If you have 64-bit machine then change IMAGE_FILE_MACHINE_I386 to the appropriate 64-bit type. If you have 32-bit build then use inline assembly to correctly set EBP, ESP and EIP. Here is one way to do it:

__declspec(naked) void WINAPI CaptureContext_X86ControlOnly(CONTEXT *context) {
  __asm {
    push ebp
    mov  ebp, esp
    mov  ecx, context            //ecx = [ebp + 8]
    pop  ebp                     //restore old frame
    pop  eax                     //pop return address
    pop  ecx                     //pop context as WINAPI needs. Note: ecx will stay the same
    mov [ecx]CONTEXT.ContextFlags, CONTEXT_CONTROL
    mov [ecx]CONTEXT.Ebp, ebp
    mov [ecx]CONTEXT.Eip, eax
    mov [ecx]CONTEXT.Esp, esp
    jmp  eax
  }
} //I'm writing from my memory - so step through the code above to double check.

Minor point - SymGetSymFromAddr64 is ok, but it is recommended to use SymFromAddr instead.

Good luck to all those tracing stack on Windows.

Cogitative answered 19/6, 2013 at 15:9 Comment(0)
E
0

See this answer to what is essentially the same question:

https://mcmap.net/q/358043/-windows-c-stack-trace-from-a-running-app

Note that you need to make sure your users have the .pdb file, and that their process can find it - see that answer for more details.

Esse answered 6/3, 2015 at 5:50 Comment(0)
P
0

In addition to allocating enough space in the structure and setting structure size correctly. The symbol resolver was not initialized.

Because the answers here were not great and the subsequent links lead to a codeproject that overcomplicates a simple stackwalk. I decided to post my modified code. OP was really close to having a working function.

Here is the modified code, which produces a correct stack walk for a win32 app:

#include <dbghelp.h>
void printStack(void)
{
    BOOL                result;
    HANDLE              process;
    HANDLE              thread;
    CONTEXT             context;
    STACKFRAME64        stack;
    ULONG               frame;
    char                name[(MAX_PATH * sizeof(TCHAR))];
    char                Storage[sizeof(IMAGEHLP_SYMBOL64) + (sizeof(name))];
    IMAGEHLP_SYMBOL64*  symbol;
    DWORD64             displacement;
 
    symbol = (IMAGEHLP_SYMBOL64*)Storage;
    RtlCaptureContext(&context);
    memset(&stack, 0, sizeof(STACKFRAME64));

    process = GetCurrentProcess();
    thread = GetCurrentThread();
    displacement = 0;
    stack.AddrPC.Offset = context.Eip;
    stack.AddrPC.Mode = AddrModeFlat;
    stack.AddrStack.Offset = context.Esp;
    stack.AddrStack.Mode = AddrModeFlat;
    stack.AddrFrame.Offset = context.Ebp;
    stack.AddrFrame.Mode = AddrModeFlat;

    BOOL initres = SymInitialize(process, nullptr, true);
    for (frame = 0; ; frame++)
    {
        result = StackWalk64
        (
            IMAGE_FILE_MACHINE_I386,
            process,
            thread,
            &stack,
            &context,
            NULL,
            SymFunctionTableAccess64,
            SymGetModuleBase64,
            NULL
        );

        symbol->SizeOfStruct = sizeof(Storage);
        symbol->MaxNameLength = sizeof(name);

        BOOL SymResult = SymGetSymFromAddr64(process, (ULONG64)stack.AddrPC.Offset, &displacement, symbol);
        if (SymResult == false) {
            DWORD Error = GetLastError();
            OutputDebugString(L"Could not resolve symbol");
        }

        UnDecorateSymbolName(symbol->Name, (PSTR)name, sizeof(name), UNDNAME_COMPLETE);
        printf
        (
            "%02u 0x%08X 0x%08X 0x%08X  %s\n",
            frame,
            (ULONG64)stack.AddrPC.Offset,
            (ULONG64)stack.AddrStack.Offset,
            (ULONG64)stack.AddrFrame.Offset,
            symbol->Name
        );

        if (result == false) {
            DWORD frameError = GetLastError();
            break;
        }
    }
}
Prismatic answered 12/12, 2021 at 18:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.