分类 调试 下的文章

简介

让我们从我写这篇文章的原因开始。一天,一个同事让我帮他调试他遇到的问题。所以我看着他在输入代码,这时我注意到下面一行:

int test = GetLastError();

他这样做是因为他想知道错误代码,如果之前的函数失败了。他每次想知道错误代码就加上这一行。我建议他删除所有这些行并在他的监视窗口中使用@ERR伪寄存器。他不知道这是什么,在办公室里到处打听,很多人都不知道,所以我为从来没有听说过伪寄存器的人写了这篇文章。

什么是伪寄存器?

伪寄存器不是当前的硬件寄存器,而是像硬件寄存器一样显示。使用伪寄存器,您可以在调试器中看到并使用某些值(错误代码、线程信息块…)。让我们看看@ERR伪寄存器。用您最喜欢的应用程序启动调试器。在代码中放置断点,以便调试器中断执行。打开“监视”窗口(如果尚未打开)(右键单击某个空工具栏空间,然后从该列表中选择“监视”来执行此操作)。在这个监视窗口中添加@ERR。您应该在值列中看到0。现在检查一下你的代码,看看这个值。它将始终显示当前线程的GetLastError()数字。所以如果你的代码出错了,这个值就会改变。
如果你想测试这个,但是你的代码没有任何错误,我建议你放一些进去(但是不要忘记以后删除它们)。您可以插入如下内容:

FILE *fp = fopen("c:\\a_file_that_does_not_exist.txt", "r");

如果执行这一行,就会看到@ERR值变为2。转到“工具”->“错误查找”查看此错误值的含义(“如果您想知道,系统找不到指定的文件”)。像我这样懒散的流浪汉,还有像你这样聪明的小伙子/姑娘,可以把@ERR伪寄存器改成@ERR,hr。这样做会将伪寄存器的值更改为错误字符串。现在您甚至不必查找错误。我一直把“@ERR,hr”放在观察窗里。

条件表达式

伪寄存器也可用于条件表达式。要尝试此操作,请在fopen后面加上以下行:

if (fp)
{
fclose(fp);
}

在if(fp)行上设置断点。转到“编辑”->“断点”(或按Alt-F9)。选择刚才插入的断点并按“条件”按钮。在这里,您可以输入@ERR==2条件。现在启动调试器。如果fopen()由于找不到文件而失败,调试器将在此断点上中断。如果文件确实存在,调试器将不会中断,即使它遇到另一个错误(例如错误4:无法打开文件)。通过在创建后运行代码(不是步进),并在c:\中删除“AyFieleToSodoSoNothOx.Txt”文件来尝试这一点。

如何在调试会话中找到调试对象中使用的.NET运行时版本?以自动/脚本方式,不使用调试器扩展或符号?

答案:

!for_each_module .if ( ($sicmp( "@#ModuleName" , "mscorwks") = 0) | ($sicmp( "@#ModuleName" , "mscorsvr") = 0) | ($sicmp( "@#ModuleName" , "clr") = 0)) {.echo @#ProductVersion}

0:000:x86>  !for_each_module .if ( ($sicmp( "@#ModuleName" , "mscorwks") = 0) | ($sicmp( "@#ModuleName" , "mscorsvr") = 0) | ($sicmp( "@#ModuleName" , "clr") = 0)) {.echo @#ProductVersion}
4.7.3132.0
1a: Unable to retrieve module names (8007007e)
34: Unable to retrieve module names (8007007e)
87: Unable to retrieve module names (8007007e)
8a: Unable to retrieve module names (8007007e)
9f: Unable to retrieve module names (8007007e)
b0: Unable to retrieve module names (8007007e)
b8: Unable to retrieve module names (8007007e)
c0: Unable to retrieve module names (8007007e)
c1: Unable to retrieve module names (8007007e)
c5: Unable to retrieve module names (8007007e)
cc: Unable to retrieve module names (8007007e)
d2: Unable to retrieve module names (8007007e)

如名称的Win32部分所示System.ComponentModel.Win32Exception仅在处理传统样式的应用程序或代码时发生-应用程序必须调用直接操作系统调用,例如尝试执行其他应用程序。在本文中,我们将深入探讨System.ComponentModel.Win32异常。更详细地说,包括它在.NET异常层次结构中的位置。我们还将深入研究一些函数C#代码示例,以更好地说明如何System.ComponentModel.Win32Exceptions可能会在自己的编码中被抛出!

要点

  • 所有 .NET exceptions 继承自 System.Exception base class, 或者继承自inherited class.
  • System.SystemException 继承自 System.Exception class.
  • System.Runtime.InteropServices.ExternalException 直接继承自System.SystemException.
  • 最后, System.ComponentModel.Win32Exception 继承自 System.Runtime.InteropServices.ExternalException.

什么时候用它呢?

这个System.ComponentModel.Win32异常是在使用内部win32样式的操作系统调用时发生错误时在.NET应用程序中发生的最基本的异常类型。这些问题可能不同,从无效路径和文件找不到错误到网络地址问题和资源管理问题。因为System.ComponentModel.Win32Exceptions是旧形式异常的包装器,您可能遇到的每个可能的错误都有自己的NativeErrorCode属性值,它是一个32位整数,引用与引发的异常相关联的Win32错误代码值。
我们的示例代码开始。整个代码段如下所示,之后我们将更详细地讨论它:

usingSystem.Diagnostics;usingUtility;namespaceAirbrake.ComponentModel.Win32Exception
{
classProgram
{
static void Main(string[] args)
{
StartProcessFromPath(
"c:/windows/notepad.exe");
Logging.LineSeparator();
StartProcessFromPath(
"c:/windows/invalid.exe");
}
static void StartProcessFromPath(stringpath)
{
try{//Create a new process with StartInfo.FileName set to provided path. var process = new Process { StartInfo = { FileName =path } };//Attempt to start the process using provided executable path. var success =process.Start();if(success)
{
Logging.Log($
"Successfully launched '{process.ProcessName.ToString()}' process!");//Sleep for two seconds to allow time for window to be shown. System.Threading.Thread.Sleep(2000);//Kill process. process.Kill();
Logging.Log($
"Killed '{process.ProcessName.ToString()}' process.");
}
else{//This code never executes since we're catching//an exception from the process.Start() invocation line. }
}
catch(System.ComponentModel.Win32Exception exception)
{
//Indicate failure to start. Logging.Log($"Unable to start process with executable path '{path}'.");//Output caught exception. Logging.Log(exception);
Logging.Log($
"Native Win32 Error Code: {exception.NativeErrorCode}");
}
}
}
}
usingSystem;usingSystem.Collections;usingSystem.Collections.Generic;usingSystem.Diagnostics;usingSystem.Reflection;usingSystem.Text;namespaceUtility
{
/// <summary> ///Houses all logging methods for various debug outputs./// </summary> public static classLogging
{
/// <summary> ///Outputs to<see cref="System.Diagnostics.Debug.WriteLine"/>if DEBUG mode is enabled,///otherwise uses standard<see cref="Console.WriteLine"/>./// </summary> /// <param name="value">Value to be output to log.</param> public static void Log(stringvalue)
{
#if DEBUGDebug.WriteLine(value);#elseConsole.WriteLine(value);#endif}/// <summary> ///When<see cref="Exception"/>parameter is passed, modifies the output to indicate///if<see cref="Exception"/>was expected, based on passed in `expected` parameter./// <para>Outputs the full<see cref="Exception"/>type and message.</para> /// </summary> /// <param name="exception">The<see cref="Exception"/>to output.</param> /// <param name="expected">Boolean indicating if<see cref="Exception"/>was expected.</param> public static void Log(Exception exception, bool expected = true)
{
string value = $"[{(expected ?"EXPECTED":"UNEXPECTED")}] {exception.ToString()}: {exception.Message}";#if DEBUGDebug.WriteLine(value);#elseConsole.WriteLine(value);#endif}/// <summary> ///Outputs a dashed line separator to<see cref="System.Diagnostics.Debug.WriteLine"/> ///if DEBUG mode is enabled, otherwise uses standard<see cref="Console.WriteLine"/>./// </summary> public static voidLineSeparator()
{
#if DEBUGDebug.WriteLine(new string('-', 20));#elseConsole.WriteLine(new string('-', 20));#endif}
}
}

有许多选项可用于控制符号的加载和使用方式。这些选项可以通过多种方式设置。

下表列出了这些符号选项:

FlagOption NameDefault in debuggerDefault in DBH

0x1

SYMOPT_CASE_INSENSITIVE

On

On

0x2

SYMOPT_UNDNAME

On

On

0x4

SYMOPT_DEFERRED_LOADS

On

Off

0x8

SYMOPT_NO_CPP

Off

Off

0x10

SYMOPT_LOAD_LINES

Off in KD and CDB

On in WinDbg

On

0x20

SYMOPT_OMAP_FIND_NEAREST

On

Off

0x40

SYMOPT_LOAD_ANYTHING

Off

Off

0x80

SYMOPT_IGNORE_CVREC

Off

Off

0x100

SYMOPT_NO_UNQUALIFIED_LOADS

Off

Off

0x200

SYMOPT_FAIL_CRITICAL_ERRORS

On

Off

0x400

SYMOPT_EXACT_SYMBOLS

Off

On

0x800

SYMOPT_ALLOW_ABSOLUTE_SYMBOLS

Off

On

0x1000

SYMOPT_IGNORE_NT_SYMPATH

Off

Off

0x2000

SYMOPT_INCLUDE_32BIT_MODULES

Off

Off

0x4000

SYMOPT_PUBLICS_ONLY

Off

Off

0x8000

SYMOPT_NO_PUBLICS

Off

Off

0x10000

SYMOPT_AUTO_PUBLICS

On

On

0x20000

SYMOPT_NO_IMAGE_SEARCH

On

Off

0x40000

SYMOPT_SECURE

Off

Off

0x80000

SYMOPT_NO_PROMPTS

On in KD and CDB

Off in WinDbg

Off

0x80000000

SYMOPT_DEBUG

Off

Off

更改符号选项设置

.symopt(设置符号选项)命令可用于更改或显示符号选项设置。此外,可以使用许多命令行参数和命令来更改这些设置;这些参数和命令在各个SYMOPT_XXX部分中列出。
您还可以使用-sflagscommand-line选项同时控制所有设置。此选项后面可以跟一个十进制数,也可以跟一个前缀为0x的十六进制数。建议您使用十六进制,因为符号标志是以这种方式正确对齐的。使用此方法时要小心,因为它设置了整个位字段并将覆盖所有符号处理程序默认值。例如,-sflags 0x401不仅将启用SYMOPT_EXACT_SYMBOLS和SYMOPT_CASE_INSENSITIVE,而且还将禁用默认情况下正常打开的所有其他选项!
当这些程序在没有任何与符号相关的命令行选项的情况下启动时,WinDbg中的总标志位的默认值为0x30237,CDB和KD中的默认值为0xB0227,DBH工具中的默认值为0x10C13。

SYMOPT_CASE_INSENSITIVE

此符号选项导致对符号名的所有搜索都不区分大小写。默认情况下,在所有调试器中都启用此选项。调试器运行后,可以分别使用.symopt+0x1或.symopt-0x1来打开或关闭它。在DBH中,默认情况下启用此选项。一旦DBH运行,就可以分别使用symopt+1或symopt-1来打开或关闭它。

SYMOPT_UNDNAME

这个符号选择公共符号的名称在显示时被理解,原因搜索符号名称以忽略符号装饰。私人符号名称从来没有装饰过,请注意此选项是否有效。此选项适用于所有调试器。当调试器运行时,它可以通过SYMOPT+0x2或SYMOPT-0x2分别打开或关闭。这个选项在DBH中是默认打开的。如果使用了-d选项,就取消了。一旦DBH运行,它可以通过SYMOPT+2或SYMOPT+2打开或关闭。

SYMOPT_DEFERRED_LOADS

此符号选项称为延迟符号加载或延迟符号加载。当它处于活动状态时,在加载目标模块时不会实际加载符号。相反,调试器根据需要加载符号。默认情况下,在所有调试器中都启用此选项。在CDB和KD中,-s选项将关闭此选项。也可以在CDB中使用tools.ini文件中的LazyLoad变量来关闭它。调试器运行后,可以分别使用.symopt+0x4或.symopt-0x4来打开或关闭此选项。默认情况下,在DBH中此选项处于禁用状态。一旦DBH运行,就可以分别使用symopt+4或symopt-4来打开或关闭它。

SYMOPT_NO_CPP

这个符号选项关闭C++翻译。设置此符号选项时,所有符号中的::将替换为__ 。默认情况下,在所有调试器中都禁用此选项。它可以通过使用-snc选项激活。调试程序运行后,可以分别使用.symopt+0x8或.symopt-0x8来打开或关闭它。默认情况下,在DBH中此选项处于禁用状态。一旦DBH运行,就可以分别使用symopt+8或symopt-8来打开或关闭它。

SYMOPT_LOAD_LINES

此符号选项允许从源文件中读取行号信息。必须启用此选项,源调试才能正常工作。在KD和CDB中,此选项默认为关闭;在WinDbg中,此选项默认为打开。在CDB和KD中,-lines命令行选项将启用此选项。调试程序运行后,可以分别使用.symopt+0x10或.symopt-0x10来打开或关闭它。也可以使用.lines(Toggle Source Line Support)命令来打开和关闭它。在DBH中,默认情况下启用此选项。一旦DBH运行,就可以分别使用symopt+10或symopt-10来打开或关闭它。

SYMOPT_OMAP_FIND_NEAREST

当代码已优化且预期位置没有符号时,此选项将使用最近的符号。默认情况下,在所有调试器中都启用此选项。调试器运行后,可以分别使用.symopt+0x20或.symopt-0x20来打开或关闭它。在DBH中,默认情况下启用此选项。运行DBH之后,可以分别使用symopt+20或symopt-20来打开或关闭它。

SYMOPT_LOAD_ANYTHING

此符号选项可减少符号处理程序在尝试匹配符号时的挑剔程度。默认情况下,在所有调试器中都禁用此选项。调试程序运行后,可以分别使用.symopt+0x40或.symopt-0x40来打开或关闭它。默认情况下,在DBH中此选项处于禁用状态。一旦DBH运行,就可以分别使用symopt+40或symopt-40来打开或关闭它。

SYMOPT_IGNORE_CVREC

此符号选项使符号处理程序在搜索符号时忽略加载的image header中的CV记录。默认情况下,在所有调试器中都禁用此选项。它可以通过使用-sicv命令行选项激活。调试程序运行后,可以分别使用.symopt+0x80或.symopt-0x80来打开或关闭它。默认情况下,在DBH中此选项处于禁用状态。一旦DBH运行,就可以分别使用symopt+80或symopt-80来打开或关闭它。

SYMOPT_NO_UNQUALIFIED_LOADS

此符号选项禁用符号处理程序自动加载模块。当设置此选项并且调试器尝试匹配符号时,它将只搜索已加载的模块。此选项可用于防止错误输入符号名称。通常,键入错误的符号将导致调试器在搜索所有卸载的符号文件时暂停。当此选项处于活动状态时,在加载的模块中找不到键入错误的符号,然后搜索将终止。默认情况下,在所有调试器中都禁用此选项。它可以通过使用-snul命令行选项激活。调试器运行后,可以分别使用.symopt+0x100或.symopt-0x100来打开或关闭它。默认情况下,在DBH中此选项处于禁用状态。运行DBH之后,可以分别使用symopt+100或symopt-100来打开或关闭它。

SYMOPT_FAIL_CRITICAL_ERRORS

此符号选项将禁止显示文件访问错误对话框。如果禁用此选项,则在符号加载期间遇到的文件访问错误(如“驱动器未就绪”)将导致出现对话框。如果启用此选项,则这些框将被抑制,所有访问错误都将收到“失败”响应。默认情况下,在所有调试器中都启用此选项。可以使用-sdce命令行选项停用它。调试程序运行后,可以分别使用.symopt+0x200或.symopt-0x200来打开或关闭它。默认情况下,在DBH中此选项处于禁用状态。运行DBH之后,可以分别使用symopt+200或symopt-200来打开或关闭它。

SYMOPT_EXACT_SYMBOLS

此符号选项使调试器对所有符号文件执行严格的求值。启用此选项时,即使符号文件与符号处理程序的期望值之间存在微小差异,也会导致忽略符号。默认情况下,在所有调试器中都禁用此选项。它可以通过使用-ses命令行选项激活。调试程序运行后,可以分别使用.symopt+0x400或.symopt-0x400来打开或关闭它。-failinc命令行选项还可以打开SYMOPT_EXACT_SYMBOLS。此外,如果正在调试用户模式minidump或内核模式minidump,-failinc将阻止调试器加载其映像无法映射的任何模块。在DBH中,默认情况下启用此选项。一旦DBH运行,就可以分别使用symopt+400或symopt-400来打开或关闭它。

SYMOPT_ALLOW_ABSOLUTE_SYMBOLS

此符号选项允许DbgHelp读取存储在内存中绝对地址的符号。在绝大多数情况下不需要这种选择。默认情况下,在所有调试器中都禁用此选项。调试程序运行后,可以分别使用.symopt+0x800或.symopt-0x800来打开或关闭它。在DBH中,默认情况下启用此选项。一旦DBH运行,就可以分别使用symopt+800或symopt-800来打开或关闭它。

SYMOPT_IGNORE_NT_SYMPATH

此符号选项使调试器忽略符号路径和可执行映像路径的环境变量设置。默认情况下,在所有调试器中都禁用此选项。它可以通过使用-sins命令行选项激活。但是,一旦调试器运行,它就不能由.symopt控制,因为环境变量只在启动时读取。此选项在DBH中默认为禁用,在所有情况下都被DBH忽略。

SYMOPT_PUBLICS_ONLY

此符号选项使DbgHelp忽略私有符号数据,并仅搜索公共符号表以获取符号信息。这将在添加对这些类型的支持之前模拟DbgHelp的行为。默认情况下,在所有调试器中都禁用此选项。调试程序运行后,可以分别使用.symopt+0x4000或.symopt-0x4000来打开或关闭它。默认情况下,在DBH中此选项处于禁用状态。如果使用-d命令行选项,则该选项处于启用状态。一旦DBH运行,就可以分别使用symopt+4000或symopt-4000来打开或关闭它。

SYMOPT_NO_PUBLICS

此符号选项防止DbgHelp搜索公共符号表。这可以使符号枚举和符号搜索更快。如果您只关心搜索速度,则SYMOPT_AUTO_PUBLICS选项通常比此选项更可取。有关公共符号表的信息。默认情况下,在所有调试器中都禁用此选项。调试程序运行后,可以分别使用.symopt+0x8000或.symopt-0x8000来打开或关闭它。默认情况下,在DBH中此选项处于禁用状态。DBH运行后,可以分别使用symopt+8000或symopt-8000来打开或关闭它。

SYMOPT_AUTO_PUBLICS

此符号选项使DbgHelp只能作为最后的手段在.pdb文件中搜索公共符号表。如果在搜索专用符号数据时发现任何匹配项,则不会搜索公用符号。这提高了符号搜索速度。默认情况下,在所有调试器中都启用此选项。可以使用-sup命令行选项停用它。调试程序运行后,可以分别使用.symopt+0x10000或.symopt-0x10000来打开或关闭它。在DBH中,默认情况下启用此选项。如果使用-d命令行选项,则该选项将关闭。运行DBH之后,可以分别使用symopt+10000或symopt-10000来打开或关闭它。

SYMOPT_NO_IMAGE_SEARCH

此符号选项防止DbgHelp在加载符号时在磁盘中搜索image的副本。默认情况下,在所有调试器中都启用此选项。调试程序运行后,可以分别使用.symopt+0x20000或.symopt-0x20000来打开或关闭它。默认情况下,在DBH中此选项处于禁用状态。一旦DBH运行,就可以分别使用symopt+20000或symopt-20000来打开或关闭它。