分类 调试 下的文章

“选项”对话框 ->“调试”->“编辑并继续”

若要访问“编辑并继续”页,请打开“工具”菜单并选择“选项”。 在“选项”对话框中,展开“调试”文件夹并选择“编辑并继续”。 该页允许为“编辑并继续”设置下列选项。

  • 启用“编辑并继续”
    选定后,启用“编辑并继续”。

    “编辑并继续”能够在程序处于中断模式(有一些限制)时更改源代码,并能应用这些更改,而无需结束调试会话和重新生成程序。

    调试期间无法更改此设置。

  • 由调试命令调用
    (仅限本机)选定后,可使“编辑并继续”由执行命令(例如“单步执行”)调用。

  • 首先询问
    (仅限本机)告知调试器在从调试命令调用“编辑并继续”前首先询问您。 这样,您可以根据需要取消调用。

  • 就陈旧的代码发出警告
    (仅限本机)选定后,调试器使用“陈旧代码警告”对话框就陈旧代码向您发出警告。

    在某些情况下,“编辑并继续”无法将代码更改立即应用于可执行文件,但如果您继续调试,则可能稍后会应用代码更改。 “编辑并继续”此时将更新代码。 代码更新之前,源窗口一直以灰色显示原始代码。 因为此代码已被代码更改取代,它已经过时或陈旧。 无法编辑陈旧的代码。

  • 调试后重新链接代码更改
    (仅限本机)调试后重新链接本机代码更改。

    “编辑并继续”不调用自定义生成步骤。 如果程序使用自定义生成步骤,则可能要手动重新生成这些步骤才能调用它们。 在这种情况下,可以在“编辑并继续”后禁用重新链接,以确保系统会提示您手动重新生成。

  • 允许预编译
    (仅限本机)允许“编辑并继续”在后台加载并处理预编译头,以加快对代码更改的处理。 加载预编译头需要分配物理内存,如果您正在一台 RAM
    有限的计算机上进行编译,这可能会是一个问题。 通过在调试期间使用任务管理器确定可用物理内存的数量,可以确定这是否是个问题。
    如果此数量大于预编译头的大小,则“编辑并继续”应没有问题。
    如果此数量小于预编译头的大小,可以通过清除该选项禁止“编辑并继续”在后台加载预编译头。

  • 在远程调试或对以另一个用户帐户运行的应用程序进行调试时启用
    (仅限本机)在计算机之间或跨用户帐户调试时允许执行“编辑并继续”操作。

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

在两种主要情况下,您可能需要使用内存转储进行调试。第一种情况是应用程序有一个未处理的异常并崩溃,而您只有一个内存转储。第二种情况是,在生产环境中出现异常或特定行为,并且在排除故障时不能将调试器保留在附件中,因为调试器可能会中断用户服务。相反,您可以附加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“显示所有参数和局部变量:

“选项”对话框(“工具”菜单)中的“调试”文件夹提供以下页,可以使用它们指定调试器工具选项。下面详细说说。

“选项”对话框 ->“调试”->“常规”

若要访问“常规”页,请单击“工具”菜单并选择“选项”。 在“选项”对话框中,展开“调试”节点并选择“常规”。 该页可设置下列常规调试选项。

  • 在删除所有断点之前询问
    在完成“删除所有断点”命令前需要进行确认。
    就是这个对话框

  • 一个进程中断时则中断所有进程
    发生一个中断时,同时中断调试器附加到的所有进程。

  • 当异常跨越 AppDomain 或托管/本机边界时中断
    在托管或混合模式调试中,如果满足以下条件,公共语言运行时可能会捕获跨越应用程序域边界或托管/本机边界的异常:

    1) 当本机代码使用 COM 互操作调用托管代码而托管代码却引发异常时。

    2) 当在应用程序域 A 中运行的托管代码调用应用程序域 B 中的托管代码而应用程序域 B 中的代码却引发异常时。

    3) 当代码使用反射调用一个函数而该函数却引发异常时。 有关反射的更多信息,请参见 反射。

    在第 2) 和 3) 条中,异常有时由 mscorlib 中的托管代码而不是公共语言运行时捕获。 此选项不影响在 mscorlib 捕获到异常时中断。

  • 启用地址级调试
    启用在地址级上进行调试的高级功能(“反汇编”窗口、“寄存器”窗口和地址断点)。也就是说如果启用该项,将会得到另外三个调试工具:“反汇编”窗口、“寄存器”窗口和地址断点。如果不选中此项,就不会有。

  • 如果源不可用,则显示反汇编
    自动在尝试调试源不可用的代码时显示“反汇编”窗口。

  • 启用断点筛选器
    使您可以在断点上设置筛选器以使其仅影响特定的进程、线程或计算机。当勾选上时,在断点处点击右键可见

    否则不可见

  • 启用异常助手
    仅用于托管代码。 选中此选项时,托管异常打开新的“异常助手”对话框而不是旧的“异常”对话框。 “异常助手”更详细,可提供关于异常的更好帮助。
    勾选上时

    否则

  • 出现未经处理的异常时展开调用堆栈
    导致“调用堆栈”窗口将调用堆栈回滚到未经处理的异常发生之前的点。

  • 启用“仅我的代码”(仅限托管)
    启用此功能时,调试器仅显示和单步执行用户代码(“我的代码”),而忽略系统代码和其他经过优化或没有调试符号的代码。
    那么那些是“你的”代码呢?

    1、你的代码被优化过。
    2、你的代码没有符号
    3、你的代码没有标记DebuggerNonUserCodeAttribute

    例如在一个解决方案里有两个项目:ClassLibrary1 and
    ConsoleApplication1,ConsoleApplication1调用ClassLibrary1 的类在控制台显示一个“hello
    world”,在生成的时候比较ClassLibrary1为优化,在ConsoleApplication1中封装Console.WriteLine方法地方设置一个断点,然后单步调试,到达断点时候,发现在断点处中断了,继续按F11,并没有进入方法内部,而是跳到了下一行。

  • 在变量窗口中显示非用户对象的所有成员(仅限 Visual Basic)
    启用非用户代码(不是“我的代码”)中对象中的非公共成员的显示。

  • 启动时若没有用户代码则发出警告(仅限托管)
    当启用“仅我的代码”进行调试时,此选项在没有用户代码(“我的代码”)的情况下发出警告。

  • 启用 .NET Framework 源代码单步执行
    允许调试器单步执行 .NET Framework 源代码。 自动启用此选项会禁用“仅我的代码”,.NET Framework 符号将下载到缓存位置。 可以在“选项”对话框中“调试”类别下的“符号”页中更改缓存位置。

  • 逐过程执行属性和运算符(仅限托管)
    使调试器无法单步执行托管代码中的属性和运算符。

  • 启用属性求值和其他隐式函数调用
    在变量窗口和“快速监视”对话框中打开属性的自动求值和隐式函数调用。

  • 在变量窗口中对对象调用字符串转换函数(仅限 C# 和 JavaScript)
    在变量窗口中计算对象时,执行隐式字符串转换调用。 因此,结果将显示为字符串而不是类型名。 仅在 C# 代码中进行调试期间适用。 此设置可以由 DebuggerDisplay 特性重写。

  • 启用源服务器支持
    告知 Visual Studio 调试器从用于 Windows 的调试工具中的 SrcSrv 源服务器 (srcsrv.dll) 获取代码。

  • 将源服务器诊断消息打印到输出窗口
    启用源服务器支持时,此设置打开诊断显示。

  • 为断点和当前语句突出显示整个行
    调试器突出显示断点或当前语句时,会突出显示整个行。
    勾选上时

    否则

  • 要求源文件与原始版本完全匹配
    告知调试器验证源文件是否匹配于用于生成正在调试的可执行文件的源代码版本。 如果版本不匹配,则会提示您查找匹配源。 如果未找到匹配源,则在调试过程中不会显示源代码。

  • 将所有输出窗口文本重定向到即时窗口
    将通常显示在“输出”窗口中的所有调试器消息改为发送到“即时”窗口。
    不勾选时

     

    勾选后

     


  • 在变量窗口中显示对象的原始结构
    关闭所有对象结构视图自定义。

  • 在模块加载时取消 JIT 优化(仅限托管)
    在附加调试器的情况下,加载模块并编译 JIT 后,禁用托管代码的 JIT 优化。 禁用优化可能更易于调试某些问题,尽管这会降低性能。 如果正在使用“仅我的代码”,则取消 JIT 优化会导致非用户代码显示为用户代码(“我的代码”)。

  • 启动时若无符号则发出警告(仅限本机)
    选定后,如果您尝试对调试器没有其符号信息的程序进行调试,系统将显示警告对话框。

  • 如果启动时禁用了脚本调试,则发出警告
    选定后,如果在启动调试器时禁用了脚本调试,则会显示警告对话框。

  • 当通过 IL 解释(仅托管)进行小型转储调试时,启用功能评估。
    启用调试器来解释 IL 操作码,以在调试托管转储时模拟功能评估。

  • 自下而上显示并行堆栈关系图
    控制“并行堆栈”窗口中堆栈的显示方向。

内存转储是查明托管.NET应用程序中异常的原因的一种极好的方法,特别是在生产应用程序中发生异常时。当您在无法使用Visual Studio的应用程序中跟踪异常时,cdb和sos.dll的使用技术就变成了它们自己的技术。它们可能也不是你需要经常使用的技能,但在某些时候,它们将是无价的。Edward提供了内存转储示例,并为您提供了一个简单的介绍。

Windows的调试工具占主导地位,sos.dll将CLR内部变成了一个真正的知识源泉。具体来说,我将讨论现有的异常处理框架,讨论.Net异常,如System.exception,以及我所称的“CPU异常”,如访问冲突(C0000005):您可能知道它们是Win32或硬件异常。然后,我将解释如何创建和使用内存转储来查找和修复事后的错误;换句话说,事后调试。 最后,我将展示一个使用cdb和Visual Studio调试的示例。在第一篇文章中(不要给我任何VS),我提倡使用cdb,但是我现在意识到这是一个很大的飞跃:因此,在本文中,我将展示一个使用cdb和sos.dll的示例,然后继续讨论如何在Visual Studio中实现相同的功能,尽管在这个特定的示例中,你需要一定程度的耐心。不过,我想说清楚的是,我个人并不反对Visual Studio或微软,我认为它是一个非常好的IDE,我每天都在使用它,并且对它很满意,我只是相信,Windows的调试工具在调试时给了您更多的控制和动力,当您没有安装Visual Studio(即生产服务器)的选项,或者您没有现成的源代码时,您就无法击败cdb和sos.dll来调试托管应用程序。

什么是异常?

让我们从头开始。异常是指在正常操作下,不应在任何时间发生但可能发生的事件。如果不进行处理,它会阻止应用程序运行。一个例外类似于你的车在你开车的时候停了下来。停车有很多可能的原因;你可能踩了刹车,发动机可能卡住了,车内的乘客可能拉了手刹,或者外部有人把一些相当坚固的东西挡在你的路上。不管发生了什么,如果你处理好了,有些事情你可以恢复,有些事情你不能。

很长一段时间以来,我有一个注册表文件来为WinDbg创建上下文菜单项。这些条目允许您选择x86或x64调试器。在公司内部,我有另一个版本的注册表文件,其中包含了私有符号服务器的另外两个上下文菜单项。你可以在这个截图中看到我在内部添加的所有4个选项。

 

注意,注册表文件假定WinDBG安装在c:\调试器_x86和c:\调试器中(分别用于x86和AMD64调试器)。

注册表项传入Microsoft公共符号服务器(http://msdl.Microsoft.com/download/symbols)和Microsoft专用符号服务器的符号路径。在这两种情况下,符号都通过SRV*链接在本地缓存(例如c:\符号)。与体系结构条目非常相似,这种技术为您提供了一种在公共和私有符号之间切换的快速方法(在我的例子中)。符号路径的传递,而不是使用-NT_symbol_path环境变量,是绕过WinDBG所做的具体化的唯一方法。如果设置了环境变量,则该变量将位于传递的符号路径的前面。这意味着仍然可以使用环境变量路径而不是传递路径。我承认,这只是一个问题,为人们需要支持的两个符号商店和绝大多数人,环境变量是一个更好的方式去做。如果这样做,只需删除命令行的-y部分。

注册表文件为所有转储变量建立关联,包括反射转储文件(*.ini)。反射转储可以使用ProcDump和-r开关(例如ProcDump.exe-r-ma notepad.exe)进行。目前,我使用Visual Studio 2010键来获取图标,并且仍然支持通过Open条目在Visual Studio中打开转储(我不知道您为什么要这样做)。
我做的另一件事是加载常用的调试器扩展。在这个(示例)注册表文件中,我只是通过-a开关(为MSDN Magazine调试API系列创建的扩展名)包含了自己的一个扩展名(myext.dll)。如果要加载更多内容,只需在命令行中包含多个-a myext.dll条目。(另请注意,如果对cdb执行相同操作,则不能在-a和dll名称之间留有空格)。在我的个人版本中,我加载了六个扩展。
您可能需要考虑的一件事是添加-WX开关以忽略默认工作区。我亲自按照自己喜欢的方式设置默认工作区(命令窗口停靠、在主监视器上最大化WinDbg、为每个文本类型设置字体颜色等),并将其用作每个新调试会话的模板。因此,我不希望使用-WX开关(这是一个双负数),因为我希望应用工作区。但是,如果要始终使用“开箱即用”默认值,请添加-WX开关。

Windows Registry Editor Version 5.00[HKEY_CLASSES_ROOT\.dmp]
@
="VisualStudio.dmp.10.0"[HKEY_CLASSES_ROOT\.hdmp]
@
="VisualStudio.dmp.10.0"[HKEY_CLASSES_ROOT\.mdmp]
@
="VisualStudio.dmp.10.0"[HKEY_CLASSES_ROOT\.kdmp]
@
="VisualStudio.dmp.10.0"[HKEY_CLASSES_ROOT\.ini]
@
="VisualStudio.dmp.10.0"[HKEY_CLASSES_ROOT\VisualStudio.dmp.10.0\shell]
@
="WinDbg_x64_public"[HKEY_CLASSES_ROOT\VisualStudio.dmp.10.0\shell\WinDbg_x86_public]
@
="Open with WinDbg x86 (Public)"[HKEY_CLASSES_ROOT\VisualStudio.dmp.10.0\shell\WinDbg_x86_public\Command]
@
="\"C:\\Debuggers_x86\\windbg.exe\" -z \"%1\" -a myext.dll -y \"SRV*C:\\Symbols*http://msdl.microsoft.com/download/symbols\\ [HKEY_CLASSES_ROOT\VisualStudio.dmp.10.0\shell\WinDbg_x64_public]
@
="Open with WinDbg x64 (Public)"[HKEY_CLASSES_ROOT\VisualStudio.dmp.10.0\shell\WinDbg_x64_public\Command]
@
="\"C:\\Debuggers\\windbg.exe\" -z \"%1\" -a myext.dll -y \"SRV*C:\\Symbols*http://msdl.microsoft.com/download/symbols\\