【问题标题】:StackWalk64 on Windows - Get symbol nameWindows 上的 StackWalk64 - 获取符号名称
【发布时间】:2011-08-08 00:27:03
【问题描述】:

好的,一天后关于 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 解决方案...

【问题讨论】:

  • 你检查过SymGetSymFromAddr64UnDecorateSymbolName的返回码吗?
  • 你必须调用 SymInitialize,正如 Muqker 所提到的

标签: c windows stack backtrace


【解决方案1】:

查看Stackwalker project on codeplex - 它是开源的。效果很好。

【讨论】:

  • 感谢您的回答 :) 我应该说我不想使用第三方库,并且我不想使用 C++...
  • 你可以查看源代码,了解什么是有效的。没有太多的c++;在那个项目中,演示如何解决您提出的问题的核心代码就是 C 代码。
  • 试过了...到目前为止没有运气... : (
  • 试过了——它可以在我的开发笔记本电脑上运行,但相同的 .exe 不能在任何其他笔记本电脑上运行——它无法找到符号。
【解决方案2】:

您已将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;

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

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

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

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

【讨论】:

  • 感谢您的提示。似乎是使用 IMAGEHLP_SYMBOL64 类型的正确方法。然而仍然没有运气......
  • 您还需要根据文档将 SizeOfStruct 成员设置为 sizeof(IMAGEHLP_SYMBOL64)。
  • 我的代码仍然不起作用,但我能够使用CaptureStackBackTrace 函数获得回溯。它需要一个SYMBOL_INFO 结构作为参数,它需要以与IMAGEHLP_SYMBOL64 相同的方式初始化。所以我接受你的回答。非常感谢您的帮助!
  • 分配适当大小的结构的正确方法是malloc(offsetof(IMAGEHLP_SYMBOL64, Name[MaxNameLen]))。与添加单个尺寸不同,这考虑了填充和对齐。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    首先要解决两个问题:

    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) 在 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.
    

    次要 - SymGetSymFromAddr64 是可以的,但建议使用 SymFromAddr 代替。

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

    【讨论】:

      【解决方案5】:

      请参阅这个基本相同问题的答案:

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

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

      【讨论】:

        【解决方案6】:

        除了在结构中分配足够的空间和正确设置结构大小。符号解析器未初始化。

        因为这里的答案不是很好,并且随后的链接导致了一个代码项目,使简单的堆栈遍历过于复杂。我决定发布我修改后的代码。 OP 真的很接近具有工作功能。

        这是修改后的代码,它为 win32 应用程序生成正确的堆栈遍历:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-05-10
          • 1970-01-01
          • 2012-11-30
          • 1970-01-01
          • 1970-01-01
          • 2018-06-19
          • 2021-07-07
          • 2015-12-05
          相关资源
          最近更新 更多