为什么要使用内存转储进行调试?

在两种主要情况下,您可能需要使用内存转储进行调试。第一种情况是应用程序有一个未处理的异常并崩溃,而您只有一个内存转储。第二种情况是,在生产环境中出现异常或特定行为,并且在排除故障时不能将调试器保留在附件中,因为调试器可能会中断用户服务。相反,您可以附加cdb,在正确的位置创建转储文件,然后分离调试器,这意味着应用程序只在服务中有一个小的中断时继续运行。当然,取决于你的环境,这并不总是可能的,但有时可能是你唯一的选择。让我们来看看如何在cdb和Visual Studio中使用内存转储进行调试。这来自本文附带的example2.exe应用程序。它有很多选项,但我们将要讨论的是为什么cdb比Visual Studio更快。首先,您应该启动一个命令提示符并使用调试工具切换到目录,然后运行“doDebug.cmd”文件,如前一篇文章所述(也附在zip文件中)。然后是“cdb-z c:\pathToDumpFile\stackOverflow.dmp”。应按照以下要求打开:

这表明我们有一个内存充足的小型转储,所以我们有我们需要的部分。我们有一个异常,在这种情况下是“堆栈溢出”异常。 我们知道存在堆栈溢出,但不知道这是托管还是非托管问题,所以让我们获取本机堆栈跟踪“kf”

这显示了mscorwks!CallDescrWorker调用了未知的方法。未知的原因是调试器使用模块列表来确定模块的名称(从“lm”获取模块列表),因为CLR JIT的MSIL进入程序集,所以当前模块中存在,因为它挂在进程中的某个位置。

因为mscorwks调用了未知函数,所以这很可能是一个托管问题。在上面的堆栈跟踪中,我使用了kf,因为这些显示了每个堆栈帧使用了多少内存。它实际上计算出了两帧之间的距离,因此第一帧没有内存使用。内存之所以重要,是因为有两种类型的堆栈溢出:第一种是在堆栈上创建太多或太大的对象;第二种是函数递归并耗尽所有内存。

那我们在哪?我们知道这可能是托管代码问题,所以让我们加载so s.dll,'loadby sos mscorwks'并获取堆栈跟踪。注意,因为这是递归的问题,如果你做了!CLRStack“调试器将在cdb中显示一帧又一帧。从Ctrl+C停止它,它将中断跟踪。使用cdb的一个技巧是,如果你需要做一些能输出大量数据的事情,那么就把窗口降到最低,这样就可以让整个团队受益。

所以我们现在有一个显示导致堆栈溢出的函数名的堆栈跟踪:如果我们有源代码,我们可以检查它并查看可能发生的情况,但是让我们快速查看一下MSIL。 和往常一样,我们需要获得helper方法的方法描述;所以我们通过执行“!Name2EE * example2.StackOverflow“。然后我们从“MethodTable:”中获取值并执行“!DumpMT -md 019631a4“。

那么,要得到MSIL,就做“!DumpIL 001963190“我们得到:

所以这很有趣,“ldarg.1”抓取第一个参数,“ldc.i4.1”抓取值1,“add”将它们相加;所以它递增某种类型的计数器,然后它根据_max_RecurseLevel检查结果,如果它大于或等于它,则它退出函数(“bge.s IL_0016”)。这是一个相当简单的例子,但是现在我们需要做的只是看看maxRecurseLevel和传入的值,看看我们在哪里。

顺便说一句,这些是msil文档的链接,当您不了解msil正在做什么时,使用msdn查看每个操作员的操作相当简单:

Ldarg.1 http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.ldarg_1.aspx

Ldc.i4.1 http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.ldc_i4_1.aspx

Bge_s http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.bge_s.aspx

Add http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.add.aspx

好吧,让我们回头看看我们在哪里,传递了什么参数。再次使用ctrl+c准备并执行“!clrstack-a“显示所有参数和局部变量:

注意“this”指针是如何作为参数传递的,它总是第一个,也注意我们只在top方法上看到它。这是因为CLR优化了代码,而不是在堆栈上传递引用,这样会占用更多的空间,而是将其存储在寄存器中并使用它。这是一个CPU级别的优化,这意味着您的应用程序更快,但调试有时可能会更困难。

现在让我们通过“!do 0x01ff66a0“来dump出"this"的值

这表明maxRecurseLevel是100万,可能太高了,所以我们知道需要做什么来修复它。如果我们想猜测一个精确的数字来使用,那么我们可以看到另一个参数的当前值,我们知道这个参数每次都会增加一个,以查看我们有多少个参数。第一个堆栈帧上的值是“i=0x0001f88c”,所以我们只需要将这个十六进制数转换成可读的值?0x0001f88c

所以我们知道它会爆炸大约13万个递归调用。如果我们想知道它是如何进入递归函数的呢?好吧,我们别无选择,只能跑一次“!CLRStack“然后让它完成,我建议添加-a,这样你就可以得到所有的参数,并且在完成之后最小化窗口(记住它有129164个帧要转储,在它到达剩下的代码之前!)如果你想跑“!CLRStack-a“完成后,需要一两分钟,您应该得到:

这向我们展示了Main方法称为helper——如果您查看源代码的话,这并不是严格意义上的正确方法。因为它是一个发布版本,所以很多代码得到了优化,许多参数的值也得到了优化。若要查看堆栈上随时可用并存储的所有值,或在堆栈上具有引用,请执行“!dso”。使用所有这些信息,我们应该有足够的信息来跟踪并修复错误。

正如所承诺的,我说过我会尽可能在Visual Studio 2008中介绍这个示例。如果您启动Visual Studio,请转到文件打开解决方案并浏览到dmp文件。加载后,转到解决方案资源管理器并右键单击转储文件。然后选择“调试步骤进入新实例”。当这开始时,系统会提示您一个“堆栈溢出”异常,这样我们就知道出了什么问题。如果你点击“中断”,你可以进入调试器。

需要注意的是,调用堆栈是错误的,它只显示了一个显然不对的函数,它显示了一页反汇编,但是很难看到您的位置。要查找当前行,您需要显示registers窗口,并查看eip的值,该值给出了您当前所在的地址。。
因此,我们无法获取本机堆栈跟踪来查看我们的位置。相反,让我们继续加载sos.dll并获得“!CLRStack“。如果在打开时打开命令窗口(查看其他窗口命令窗口),如果有“>”提示,则键入“immed”进入立即模式。
如果您输入“.load sos”–我必须承认,在Visual Studio中加载sos.dll的语法确实比在cdb中更容易。这应该表明dll已经加载,现在就做“!CLRStack-a“:但要注意,它需要很长时间才能完成,而且在它完成时,除非您杀死Visual Studio,否则您将无法使用它或停止它。我总是在大约20分钟后杀死Visual Studio,如果你有一台更快的机器或更多的耐心,那么它很可能完成。
当它完成后,你可以继续从我们在cdb得到的痕迹,所以“!Name2EE*example2.StackOverflow”并从“MethodTable:”和“do”中获取值!DumpMT-md 019631a4“然后给你方法描述,然后你可以运行”!扔垃圾“反对。
显然,这是一个有利于cdb的演示,但是Visual Studio的速度确实较慢,而且还有一些时候它明显是这样。另一个我将在以后的文章中进一步讨论的是“奇妙的命令”!DumpHeap-stat“–这是我们用来跟踪内存泄漏的,因为它显示了所有不同类型的对象的列表以及它们使用的内存量,所以您可以一眼看到内存的确切去向。我们开始吧!cdb中的DumpHeap-stat“

这向我们展示了通过增加内存使用量来排列的所有对象,因此列表越往下,该类型使用的内存就越多。在这个视图中,我们得到方法表、计数和总大小。我之所以强调example2.StackOverflow,是因为它是另一种简单的获取类详细信息的方法,我们想进一步研究这个类,在这种情况下,我们有一个它的实例,所以它很简单,如果它是System.String,或者是一个经常使用的类,那么在跟踪正确的类时需要做更多的工作。找到类的具体实例就行了!DumpHeap-type example2“(或-type example2.StackOverflow,因为它是一个子字符串匹配项,两者都可以工作):

如果是的话!DumpHeap-type System.String或其他类,然后,如前所述,列出的将远远不止一个。如果我们想看的对象,我们只是简单地做“!do 01ff66a0“,这是对象的地址,我们可以再次看到maxRecurseLevel的值:

总结

我在本文中已经讨论了相当多的主题,并希望解释了CLR如何在非托管运行时上下文中处理异常。理解它是如何工作的很重要,因为在您自己的代码中使用异常是.NET编程的一个基本部分;理解它的工作方式将有助于在cdb中调试问题,甚至没有.NET异常。

如果您可以从客户或实时生产服务器获取内存转储,那么这是一种解决问题的简单方法,因为您可以很容易地看到崩溃或其他事件的原因。如果您想要一个如何使用它们的示例,Microsoft有一个服务,当您遇到崩溃时,它可以建议使用知识库或支持页来帮助解决问题。watson博士(dw20)可以获取进程的内存转储,然后将此转储上载到其服务器;然后根据已知问题分析堆栈,以自动将您重定向到知识库或支持页以帮助解决问题。微软审查了每种产品中最常见的崩溃,这是他们修复实际影响用户的问题的一个非常好的方法。如果您自己提供软件,那么我建议您在产品中添加一些内容,以便在必要时获得用户内存转储。

至于cdb/Visual Studio,我相信如果你花时间学习Windows的调试工具是如何工作的,你会发现这项工作是值得的。我不能保证你需要经常使用这些技能,但我保证,在某些时候,它们将是无价的。

查看.NET应用程序中的异常(下)的更多相关文章

  1. 查看.NET应用程序中的异常(上)

    内存转储是查明托管.NET应用程序中异常的原因的一种极好的方法,特别是在生产应用程序中发生异常时.当您在无法使用Visual Studio的应用程序中跟踪异常时,cdb和sos.dll的使用技术就变成 ...

  2. 微信小程序之自定义select下拉选项框组件

    知识点:组件,animation,获取当前点击元素的索引与内容 微信小程序中没有select下拉选项框,所以只有自定义.自定义的话,可以选择模板的方式,也可以选择组件的方式来创建. 这次我选择了组件, ...

  3. C++ 中的异常机制分析

    C++异常机制概述 异常处理是C++的一项语言机制,用于在程序中处理异常事件.异常事件在C++中表示为异常对象.异常事件发生时,程序使用throw关键字抛出异常表达式,抛出点称为异常出现点,由操作系统 ...

  4. 逆向project第004篇:令计算器程序显示汉字(下)

    一.前言 钩子技术是一项很有有用价值的技术.在Windows下HOOK技术的方法比較多,使用比較灵活,常见的应用层的HOOK方法有Inline HOOK(详见<反病毒攻防研究第012篇:利用In ...

  5. dart系列之:dart语言中的异常

    目录 简介 Exception和Error Throw和catch Finally 总结 简介 Exception是程序中的异常情况,在JAVA中exception有checked Exception ...

  6. 2019.12.11 java程序中几种常见的异常以及出现此异常的原因

    1.java.lang.NullpointerException(空指针异常) 原因:这个异常经常遇到,异常的原因是程序中有空指针,即程序中调用了未经初始化的对象或者是不存在的对象. 经常出现在创建对 ...

  7. .Java中的异常、断言、日志【草稿下,Log4j专题】

    (本章主要讲解Java里面比较核心的一块内容--异常处理,Java异常处理机制,一致都是比较复杂的一块,而很多时候如果写程序的时候能够适当地注意对应的一些异常处理情况,那么就会在开发过程节省一大部分时 ...

  8. 如何捕获Wince下form程序的全局异常

    前言 上两篇文章我们总结了在winform程序下如何捕获全局的异常.那么同样的问题,在wince下我们如何来处理呢?用相同的代码来处理可以吗? 答案是否定的,上面的方案1完全不能解决wince下的情况 ...

  9. 全栈开发工程师微信小程序-中(下)

    全栈开发工程师微信小程序-中(下) 微信小程序视图层 wxml用于描述页面的结构,wxss用于描述页面的样式,组件用于视图的基本组成单元. // 绑定数据 index.wxml <view> ...

随机推荐

  1. PB 获取或操作数据窗口语句的方法

    1.setsqlselect用法: ls_select=getsqlselect    //通过getsqlselect取得当前数据窗口的查询语句 ls_where="  "    ...

  2. python使用自带模块httplib进行http请求

    #-*- encoding:utf-8 -*- import httplib, time class httpRequest(): def __init__(self, headers, reques ...

  3. Linux文件比对,批量复制

    --背景 工作中突然有一天文件服务器空间满了,导致文件存不进去,立马换了另外一台服务器作为文件服务器,将服务器挂载上去,原来的服务器修复之后需要重新换回来,但是需要将临时使用的服务器内的文件迁移至原文 ...

  4. [Linux学习--用户管理]centos中添加一个新用户,并授权

    前言 有时候给root用户不太方便,新建一个用于并赋予权限这个做法相对好些 创建新用户 创建一个用户名为:cmj [root@localhost ~]# adduser cmj 为这个用户初始化密码, ...

  5. 谈谈MySQL中的锁

    谈谈MySQL中的锁 锁的定义 ​ 在生活中锁的例子就非常多了,所以应该很容易理解锁的含义.在计算机领域,可以这样来概述,锁是计算机协调多个进行进程并发访问某一资源的机制. ​ 在数据库中,锁也是一个 ...

  6. 解决SqlDataSource连接超时的问题

    采用两种策略: 1.连接字符串增加Connect Timeout=1000(大约1000秒/60=16分钟) 2.设置SqlDataSourced 的 EnableCaching="True ...

  7. workermanPHP聊天框架项目windows环境部署实践

    一.官方下载地址: https://www.workerman.net/workerman-chat 二.下载后解压至任意目录,如下图: 三.windows需配置PHP环境变量,如下图: 四.双击st ...

  8. MySQL Charset--UTF8和UTF8MB4对比测试

    UTF8和UTF8MB4 在早期MySQL版本中,使用只支持最长三字节的UTF8字符集便可以存放所有Unicode字符.随着Unicode的完善,Unicode字符集收录的字符数量越来越多,最新版本的 ...

  9. Intellij Idea 导入多个maven项目,通过父工程引入子工程

    刚刚要开始从eclipse切换成idea,据说idea功能强大,可是刚刚开始使用很多不习惯,导入第二个maven项目时之前的项目就没了,比较苦恼,下面介绍下导入多个maven项目展示在左侧栏Maven ...

  10. golang中逗号ok模式_转

    ,ok,第一个参数是一个值或者nil,第二个参数是true/false或者一个错误error.在一个需要赋值的if条件语句中,使用这种模式去检测第二个参数值会让代码显得优雅简洁.这种模式在go语言编码 ...