2023年1月

在安装.NET 4.0或更高版本之后,您可能会注意到.NET进程有点不寻常。下面是用.NET 2.0编译器编译的简单“Hello World”可执行文件的加载模块的部分列表。

 

开始-结束模块名称

60f00000 61491000 mscorwks C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll

6c650000 6c6b6000 mscoreei C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll

6d420000 6d46a000 MSCOREE C:\Windows\SYSTEM32\MSCOREE.DLL

75a80000 75aca000 KERNELBASE C:\Windows\system32\KERNELBASE.dll

 

这里有些东西看起来不合适-mscoreei.dll文件是从v4.0.30319文件夹加载的。它在v2.0.50727的主clr dll(mscorwks.dll)旁边做什么?实际上,这是预期的行为。我们称mscoreei.dll为“shim实现”,或者简称为“shim impl”,它是.NET 4.0的新功能。上面列表中的第三个dll以前称为“shim”,现在更准确地称为“shell shim”。两者紧密地结合在一起,完成先前由mscoree单独完成的主要工作——提供加载运行时的接口。一般来说,shell shim现在由薄包装函数组成,每个薄包装函数将其功能委托给shim实现中的相应函数。

为什么要分开?作为.NET redist安装的一部分,我们已经看到大量计算机重新启动,通常是因为需要更新正在使用的文件。使用中最常见的文件是mscoree.dll,每个.NET应用程序都会加载该文件,甚至一些服务(如MSI)也会加载该文件。因此,我们进行了mscoree“拆分”,以避免机器重新启动。通过将shim实现移动到特定于版本的文件中,我们可以部署mscoreei.dll的新版本(例如,作为.NET 5.0安装的一部分,在v5.0.NET文件夹中),而无需接触计算机范围内的mscoree.dll文件。下次运行托管应用程序时,mscoree将动态查找新的mscoreei,并将其每个函数调用延迟到该mscoreei。这样,我们就可以在运行现有托管应用程序的机器上部署新版本的框架,而不需要重新启动。

请注意,mscoree总是使用它能找到的最新mscoreei,但实际加载的运行时是一个完全不同的问题。因此,看到mscoreei的新版本在同一个进程中加载了运行时dll的旧版本并不奇怪。事实上,CLR版本已经使用最新的填充程序运行多年了。如果在同时安装了.NET 1.1和.NET 2.0的计算机上运行.NET 1.1应用程序时检查文件版本,则可以看到我使用的填充程序比运行时更新。

 

开始-结束模块名称

79000000 79045000 mscoree C:\WINDOWS\system32\mscoree.dll

File version: 2.0.50727.42

791b0000 79412000 mscorwks C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorwks.dll

File version: 1.1.4322.573

 

现在的区别是,shim的拆分使shim版本不总是与运行时版本匹配变得更加明显,因为版本号就在shim impl的路径中。这让我想起了大卫惠勒的一句格言:计算机科学中的所有问题都可以通过另一个层次的间接解决。

为什么有些语言支持它们而另一些不支持呢?把它们加到我的新语言里是个好主意吗?我应该什么时候使用过滤器和catch/rethrow?就像很多事情一样,异常过滤器有好的一面也有坏的一面…

什么是异常过滤器?

CLR提供了许多高级语言可以构建的异常处理原语。有些是相当明显的,并且很容易映射到我们大多数人都知道和喜欢的语言结构:例如try/catch和try/finally。我敢猜测每个人都知道他们在做什么,但为了以防万一,让我们考虑一下C#:

    try{try{
Console.Write(“
1”);if (P) throw newArgumentException();
}
finally{
Console.Write(“
2”);
}
}
catch(ArgumentException e)
{
Console.Write(“
3”);
}
Console.Write(“
4”);

sos.dll/mscordacwks.dll

公共语言运行库(CLR)是执行托管代码的Microsoft.NET框架的核心引擎。简单地说,它通过在托管程序集中使用中间语言和元数据,JIT按需编译代码,构建程序集定义和使用的类型的内存表示,并确保生成的代码是安全的、安全的和可验证的,并在需要时执行。这个引擎本身是用本机代码实现的。当我们想使用本机调试器(如CDB或WinDBG)调试.NET应用程序时(如果我们想使用事后内存转储文件调试它,我们目前做了很多工作),我们必须在本机调试器和托管世界之间使用“桥”,因为本机调试器本身并不理解托管代码。它是本机调试器。
为了提供这个桥,CLR附带了一个调试扩展名SOS.DLL。这理解CLR的内部结构,因此允许我们执行诸如输出托管调用堆栈、转储托管堆等操作。
但是,这些内部数据结构和CLR的详细信息会不时发生变化,因此,从使.NET应用程序工作的CLR的实际内部实现中抽象出此调试器扩展所需的CLR接口是非常有用的。输入mscordacwks.dll。这提供了数据访问组件(DAC),该组件允许SOS.DLL调试器扩展解释维护.NET应用程序状态的内存中数据结构。

如果您在框架文件夹中查看,则应始终看到这些dll的匹配集:

  • 32位的
    C:\Windows\Microsoft.NET\Framework\v4.0.30319
  • 64位的
    C:\Windows\Microsoft.NET\Framework64\v4.0.30319

错误表现

很多时候当你这样做的时候你会得到这个错误。

  1. .load sos

    0:080> .load sos

    The call to LoadLibrary(sos) failed, Win32 error 0n2
    "The system cannot find the file specified."
    Please check your debugger configuration and/or network access.
    You can solve this by doing .loadby sos clr, this means you are trying to load sos.dll from the same location you loaded clr.dll from.


    This is a different error
  2. .loadby sos clr

    0:091> .loadby sos clr


    Unable to find module 'clr'
    我问自己的第一个问题是,我是否在用64位调试器调试32位应用程序,如果不是,我要列出所有加载的模块
    在本例中,我看到没有加载clr.dll,但mscorwks是因为我正在调试的应用程序使用的是.net framework 2.0

    0:091> lm

    start             end                 module name

    00000000`1b6d0000 00000000`1b9cf000   System_Data   (deferred)            

    00000000`1e230000 00000000`1e279000   System_Transactions   (deferred)            

    00000000`714b0000 00000000`71556000   System_Core   (deferred)            

    00000000`77610000 00000000`777b9000   ntdll      (private pdb symbols)  d:\symbols\ntdll.pdb\15EB43E23B12409C84E3CC7635BAF5A32\ntdll.pdb

    00000642`ffaf0000 00000642`ffb09000   mscorsec   (deferred)            

    000007fe`dae40000 000007fe`db68b000   System_Data_ni   (deferred)            

    000007fe`e07b0000 000007fe`e0945000   System_DirectoryServices_ni   (deferred)            

    000007fe`e0950000 000007fe`e0a5d000   System_EnterpriseServices_ni   (deferred)            

    000007fe`e0a60000 000007fe`e0b45000   System_Transactions_ni   (deferred)            

    000007fe`eb380000 000007fe`ec25c000   mscorlib_ni   (deferred)            


    000007fe`ec260000 000007fe`ecbfc000   mscorwks   (deferred)           

  3. .loadby sos mscorwks

    Failed to load data access DLL, 0x80004005 
    Verify that

                1) you have a recent build of the debugger (6.2.14 or newer) 

                2) the file mscordacwks.dll that matches your version of mscorwks.dll is 
                    in the version directory 
                3) or, if you are debugging a dump file, verify that the file 
                    mscordacwks_<arch>_<arch>_<version>.dll is on your symbol path. 
                4) you are debugging on the same architecture as the dump file. 
                    For example, an IA64 dump file must be debugged on an IA64 
                    machine.

    You can also run the debugger command .cordll to control the debugger's
    load of mscordacwks.dll.  .cordll -ve -u -l will do a verbose reload.
    If that succeeds, the SOS command should work on retry.

    If you are debugging a minidump, you need to make sure that your executable
    path is pointing to mscorwks.dll as well.

这些错误消息通常是人们在尝试使用WinDBG/CDB使用SOS调试器扩展调试.NET/CLR应用程序转储时遇到的问题,这意味着SOS.DLL调试器扩展无法找到匹配的mscordacwks.DLL,它需要能够调试您尝试调试的转储文件。如果调试的是实时应用程序,调试器扩展将自动从框架目录中查找并加载mscordacwks.dll。

如果您正在调试一个转储文件,该文件从使用不同版本(例如,不同安装的服务包或修补程序)的CLR应用程序到本地系统上安装的应用程序,或者.NET框架安装在系统上与其安装位置不同的位置,并且调试器无法通过其他方式找到正确的mscordacwks.dll。因为拥有匹配的mscordacwks.dll对于SOS.dll的正常工作非常重要,所以SOS有很多技巧可以找到它。特别是,如果对符号服务器进行了正确的索引,调试器将从那里加载它。调试器还将在调试器目录中查找它,前提是它已以特殊方式重命名

怎么解决

大多数情况下,如果已正确设置了符号路径(无论如何,在调试任何内容时都需要设置该路径,更不用说托管应用程序),则调试器应能够自动从符号服务器获取正确的mscordacwks.dll,执行下面的指令系列:

  1. !sym noisy
  2. .symfix 路径
  3. .cordll -ve -u -l

如果做了上述工作,还不起作用,我们还可以这样做:

最简单的方法是让给您转储文件的人给您mscordacwks.dll的副本。一旦你有了它,检查它的文件属性的版本号。应该是2.0.50727.xxxx。然后将其重命名为mscordacwks_AAA_AAA_2.0.50727.xxxx.dll。其中,xxxx是版本号的适当位,AAA是x86还是AMD64,这取决于您处理的是32位还是64位应用程序转储。(在我们提到x64之前,AMD64是一个遗留的东西)。然后将这个重命名的副本放入调试器目录(安装WinDBG的目录)。然后,根据错误消息,告诉调试器重试:.cordll -ve -u -l
尽管我们试图确保发布的每个CLR版本(作为服务包、修补程序或其他版本)在公共符号服务器上都有其mscordacwks.dll索引,但不幸的是,有时不会发生这种情况。但由于它总是作为CLR的一部分发布,所以您总是可以选择从转储的机器获取它。

如果你按上面指示跑了.cordll -ve -u -l收到了这样的信息:
CLR DLL status: ERROR: Unable to load DLL mscordacwks_AMD64_x86_2.0.50727.3053.dll, Win32 error 0n87

这意味着您很可能使用64位调试器在64位系统上转储32位进程(在WoW64下运行),现在您正尝试使用64位调试器分析转储。这就是消息引用AMD64和x86的原因。这是行不通的。因为SOS.DLL扩展实际上在调试需要匹配的位时使用了框架。我强烈建议始终使用与进程相同位的调试器生成转储(因此,即使系统是x64系统,也要为WoW64进程生成x86调试器),并使用生成转储的调试器的相同位分析转储。当然,这意味着您不能在32位系统上调试64位转储。这还意味着您必须安装框架来调试托管应用程序转储。

你还可能看到下面的信息输出:

0:018> .cordll -ve -u -l
CLRDLL: ERROR: DLL C:WindowsMicrosoft.NETFrameworkv2.0.50727mscordacwks.dll init failure, Win32 error 0n87
CLR DLL status: ERROR: DLL C:WindowsMicrosoft.NETFrameworkv2.0.50727mscordacwks.dll init failure, Win32 error 0n87

这也意味着有点混乱。我在使用32位调试器分析使用64位调试器生成的WoW64进程的转储时看到了这一点。

假设我们希望在加载特定的dll时中断调试器,例如,我想启用一些SOS命令,而clr还没有加载,当您遇到程序中过早发生的异常,并且您不能依赖手动尝试在正确的时间中断时,这尤其有用。
例如,在将调试器附加到一个进程之后,我会得到一个错误,因为clr尚未加载

0:000> .loadby sos clr

Unable to find module 'clr'

使用sxe ld命令,我可以告诉调试器在加载clr时中断.

0:000> sxe ld clr

0:000> g

我马上就明白了:

ModLoad: 6f580000 6fc03000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll

eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=7efdd000 edi=003cf400

eip=77d7fc42 esp=003cf2d4 ebp=003cf328 iopl=0         nv up ei pl zr na pe nc

cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

ntdll!NtMapViewOfSection+0x12:

77d7fc42 83c404          add     esp,4

现在命令就会成功的!

0:000> .loadby sos clr

如果您正在开发软件,很可能遇到了“不匹配的PDB”调试器错误。当您将调试器指向错误的符号路径时,通常会发生这种情况。 但有时你确信你所指向的符号是正确的符号,这让你想知道为什么调试器认为这些符号不匹配。