Windows 上的 StackWalk64 - 获取符号名称

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5705650/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 16:40:49  来源:igfitidea点击:

StackWalk64 on Windows - Get symbol name

cwindowsstackbacktrace

提问by Macmade

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

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

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

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

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

今天早上,我也问了一个关于这个的问题:

Win32 - Backtrace from C code

Win32 - 从 C 代码回溯

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

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

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 SymGetSymFromAddr64function for this, with UnDecorateSymbolName. But I only get junk characters.

我正在SymGetSymFromAddr64为此使用该功能,带有UnDecorateSymbolName. 但我只得到垃圾字符。

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

这是我的代码。希望不要乱七八糟,因为我不习惯 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;
        }
    }
}

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 : )

顺便说一下,堆栈地址始终为 0 似乎很奇怪......感谢任何帮助:)

Thanks to everyone!

谢谢大家!

EDIT

编辑

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

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

采纳答案by AShelly

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

您已设置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;

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 pagefor the structure says (emphasis added):

否则,SymGetSymFromAddr64()很可能会覆盖内存。以下是该结构的帮助页面内容(已添加强调):

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.

MaxNameLength:Name 成员可以包含的字符串的最大长度,以字符为单位,不包括空终止字符。由于符号名称的长度可能不同,因此该数据结构 由调用者分配。使用这个成员是为了让库知道有多少内存可供符号名称使用。

回答by Cheeso

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

查看codeplex 上Stackwalker 项目- 它是开源的。很好用。

回答by Muqker

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.

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

回答by Sergey D

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:

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) 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:

2) 在 32 位构建中使用 RtlCaptureContext() 获取上下文是不行的。如果您有 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.

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

次要问题 - SymGetSymFromAddr64 没问题,但建议改用 SymFromAddr。

Good luck to all those tracing stack on Windows.

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

回答by Tim Cooper

See this answer to what is essentially the same question:

请参阅此对本质上相同问题的回答:

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

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

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.

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