Windows上的StackWalk64 - 获取符号名称

时间:2011-04-18 16:01:50

标签: c windows stack backtrace

好的,关于SO的第二个问题在一天之内。看起来像Windows编程让我开心......:S

我正在尝试在Win32可执行文件上获取函数调用堆栈。

今天早上,我也问了一个问题:

  

Win32 - Backtrace from C code

现在,我很确定StackWalk64函数是关键。 我已经阅读了一些关于如何使用它的文章,以及MS文档。

它实际上在我的测试程序中显示帧,所以它有点工作......

问题是我无法从堆栈信息中检索符号名称。

我正在使用SymGetSymFromAddr64函数,UnDecorateSymbolName。但我只收到垃圾字符。

这是我的代码。希望它不要乱,因为我不熟悉Windows编程:

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;
        }
    }
}

实际输出是:

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

似乎奇怪的是,堆栈地址总是为0 ...任何帮助赞赏:)

感谢大家!

修改

我正在寻找一个简单的C解决方案,没有第三方库......

5 个答案:

答案 0 :(得分:6)

您已将symbol.MaxNameLength设置为255,但您在堆栈上使用IMAGEHLP_SYMBOL64 symbol;分配了“符号”。该类型定义为:

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

请注意,Name字段默认只有一个字符。如果要存储更大的名称,则需要执行以下操作:

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

否则,SymGetSymFromAddr64()可能会覆盖内存。以下是结构的the help page(强调添加):

  

MaxNameLength :最大长度   Name成员可以使用的字符串   包含字符,不包括   空终止字符。   因为符号名称可能不同   长度,这个数据结构是    由来电者分配 。这个成员   使用,所以图书馆知道多少   内存可供使用   符号名称。

答案 1 :(得分:5)

查看Stackwalker project on codeplex - 它是开源的。很好地工作。

答案 2 :(得分:2)

我使用了你的代码并且它起初也没有用,直到我在文档中注意到你首先需要调用SymInitialize,比如SymInitialize(process,NULL,TRUE)。你可以在RtlCaptureContext之前调用它。

答案 3 :(得分:0)

首先要解决两个问题:

1)如AShelly所指出的,名称需要预先分配。你不需要malloc来做它:

#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)使用RtlCaptureContext()来获取32位构建中的上下文是不行的。如果您有64位计算机,则将IMAGE_FILE_MACHINE_I386更改为适当的64位类型。如果您有32位版本,则使用内联汇编来正确设置EBP,ESP和EIP。这是一种方法:

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

小点 - SymGetSymFromAddr64没问题,但建议改用SymFromAddr。

祝所有Windows上的跟踪堆栈好运。

答案 4 :(得分:0)

查看基本相同问题的答案:

https://stackoverflow.com/a/28276227/10592

请注意,您需要确保您的用户拥有.pdb文件,并且他们的进程可以找到它 - 请参阅该答案以获取更多详细信息。