使用.net中用户错误报告的行号重新创建堆栈跟踪?
首先,问题是:我有几个免费项目,并且任何软件都包含bug。 一些用户遇到bug时会向我发送一个带有堆栈跟踪的bug报告。 为了简化查找故障位置,我想查看此堆栈跟踪中的行号。 如果应用程序没有.pdb文件,那么所有行信息都会丢失,因此目前我的所有项目都部署了.pdb文件,因此生成的堆栈跟踪具有此数字。 但! 但我不希望在发行版中看到这些文件,并希望删除所有.pdb。 它们会混淆用户,消耗安装程序中的空间等。
Delphi解决方案:很久以前,当我是一名delphi程序员时,我使用了以下技术:exception我的应用程序在堆栈上行走并收集地址。 然后,当我收到错误报告时,我使用了一个工具,根据收集的地址和位于MY机器上的相应符号文件,使用函数名称和行号重建有效的堆栈跟踪。
问题:在.NET中是否有任何lib,或技术或其他任何相同的操作?
状态更新:非常有趣,经常提出问题是开始自己调查的最佳方式。 例如,我想了一段时间这个问题,但几天前才开始寻找答案。
选项1:MiniDumps。 经过大量的谷歌搜索后,我找到了一种从代码创建迷你转储的方法,以及如何从托管迷你转储中重新创建堆栈。
- 可再发行程序集以创建迷你转储表单代码 – clrdump
- 关于使用以前的程序集的博客文章 – 在.NET生产应用程序中创建和分析minidump
然而,该解决方案需要重新分配两个额外的组件(大小约为1mb),并且小型转储需要一些空间,并且用户通过电子邮件发送它们是不舒服的。 所以对于我的目的,现在,这是不可接受的。
选项2:感谢weiqure提供线索。 可以为每个堆栈帧提取受管理的IL偏移量。 现在问题是如何根据此偏移从.pdb获取行号。 我发现了什么:
- PDB File Internals ,仅供参考,因为:
- ISymbolReader – 用于读取程序数据库文件的托管接口
- 最后是一个将.pdb文件转换为结构化xml以便于xpath处理的工具
使用此工具,可以为每个发布版本创建xml文件并将它们放入repositary。 当用户的机器上发生exception时,可以创建带有IL偏移的格式化错误消息。 然后用户通过邮件发送此消息(非常小)。 最后,可以创建一个简单的工具,从格式化的错误消息重新创建结果堆栈。
我只是想知道为什么没有其他人没有实现这样的工具? 我不相信这只对我有意义。
您可以使用System.Diagnostics.StackTrace从Exception获取最后一条MSIL指令的偏移量:
// Using System.Diagnostics static void Main(string[] args) { try { ThrowError(); } catch (Exception e) { StackTrace st = new System.Diagnostics.StackTrace(e); string stackTrace = ""; foreach (StackFrame frame in st.GetFrames()) { stackTrace = "at " + frame.GetMethod().Module.Name + "." + frame.GetMethod().ReflectedType.Name + "." + frame.GetMethod().Name + " (IL offset: 0x" + frame.GetILOffset().ToString("x") + ")\n" + stackTrace; } Console.Write(stackTrace); Console.WriteLine("Message: " + e.Message); } Console.ReadLine(); } static void ThrowError() { DateTime myDateTime = new DateTime(); myDateTime = new DateTime(2000, 5555555, 1); // won't work Console.WriteLine(myDateTime.ToString()); }
输出:
在ConsoleApplicationN.exe.Program.Main(IL偏移量:0x7)
在ConsoleApplicationN.exe.Program.ThrowError(IL偏移量:0x1b)
在mscorlib.dll.DateTime..ctor(IL偏移量:0x9)
在mscorlib.dll.DateTime.DateToTicks(IL偏移量:0x61)
消息:年,月和日参数描述不可表示的DateTime。
然后,您可以使用Reflector或ILSpy来解释偏移:
.method private hidebysig static void ThrowError() cil managed { .maxstack 4 .locals init ( [0] valuetype [mscorlib]System.DateTime myDateTime) L_0000: nop L_0001: ldloca.s myDateTime L_0003: initobj [mscorlib]System.DateTime L_0009: ldloca.s myDateTime L_000b: ldc.i4 0x7d0 L_0010: ldc.i4 0x54c563 L_0015: ldc.i4.1 L_0016: call instance void [mscorlib]System.DateTime::.ctor(int32, int32, int32) L_001b: nop L_001c: ldloca.s myDateTime L_001e: constrained [mscorlib]System.DateTime L_0024: callvirt instance string [mscorlib]System.Object::ToString() L_0029: call void [mscorlib]System.Console::WriteLine(string) L_002e: nop L_002f: ret }
你知道0x1b之前的指令引发了exception。 很容易找到C#代码:
myDateTime = new DateTime(2000, 5555555, 1);
您现在可以将IL代码映射到C#代码,但我认为增益太少而且工作量太大(尽管可能有reflection器插件)。 IL偏移应该没问题。
您应该使用Environment.FailFast ,在Application.UnhandledException中调用FailFast,并为您创建转储文件。
来自MSDN:
FailFast方法使用message参数将日志条目写入Windows应用程序事件日志,创建应用程序的转储,然后终止当前进程。
如果应用程序的状态损坏无法修复,则使用FailFast方法而不是Exit方法终止应用程序,并且执行应用程序的try-finally块和终结器将损坏程序资源。 FailFast方法终止当前进程并执行任何CriticalFinalizerObject对象,但不执行任何活动的try-finally块或终结器。
您可以编写一个简单的应用程序来收集日志文件并将其发送给您。
现在,打开转储文件有点棘手,Visual Studio无法处理托管转储文件(在.NET 4.0中修复),您可以使用WinDBG,但您需要使用SOS 。
您可以在出现错误时创建应用程序minidump,并将其用于离线分析。 这不需要您在客户端计算机上部署pdbs。 这个链接可以作为学习的良好起点。
您完全正确地需要PDB文件来准确获取源和线信息。 我会质疑任何出现PDB文件的问题,因为它实际上是一个有效的问题,但是,假设有一个正当的理由,你可以这样做,但是你需要付出更多的努力来创建适当的软件构建环境。
Microsoft Symbol Server将索引调试符号并将其存储起来,以便在以后使用崩溃转储时使用,并且需要符号可用。 您可以将Visual Studio或Windbg指向您自己的符号服务器实例,就像Microsoft一样,它将下拉调试该应用程序版本所需的符号(假设您在发货前使用符号服务器索引符号)。
获得构建的相应符号后,需要确保拥有属于该构建的相应源文件。
这就是Microsoft Source Server的用武之地。就像Symbol Server索引符号的地方一样 ,源服务器将索引源代码以确保您拥有属于软件构建的相应源代码版本。
版本控制 , 符号服务器和源服务器的工作版本应该是您的软件配置管理策略的一部分。
有第三方工具,一些商业工具,将为您提供生成应用程序快照的API,但正如您已经了解的那样,您需要一种机制来以某种方式将这些快照上传到您的环境。
关于PDB文件的 John Robbins
源服务器上的John Robbins
查看有关启动并运行Symbol Server的WinDbg文档 。