Windows 上的 StackWalk64 - 获取符号名称
Posted
技术标签:
【中文标题】Windows 上的 StackWalk64 - 获取符号名称【英文标题】:StackWalk64 on Windows - Get symbol name 【发布时间】: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 解决方案...
【问题讨论】:
你检查过SymGetSymFromAddr64
和UnDecorateSymbolName
的返回码吗?
你必须调用 SymInitialize,正如 Muqker 所提到的
【参考方案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://***.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;
【讨论】:
以上是关于Windows 上的 StackWalk64 - 获取符号名称的主要内容,如果未能解决你的问题,请参考以下文章