Windows 上的 StackWalk64 - 获取符号名称

发布于 2024-11-02 16:05:39 字数 3590 浏览 1 评论 0原文

好吧,这是一天内关于 SO 的第二个问题。看起来 Windows 编程让我很高兴... : S

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

今天早上我也问了这个问题:

Win32 - 来自 C 代码的回溯

现在,我很确定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解决方案,没有第三方库......

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

北斗星光 2024-11-09 16:05:39

您已将 symbol.MaxNameLength 设置为 255,但您在堆栈上使用 IMAGEHLP_SYMBOL64 symbol; 分配了“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() 可能会覆盖内存。以下是该结构的帮助页面的内容(强调额外):

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

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.

揽清风入怀 2024-11-09 16:05:39

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

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

鸩远一方 2024-11-09 16:05:39

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

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.

疯狂的代价 2024-11-09 16:05:39

首先需要解决两个问题:

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 上的跟踪堆栈好运。

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.

半世晨晓 2024-11-09 16:05:39

请参阅本质上相同问题的答案:

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

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

See this answer to what is essentially the same question:

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.

虐人心 2024-11-09 16:05:39

除了在结构中分配足够的空间并正确设置结构尺寸之外。符号解析器未初始化。

因为这里的答案不是很好,并且后续的链接导致了一个使简单的 stackwalk 变得过于复杂的代码项目。我决定发布我修改后的代码。 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;
        }
    }
}

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;
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文