C++ C++;堆栈跟踪问题
我正在使用一个类,我想用它来记录Windows Vista/7计算机上的当前调用堆栈。(非常类似于“遍历调用堆栈”) 首先,我使用RtlCaptureContext获取当前上下文记录,然后使用StackWalk64获取各个堆栈帧。现在,我意识到每当我关闭并再次启动程序时,STACKFRAME64.AddrPC中的程序计数器实际上会改变特定代码行。出于某种原因,我认为只要不更改源代码并重新编译,特定代码行的PC地址就会保持不变 我需要PC地址来使用SymFromAddr和SymGetLineFromAddr64获取有关被调用函数、代码文件和行号的信息。不幸的是,这只在程序调试数据库(PDB文件)存在的情况下有效,但不允许我将其提供给客户机 我的计划是记录调用堆栈的PC地址(无论何时需要),然后从客户端发送给我。因此,我可以使用我的PDB文件来找出调用了哪些函数,但这当然只在PC地址是唯一标识符的情况下起作用。因为每次我启动程序时它们都会改变,所以我不能使用这种方法 您知道更好的方法来读取调用堆栈或克服程序计数器更改的问题吗 我认为一个可能的解决方案是始终获取已知位置的PC地址,并将其用作参考,以确定不同PC地址之间的偏移量。这似乎是可行的,但我不确定这是否是一种有效的方法,是否会一直有效C++ C++;堆栈跟踪问题,c++,windows,debugging,stack-trace,callstack,C++,Windows,Debugging,Stack Trace,Callstack,我正在使用一个类,我想用它来记录Windows Vista/7计算机上的当前调用堆栈。(非常类似于“遍历调用堆栈”) 首先,我使用RtlCaptureContext获取当前上下文记录,然后使用StackWalk64获取各个堆栈帧。现在,我意识到每当我关闭并再次启动程序时,STACKFRAME64.AddrPC中的程序计数器实际上会改变特定代码行。出于某种原因,我认为只要不更改源代码并重新编译,特定代码行的PC地址就会保持不变 我需要PC地址来使用SymFromAddr和SymGetLineFro
非常感谢你的帮助!我将在codeproject.com上发布最终(封装)解决方案,如果您愿意,我会说您帮助了我。我建议您查看Visual Studio项目的设置:链接器->高级->随机基址,查看所有程序和相关DLL (您可以重建)并重试。这是我唯一想到的一件事
希望对您有所帮助。您需要发送程序的运行内存映射,该映射告诉您从客户端加载的基址库/程序
然后,您可以使用基址计算符号。堆栈本身还不够,您需要加载的模块映射,这样您就可以将任何地址(随机、真)与模块关联,并找到PDB符号。但你真的是在重新发明轮子,因为至少有两种现成的解决方案可以很好地解决这个问题:
- Windows特定的DbgHlp小型转储API:。你的应用程序不应该直接调用它,而是应该附带一个极小的.exe,它所做的一切都需要一个进程的sa转储(进程ID作为参数给出),当你的应用程序遇到错误条件时,应该启动这个.exe,然后等待它的完成。原因是“转储程序”进程将在转储期间冻结转储进程,因此正在转储的进程不能与进行转储的进程相同。此方案适用于所有实现的应用程序。更不用说生成的转储是一个true.mdmp,您可以在WinDbg(或者VisualStudio中加载,如果您喜欢的话)
- 跨平台开源解决方案:。被Chrome、Firefox、Picassa和其他知名应用程序使用
因此,首先,不要重新发明轮子。另外,还有一些服务可以为错误报告增加价值,比如聚合、通知、跟踪和自动客户端响应,比如前面提到的由Microsoft提供的WER(您的代码必须经过数字签名才能符合要求),,(这是您真正创建的)和其他,但可以。只有WER适用于本机Windows应用程序。使用信息表单
上下文
可以在PE图像中找到函数部分和偏移量。例如,您可以使用此信息从链接器生成的.map文件中获取函数名
CONTEXT
struct。您对程序计数器成员感兴趣。由于CONTEXT
依赖于平台,因此您必须自己解决这个问题。您在初始化时就已经这样做了,例如x64窗口的STACKFRAME64.AddrPC.Offset=CONTEXT.Rip
。现在我们开始堆栈漫游,并使用STACKFRAME64.AddrPC.Offset
,由staclkwark64
填充作为起点RVA=STACKFRAME64.AddrPC.Offset-AllocationBase
。您可以使用VirtualQuery
获取AllocationBase
SectionOffset=RVA-SectionBase=STACKFRAME64.AddrPC.Offset-AllocationBase-SectionBase
。为了做到这一点,您需要访问PE图像头结构(图像头、图像头、图像头),以获取PE中的节数及其开始/结束地址。这很简单00002F8D->000031EB->00003253->00007947->0001552D->0002B521
。
将前三个偏移量与.map
文件内容进行比较:
...
0001:00002f40 ?GenerateReport@@YAXXZ 0000000140003f40 f FMain.obj
0001:000031e0 ?Run@@YAXXZ 00000001400041e0 f FMain.obj
0001:00003220 main 0000000140004220 f FMain.obj
...
其中00002f40
是距离00002F8D
最近的较小偏移量,依此类推。最后三个地址是指调用main
(\u tmainCRTstartup
等)的CRT/OS函数-我们应该忽略它们
因此,我们可以看到,我们能够在.map
文件的帮助下恢复堆栈跟踪。为了
D:\Work\C++\Source\Application\Prototype.Console\Prototype.Console.exe : 0001 : 0000000000002F8D
D:\Work\C++\Source\Application\Prototype.Console\Prototype.Console.exe : 0001 : 00000000000031EB
D:\Work\C++\Source\Application\Prototype.Console\Prototype.Console.exe : 0001 : 0000000000003253
D:\Work\C++\Source\Application\Prototype.Console\Prototype.Console.exe : 0001 : 0000000000007947
C:\Windows\system32\kernel32.dll : 0001 : 000000000001552D
C:\Windows\SYSTEM32\ntdll.dll : 0001 : 000000000002B521
------------------
...
0001:00002f40 ?GenerateReport@@YAXXZ 0000000140003f40 f FMain.obj
0001:000031e0 ?Run@@YAXXZ 00000001400041e0 f FMain.obj
0001:00003220 main 0000000140004220 f FMain.obj
...