这是我的《Advanced .Net Debugging》这个系列的第六篇文章。这篇文章的内容是原书的第二部分的【调试实战】的第四章。这章主要讲的是程序集加载器,比如:CLR 加载器简介、简单的程序集加载故障、加载上下文故障、互用性与 DllNotFoundException 和轻量级代码生成的调试。有了这章内容的学习,对于 CLR 如何加载程序集,加载的上下文和算法会有一个充分的了解。当然,有关程序集加载的错误也会有所调试和验证。高级调试会涉及很多方面的内容,你对 .NET 基础知识掌握越全面、细节越底层,调试成功的几率越大,当我们遇到各种奇葩问题的时候才不会手足无措。
如果在没有说明的情况下,所有代码的测试环境都是 Net 8.0,如果有变动,我会在项目章节里进行说明。好了,废话不多说,开始我们今天的调试工作。
调试环境我需要进行说明,以防大家不清楚,具体情况我已经罗列出来。
操作系统:Windows Professional 10
调试工具:Windbg Preview(Debugger Client:1.2306.1401.0,Debugger engine:10.0.25877.1004)和 NTSD(10.0.22621.2428 AMD64)
下载地址:可以去Microsoft Store 去下载
开发工具:Microsoft Visual Studio Community 2022 (64 位) - Current版本 17.8.3
Net 版本:.Net 8.0
CoreCLR源码:源码下载
二、目录结构
为了让大家看的更清楚,也为了自己方便查找,我做了一个目录结构,可以直观的查看文章的布局、内容,可以有针对性查看。
2.1、CLR 加载器介绍
2.1.1、程序集标识
A、基础知识
B、眼见为实
1)、NTSD 调试
2)、Windbg Preview 调试
2.1.2、全局程序集缓存
2.1.3、默认加载上下文
2.1.4、指定加载上下文
2.1.5、无加载上下文
2.2、简单的程序集加载故障
A、基础知识
B、眼见为实
1)、NTSD 调试
2)、Windbg Preview 调试
3)、探测程序集的加载过程
A、基础知识
B、眼见为实
2.3、加载上下文故障(Net Framework版本)
A、基础知识
B、眼见为实
1)、NTSD 调试
2)、Windbg Preview 调试
2.4、互用性与 DllNotFoundException
A、基础知识
B、眼见为实
2.5、轻量级代码生成的调试
A、基础知识
B、眼见切实
1)、NTSD 调试
2)、Windbg Preview 调试
三、调试源码
废话不多说,本节是调试的源码部分,没有代码,当然就谈不上测试了,调试必须有载体。
3.1、Example_4_1_1(Net Framework 4.8 )
1 using System; 2 using System.Reflection; 3 4 namespace Example_4_1_1 5 { 6 internal class Program 7 { 8 static void Main(string[] args) 9 { 10 Assembly assemblyLoadFromContext = null; 11 Console.WriteLine("Press any key to load into load from context"); 12 Console.ReadKey(); 13 14 assemblyLoadFromContext = Assembly.LoadFrom("ExampleCore_4_1_1.old"); 15 Console.ReadLine(); 16 } 17 } 18 }
3.2、ExampleCore_4_1_1
1 using System.Reflection; 2 3 namespace ExampleCore_4_1_1 4 { 5 public class SimpleType 6 { 7 private int _field1; 8 private int _field2; 9 10 public int Field1 11 { 12 get { return _field1; } 13 } 14 15 public int Fields 16 { 17 get { return _field2; } 18 } 19 20 public SimpleType() 21 { 22 _field1 = 10; 23 _field2 = 5; 24 } 25 } 26 internal class Program 27 { 28 static void Main(string[] args) 29 { 30 Assembly? assemblyLoadFromContext = null; 31 Console.WriteLine("Press any key to load into load from context"); 32 Console.ReadKey(); 33 34 assemblyLoadFromContext = Assembly.LoadFrom("E:\\Visual Studio 2022\\Source\\Projects\\AdvancedDebug.NetFramework.Test\\ExampleCore_4_1_1\\bin\\Debug\\net8.0\\ExampleCore_4_1_1.1.dll"); 35 36 var localObject = (SimpleType)assemblyLoadFromContext.CreateInstance("ExampleCore_4_1_1.SimpleType")!; 37 38 Console.WriteLine("Press any key to Exit"); 39 Console.ReadKey(); 40 } 41 } 42 }
3.3、ExampleCore_4_1_1.1
1 namespace ExampleCore_4_1_1 2 { 3 public class SimpleType 4 { 5 private int _field1; 6 private int _field2; 7 8 public int Field1 9 { 10 get { return _field1; } 11 } 12 13 public int Fields 14 { 15 get { return _field2; } 16 } 17 18 public SimpleType() 19 { 20 _field1 = 10; 21 _field2 = 5; 22 } 23 } 24 }
3.4、Example_4_1_2._2(Net Framework 4.8)
1 using System; 2 using System.Runtime.Remoting; 3 4 namespace Example_4_1_2._2 5 { 6 [Serializable] 7 public class Entity 8 { 9 public int a; 10 } 11 12 [Serializable] 13 public class EntityUtil 14 { 15 public void Dump(Entity e) 16 { 17 Console.WriteLine(e.a); 18 } 19 } 20 21 internal class Program 22 { 23 static void Main(string[] args) 24 { 25 Program p = new Program(); 26 p.Run(); 27 28 Console.WriteLine("按任意键程序运行结束。"); 29 Console.ReadLine(); 30 } 31 32 public void Run() 33 { 34 while (true) 35 { 36 Console.WriteLine("1、Run in default app domain"); 37 Console.WriteLine("2、Run in dedicated app domain"); 38 Console.WriteLine("Q、To quit."); 39 Console.Write("> "); 40 ConsoleKeyInfo consoleKeyInfo = Console.ReadKey(); 41 Console.WriteLine(); 42 43 if (consoleKeyInfo.KeyChar == '1') 44 { 45 RunInDefault(); 46 } 47 else if (consoleKeyInfo.KeyChar == '2') 48 { 49 RunInDedicated(); 50 } 51 else if (consoleKeyInfo.KeyChar == 'q' || consoleKeyInfo.KeyChar == 'Q') 52 { 53 break; 54 } 55 } 56 } 57 58 public AppDomain CreateDomain() 59 { 60 AppDomainSetup appDomainSetup = new AppDomainSetup(); 61 appDomainSetup.ApplicationBase = @"C:\Windows\System32"; 62 return AppDomain.CreateDomain("MyDomain", null, appDomainSetup); 63 } 64 65 public void RunInDefault() 66 { 67 EntityUtil t2 = new EntityUtil(); 68 Entity t = new Entity(); 69 t.a = 10; 70 71 t2.Dump(t); 72 } 73 74 public void RunInDedicated() 75 { 76 AppDomain domain = CreateDomain(); 77 78 ObjectHandle h = domain.CreateInstance("Example_4_1_2.2", "ExampleCore_4_1_2.EntityUtil"); 79 80 EntityUtil t2 = (EntityUtil)h.Unwrap(); 81 82 Entity t = new Entity(); 83 t.a = 10; 84 t2.Dump(t); 85 } 86 } 87 }
3.5、ExampleCore_4_1_3
1 using System.Runtime.InteropServices; 2 3 namespace ExampleCore_4_1_3 4 { 5 internal class Program 6 { 7 [DllImport("ExampleCore_4_1_4.dll", CharSet = CharSet.Auto)] 8 public static extern void Alloc(string str); 9 10 static void Main(string[] args) 11 { 12 var str = "hello world"; 13 14 Alloc(str); 15 16 Console.ReadLine(); 17 } 18 } 19 }
3.6、ExampleCore_4_1_4(C++)
1 extern "C" 2 { 3 __declspec(dllexport) void Alloc(wchar_t* c); 4 } 5 6 #include "iostream" 7 #include <Windows.h> 8 9 using namespace std; 10 11 void Alloc(wchar_t* c) 12 { 13 wprintf(L"%s------\n", c); 14 }
3.7、ExampleCore_4_1_5
1 using System.Diagnostics; 2 using System.Reflection.Emit; 3 4 namespace ExampleCore_4_1_5 5 { 6 internal class Program 7 { 8 private delegate int Add(int a, int b); 9 10 static void Main(string[] args) 11 { 12 Type[] mylocalArgs = { typeof(int), typeof(int) }; 13 DynamicMethod method = new DynamicMethod("Add", typeof(int), mylocalArgs); 14 15 ILGenerator generator = method.GetILGenerator(); 16 generator.Emit(OpCodes.Ldarg_0); 17 generator.Emit(OpCodes.Ldarg_1); 18 generator.Emit(OpCodes.Add); 19 generator.Emit(OpCodes.Ret); 20 21 Add add = (Add)method.CreateDelegate(typeof(Add)); 22 Debugger.Break(); 23 int result = add(1, 2); 24 25 Console.WriteLine($"1+2={result}"); 26 Console.Read(); 27 } 28 } 29 }
四、基础知识
在这一段内容中,有的小节可能会包含两个部分,分别是 A 和 B,也有可能只包含 A,如果只包含 A 部分,A 字母会省略。A 是【基础知识】,讲解必要的知识点,B 是【眼见为实】,通过调试证明讲解的知识点。
本章主要介绍 CLR 加载器(代码名称 fusion),以及它是如何避免 DLL 地狱问题和与 CLR 加载器相关的其他一些陷阱。如果大家不知道 DLL Hell 是什么问题的,可以自行去网上查找学习。
4.1、CLR 加载器简介
从宏观来看,程序集分为两类,一种是:私有程序集,这类程序集是属于某个特定应用程序的,它们通常保存在与应用程序本身路径相同的文件中。第二种是:共享程序集,这类程序集是由同一台机器上的两个或者更多个应用程序使用的程序集。共享程序集会被安装到全局程序集缓存中(GAC)。
在面对不同类型的程序集时,CLR 加载器如何判断从什么位置加载程序集呢?我们直接上一个图来说明问题。
在请求加载一个程序集的时候,CLR 加载器将判断加载上下文,并且根据上下文采取不同的算法来判断程序集的位置。
4.1.1、程序集标识
A、基础知识
程序集的标识定义了程序集的唯一性。当 CLR 加载器要加载程序集的时候,这个标识是非常重要的。接下来,我们看看程序集标识由什么组成。
程序集标识由以下关键部分组成:
程序集的名称:程序集简单的名称,通常是程序集的文件名,不包含扩展名 .exe 或者 .dll。
文化:程序集的目标地域(例如:中性文化=neutral)。
版本:表示程序集的版本,形如:<major>.<minor>.<build>.<revision>。
公钥:公钥属性用于强命名的程序集,包含了公钥的64位散列码,这个公钥与用于对程序集签名的私钥是相对应的。
处理器架构:在 CLR 2.0 中引入的处理器架构属性指定了程序集的目标处理器架构。
由以上5个属性合在一起构成了程序集标识。在 CLR 中广泛的使用程序集标识来判断被加载的程序集是否正确。需要注意的是,就程序集标识而言,当加载正确的程序集时,程序集的路径并不是 CLR 考虑的因素。如果两个相同的程序集位于不同的路径中,那它们也可以加载到同一个应用程序域中,只是使用了不同的加载上下文。即使这两个程序集是相同的,它们仍然被视为两个不同的程序集。当然。在程序集中包含相同的类型同样被认为是不同的。
B、眼见为实
调试源码:ExampleCore_4_1_1 和 ExampleCore_4_1_1.1
调试任务:通过不同加载上下文加载相同的类型也认为是不同的。
1)、NTSD 调试
编译项目,打开【Visual Studio 2022 Developer Command Prompt v17.10.4】命令行工具,输入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_1\bin\Debug\net8.0\ExampleCore_4_1_1.exe】,回车进入到调试器中。
1 Microsoft (R) Windows Debugger Version 10.0.22621.2428 AMD64 2 Copyright (c) Microsoft Corporation. All rights reserved. 3 4 CommandLine: E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_1\bin\Debug\net8.0\ExampleCore_4_1_1.exe 5 6 ************* Path validation summary ************** 7 Response Time (ms) Location 8 Deferred srv* 9 Symbol search path is: srv* 10 Executable search path is: 11 ModLoad: 00007ff7`969f0000 00007ff7`96a19000 apphost.exe 12 ModLoad: 00007fff`975d0000 00007fff`977c8000 ntdll.dll 13 ModLoad: 00007fff`95460000 00007fff`9551d000 C:\Windows\System32\KERNEL32.DLL 14 ModLoad: 00007fff`94940000 00007fff`94c36000 C:\Windows\System32\KERNELBASE.dll 15 ModLoad: 00007fff`97240000 00007fff`973de000 C:\Windows\System32\USER32.dll 16 ModLoad: 00007fff`95260000 00007fff`95282000 C:\Windows\System32\win32u.dll 17 ModLoad: 00007fff`955e0000 00007fff`9560b000 C:\Windows\System32\GDI32.dll 18 ModLoad: 00007fff`94f50000 00007fff`95067000 C:\Windows\System32\gdi32full.dll 19 ModLoad: 00007fff`94e90000 00007fff`94f2d000 C:\Windows\System32\msvcp_win.dll 20 ModLoad: 00007fff`95160000 00007fff`95260000 C:\Windows\System32\ucrtbase.dll 21 ModLoad: 00007fff`96460000 00007fff`96bcb000 C:\Windows\System32\SHELL32.dll 22 ModLoad: 00007fff`959d0000 00007fff`95a80000 C:\Windows\System32\ADVAPI32.dll 23 ModLoad: 00007fff`974f0000 00007fff`9758e000 C:\Windows\System32\msvcrt.dll 24 ModLoad: 00007fff`97440000 00007fff`974e0000 C:\Windows\System32\sechost.dll 25 ModLoad: 00007fff`96bd0000 00007fff`96cf5000 C:\Windows\System32\RPCRT4.dll 26 ModLoad: 00007fff`94cb0000 00007fff`94cd7000 C:\Windows\System32\bcrypt.dll 27 (45a4.3ee4): Break instruction exception - code 80000003 (first chance) 28 ntdll!LdrpDoDebuggerBreak+0x30: 29 00007fff`976a0730 cc int 3 30 0:000>
此时,调试器处于中断中断,我们使用【.cls】命令清空屏幕,使用【g】命令运行程序。直到控制台输出【Press any key to load into load from context】,我们回车继续执行,调试器会抛出异常,并中断执行。
1 0:000> g 2 ModLoad: 00007fff`973e0000 00007fff`97412000 C:\Windows\System32\IMM32.DLL 3 ModLoad: 00007fff`76f00000 00007fff`76f59000 C:\Program Files\dotnet\host\fxr\8.0.7\hostfxr.dll 4 ModLoad: 00007fff`63090000 00007fff`630f4000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\hostpolicy.dll 5 ModLoad: 00007ffe`b5620000 00007ffe`b5b05000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\coreclr.dll 6 ModLoad: 00007fff`95a90000 00007fff`95bbb000 C:\Windows\System32\ole32.dll 7 ModLoad: 00007fff`95cb0000 00007fff`96003000 C:\Windows\System32\combase.dll 8 ModLoad: 00007fff`96d00000 00007fff`96dcd000 C:\Windows\System32\OLEAUT32.dll 9 ModLoad: 00007fff`95070000 00007fff`950f2000 C:\Windows\System32\bcryptPrimitives.dll 10 (45a4.3ee4): Unknown exception - code 04242420 (first chance) 11 ModLoad: 00007ffe`b23d0000 00007ffe`b305d000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Private.CoreLib.dll 12 ModLoad: 00007ffe`b5460000 00007ffe`b5619000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\clrjit.dll 13 ModLoad: 00007fff`94f30000 00007fff`94f42000 C:\Windows\System32\kernel.appcore.dll 14 ModLoad: 000001a1`9cbe0000 000001a1`9cbe8000 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_1\bin\Debug\net8.0\ExampleCore_4_1_1.dll 15 ModLoad: 000001a1`9cbf0000 000001a1`9cbfe000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Runtime.dll 16 ModLoad: 00007fff`8f3f0000 00007fff`8f418000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Console.dll 17 ModLoad: 00007fff`82860000 00007fff`82872000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Threading.dll 18 ModLoad: 000001a1`9cc00000 000001a1`9cc08000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Text.Encoding.Extensions.dll 19 ModLoad: 00007fff`76c70000 00007fff`76c85000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Runtime.InteropServices.dll 20 Press any key to load into load from context 21 ModLoad: 000001a1`9cc20000 000001a1`9cc28000 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_1\bin\Debug\net8.0\ExampleCore_4_1_1.1.dll 22 (45a4.3ee4): C++ EH exception - code e06d7363 (first chance) 23 ModLoad: 00007fff`5ba30000 00007fff`5bc5e000 C:\Windows\SYSTEM32\icu.dll 24 (45a4.3ee4): CLR exception - code e0434352 (first chance) 25 (45a4.3ee4): CLR exception - code e0434352 (!!! second chance !!!) 26 KERNELBASE!RaiseException+0x69: 27 00007fff`9496cf19 0f1f440000 nop dword ptr [rax+rax] 28 0:000>
红色标注的说明调试器捕获到了异常。我们使用【!PrintException】或者【!pe】命令,打印当前的异常信息。
1 0:000> !PrintException 2 WARNING: SOS needs to be upgraded for this version of the runtime. Some commands may not work correctly. 3 For more information see https://go.microsoft.com/fwlink/?linkid=2135652 4 5 Exception object: 000001a19f80a480 6 Exception type: System.InvalidCastException 7 Message: [A]ExampleCore_4_1_1.SimpleType cannot be cast to [B]ExampleCore_4_1_1.SimpleType. Type A originates from 'ExampleCore_4_1_1.1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_1\bin\Debug\net8.0\ExampleCore_4_1_1.1.dll'. Type B originates from 'ExampleCore_4_1_1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_1\bin\Debug\net8.0\ExampleCore_4_1_1.dll'. 8 InnerException: <none> 9 StackTrace (generated): 10 SP IP Function 11 00000082FCD9E460 00007FFEB275F500 System_Private_CoreLib!System.Runtime.CompilerServices.CastHelpers.ChkCast_Helper(Void*, System.Object)+0xa0 12 00000082FCD9E4A0 00007FFE55BF19CB ExampleCore_4_1_1!ExampleCore_4_1_1.Program.Main(System.String[])+0x9b 13 14 StackTraceString: <none> 15 HResult: 80004002 16 0:000>
红色标注的就是异常的详细信息,表示类型转换无效。虽然这两个类型本质上是相等的,但 CLR 似乎将它们视为不同的类型定义,这正是混合使用不同加载上下文带来的危险性之一。
在本示例中,我们在 ExampleCore_4_1_1 中定义了一个类型 SimpleType,并在 ExampleCore_4_1_1.1 中定义了一个相同的类型 SimpleType。而且,我们通过 Assembly.LoadFom 这个 API 加载 ExampleCore_4_1_1.1.dll,这表示这个程序集是在指定加载上下文中加载的,因而带有相同类型的两个不同程序集就加载到同一个应用程序域中,但每个类型却视为独立的实体。2)、Windbg Preview 调试
这里的调试和【NTSD】调试器的一样,编译项目,打开【Windbg Preview】,依次点击【文件】----【Launch executable】,加载我们项目的 exe 可执行文件 ExampleCore_4_1_1.exe,进入到调试器。
使用【.cls】命令清空屏幕,使用【g】命令运行程序。我们的控制台程序提示输出【Press any key to load into load from context】,回车继续执行。
此时,调试器捕获异常并中断执行。
1 0:000> g 2 ModLoad: 00007fff`973e0000 00007fff`97412000 C:\Windows\System32\IMM32.DLL 3 ModLoad: 00007ffe`c7b50000 00007ffe`c7ba9000 C:\Program Files\dotnet\host\fxr\8.0.7\hostfxr.dll 4 ModLoad: 00007ffe`89c80000 00007ffe`89ce4000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\hostpolicy.dll 5 ModLoad: 00007ffe`89790000 00007ffe`89c75000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\coreclr.dll 6 ModLoad: 00007fff`95a90000 00007fff`95bbb000 C:\Windows\System32\ole32.dll 7 ModLoad: 00007fff`95cb0000 00007fff`96003000 C:\Windows\System32\combase.dll 8 ModLoad: 00007fff`96d00000 00007fff`96dcd000 C:\Windows\System32\OLEAUT32.dll 9 ModLoad: 00007fff`95070000 00007fff`950f2000 C:\Windows\System32\bcryptPrimitives.dll 10 (28f8.2880): Unknown exception - code 04242420 (first chance) 11 ModLoad: 00007ffe`88630000 00007ffe`892bd000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Private.CoreLib.dll 12 ModLoad: 00007ffe`88470000 00007ffe`88629000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\clrjit.dll 13 ModLoad: 00007fff`94f30000 00007fff`94f42000 C:\Windows\System32\kernel.appcore.dll 14 ModLoad: 0000026a`0ea10000 0000026a`0ea18000 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_1\bin\Debug\net8.0\ExampleCore_4_1_1.dll 15 ModLoad: 0000026a`0ea20000 0000026a`0ea2e000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Runtime.dll 16 ModLoad: 00007ffe`b5320000 00007ffe`b5348000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Console.dll 17 ModLoad: 00007fff`110f0000 00007fff`11102000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Threading.dll 18 ModLoad: 0000026a`0ea30000 0000026a`0ea38000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Text.Encoding.Extensions.dll 19 ModLoad: 00007fff`0e080000 00007fff`0e095000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Runtime.InteropServices.dll 20 ModLoad: 0000026a`103f0000 0000026a`103f8000 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_1\bin\Debug\net8.0\ExampleCore_4_1_1.1.dll 21 (28f8.2880): C++ EH exception - code e06d7363 (first chance) 22 ModLoad: 00007fff`5ba30000 00007fff`5bc5e000 C:\Windows\SYSTEM32\icu.dll 23 (28f8.2880): CLR exception - code e0434352 (first chance) 24 (28f8.2880): CLR exception - code e0434352 (!!! second chance !!!) 25 KERNELBASE!RaiseException+0x69: 26 00007fff`9496cf19 0f1f440000 nop dword ptr [rax+rax]
红色标注的就是具体的异常,我们继续使用【!PrintException】或者【!pe】命令打印异常信息。
1 0:000> !PrintException 2 Exception object: 0000026a1300a480 3 Exception type: System.InvalidCastException 4 Message: [A]ExampleCore_4_1_1.SimpleType cannot be cast to [B]ExampleCore_4_1_1.SimpleType. Type A originates from 'ExampleCore_4_1_1.1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_1\bin\Debug\net8.0\ExampleCore_4_1_1.1.dll'. Type B originates from 'ExampleCore_4_1_1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_1\bin\Debug\net8.0\ExampleCore_4_1_1.dll'. 5 InnerException: <none> 6 StackTrace (generated): 7 SP IP Function 8 00000082055EE740 00007FFE889BF500 System_Private_CoreLib!System.Runtime.CompilerServices.CastHelpers.ChkCast_Helper(Void*, System.Object)+0xa0 9 00000082055EE780 00007FFE29D619CB ExampleCore_4_1_1!ExampleCore_4_1_1.Program.Main(System.String[])+0x9b 10 11 StackTraceString: <none> 12 HResult: 80004002
红色标注的就是异常的详细信息,表示类型转换无效。虽然这两个类型本质上是相等的,但 CLR 似乎将它们视为不同的类型定义,这正是混合使用不同加载上下文带来的危险性之一。
在本示例中,我们在 ExampleCore_4_1_1 中定义了一个类型 SimpleType,并在 ExampleCore_4_1_1.1 中定义了一个相同的类型 SimpleType。而且,我们通过 Assembly.LoadFom 这个 API 加载 ExampleCore_4_1_1.1.dll,这表示这个程序集是在指定加载上下文中加载的,因而带有相同类型的两个不同程序集就加载到同一个应用程序域中,但每个类型却视为独立的实体。
4.1.2、全局程序集缓存
GAC 是保存所有共享程序集的常用位置。GAC 位于:%windir%\assembly,我的实际目录地址是:C:\Windows\assembly,效果如图:
在 GAC 中的程序集必须是强命名的,并且拥有唯一的版本号。在共享程序集加载的过程中(在默认的加载上下文中),CLR 加载器会首先检查 GAC 来确定这个程序集是否存在,然后再查看私有的加载路径。
我们可以使用【Windbg Preview】工具,使用【lm f】命令查看目标进程中所有已加载的模块以及它们相应的加载路径。这个很简单,直接贴结果。这个里面,我使用【lm】和【lm f】两个命令,大家可以自己体会区别。
1 0:006> lm 2 start end module name 3 000001d5`2a100000 000001d5`2a108000 ExampleCore_4_1_0 C (service symbols: CLR Symbols with PDB: C:\ProgramData\Dbg\sym\ExampleCore_4_1_0.pdb\E55CC9E3404548D5AF090BFC4FBE04D01\ExampleCore_4_1_0.pdb) 4 000001d5`2a110000 000001d5`2a11e000 System_Runtime (deferred) 5 000001d5`2a120000 000001d5`2a128000 ExampleCore_4_1_0_Clone (deferred) 6 000001d5`2a130000 000001d5`2a138000 System_Text_Encoding_Extensions (deferred) 7 00007ff6`5f210000 00007ff6`5f239000 apphost C (private pdb symbols) C:\ProgramData\Dbg\sym\apphost.pdb\5633DAB747FE452D91289F0AE5A53DEB1\apphost.pdb 8 00007ffc`00b00000 00007ffc`00b28000 System_Console (deferred) 9 00007ffc`00b30000 00007ffc`00ce9000 clrjit (deferred) 10 00007ffc`00cf0000 00007ffc`0197c000 System_Private_CoreLib (service symbols: CLR Symbols with PDB: C:\ProgramData\Dbg\sym\System.Private.CoreLib.pdb\44580BF6DF5DBDE0A6AF2B06590C0AF21\System.Private.CoreLib.pdb) 11 00007ffc`01e80000 00007ffc`02366000 coreclr (deferred) 12 00007ffc`02370000 00007ffc`023d4000 hostpolicy (deferred) 13 00007ffc`06f10000 00007ffc`06f69000 hostfxr (deferred) 14 00007ffc`0dff0000 00007ffc`0e005000 System_Runtime_InteropServices (deferred) 15 00007ffc`0e1a0000 00007ffc`0e1b2000 System_Threading (deferred) 16 00007ffc`daa10000 00007ffc`daaad000 msvcp_win (deferred) 17 00007ffc`dab30000 00007ffc`dab42000 kernel_appcore (deferred) 18 00007ffc`dac00000 00007ffc`dad00000 ucrtbase (deferred) 19 00007ffc`dad00000 00007ffc`dad82000 bcryptPrimitives (deferred) 20 00007ffc`dae90000 00007ffc`dafa7000 gdi32full (deferred) 21 00007ffc`db000000 00007ffc`db027000 bcrypt (deferred) 22 00007ffc`db0b0000 00007ffc`db0d2000 win32u (deferred) 23 00007ffc`db240000 00007ffc`db536000 KERNELBASE (deferred) 24 00007ffc`db540000 00007ffc`db893000 combase (deferred) 25 00007ffc`db920000 00007ffc`db952000 IMM32 (deferred) 26 00007ffc`db9c0000 00007ffc`dc12b000 SHELL32 (deferred) 27 00007ffc`dc1e0000 00007ffc`dc290000 ADVAPI32 (deferred) 28 00007ffc`dc460000 00007ffc`dc58b000 ole32 (deferred) 29 00007ffc`dc680000 00007ffc`dc6ab000 GDI32 (deferred) 30 00007ffc`dc830000 00007ffc`dc8fd000 OLEAUT32 (deferred) 31 00007ffc`dd020000 00007ffc`dd0c0000 sechost (deferred) 32 00007ffc`dd0c0000 00007ffc`dd25e000 USER32 (deferred) 33 00007ffc`dd380000 00007ffc`dd4a5000 RPCRT4 (deferred) 34 00007ffc`dd530000 00007ffc`dd5ed000 KERNEL32 (pdb symbols) C:\ProgramData\Dbg\sym\kernel32.pdb\B07C97792B439ABC0DF83499536C7AE51\kernel32.pdb 35 00007ffc`dd5f0000 00007ffc`dd68e000 msvcrt (deferred) 36 00007ffc`dd6d0000 00007ffc`dd8c8000 ntdll (pdb symbols) C:\ProgramData\Dbg\sym\ntdll.pdb\90C2362B9D1F1F5088ABFA3BDE69BAAF1\ntdll.pdb 37 38 0:006> lm f 39 start end module name 40 000001d5`2a100000 000001d5`2a108000 ExampleCore_4_1_0 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_0\bin\Debug\net8.0\ExampleCore_4_1_0.dll 41 000001d5`2a110000 000001d5`2a11e000 System_Runtime C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.3\System.Runtime.dll 42 000001d5`2a120000 000001d5`2a128000 ExampleCore_4_1_0_Clone E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_0\bin\Debug\net8.0\ExampleCore_4_1_0_Clone.dll 43 000001d5`2a130000 000001d5`2a138000 System_Text_Encoding_Extensions C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.3\System.Text.Encoding.Extensions.dll 44 00007ff6`5f210000 00007ff6`5f239000 apphost apphost.exe 45 00007ffc`00b00000 00007ffc`00b28000 System_Console C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.3\System.Console.dll 46 00007ffc`00b30000 00007ffc`00ce9000 clrjit C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.3\clrjit.dll 47 00007ffc`00cf0000 00007ffc`0197c000 System_Private_CoreLib C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.3\System.Private.CoreLib.dll 48 00007ffc`01e80000 00007ffc`02366000 coreclr C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.3\coreclr.dll 49 00007ffc`02370000 00007ffc`023d4000 hostpolicy C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.3\hostpolicy.dll 50 00007ffc`06f10000 00007ffc`06f69000 hostfxr C:\Program Files\dotnet\host\fxr\8.0.3\hostfxr.dll 51 00007ffc`0dff0000 00007ffc`0e005000 System_Runtime_InteropServices C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.3\System.Runtime.InteropServices.dll 52 00007ffc`0e1a0000 00007ffc`0e1b2000 System_Threading C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.3\System.Threading.dll 53 00007ffc`daa10000 00007ffc`daaad000 msvcp_win C:\Windows\System32\msvcp_win.dll 54 00007ffc`dab30000 00007ffc`dab42000 kernel_appcore C:\Windows\System32\kernel.appcore.dll 55 00007ffc`dac00000 00007ffc`dad00000 ucrtbase C:\Windows\System32\ucrtbase.dll 56 00007ffc`dad00000 00007ffc`dad82000 bcryptPrimitives C:\Windows\System32\bcryptPrimitives.dll 57 00007ffc`dae90000 00007ffc`dafa7000 gdi32full C:\Windows\System32\gdi32full.dll 58 00007ffc`db000000 00007ffc`db027000 bcrypt C:\Windows\System32\bcrypt.dll 59 00007ffc`db0b0000 00007ffc`db0d2000 win32u C:\Windows\System32\win32u.dll 60 00007ffc`db240000 00007ffc`db536000 KERNELBASE C:\Windows\System32\KERNELBASE.dll 61 00007ffc`db540000 00007ffc`db893000 combase C:\Windows\System32\combase.dll 62 00007ffc`db920000 00007ffc`db952000 IMM32 C:\Windows\System32\IMM32.DLL 63 00007ffc`db9c0000 00007ffc`dc12b000 SHELL32 C:\Windows\System32\SHELL32.dll 64 00007ffc`dc1e0000 00007ffc`dc290000 ADVAPI32 C:\Windows\System32\ADVAPI32.dll 65 00007ffc`dc460000 00007ffc`dc58b000 ole32 C:\Windows\System32\ole32.dll 66 00007ffc`dc680000 00007ffc`dc6ab000 GDI32 C:\Windows\System32\GDI32.dll 67 00007ffc`dc830000 00007ffc`dc8fd000 OLEAUT32 C:\Windows\System32\OLEAUT32.dll 68 00007ffc`dd020000 00007ffc`dd0c0000 sechost C:\Windows\System32\sechost.dll 69 00007ffc`dd0c0000 00007ffc`dd25e000 USER32 C:\Windows\System32\USER32.dll 70 00007ffc`dd380000 00007ffc`dd4a5000 RPCRT4 C:\Windows\System32\RPCRT4.dll 71 00007ffc`dd530000 00007ffc`dd5ed000 KERNEL32 C:\Windows\System32\KERNEL32.DLL 72 00007ffc`dd5f0000 00007ffc`dd68e000 msvcrt C:\Windows\System32\msvcrt.dll 73 00007ffc`dd6d0000 00007ffc`dd8c8000 ntdll ntdll.dll
当使用 Windows 资源管理器查看 GAC 时,它是 GAC 的视图版本,它是由 shell 扩展 shfusion.dll 实现的。
大多数程序集都是使用默认加载上下文(Default load context),也是最安全的加载方式,可以避免加载不正确的程序集。使用默认加载上下文的程序集是通过 Assembly.Load 及其变化形式来加载的。在默认的加载上下文中,CLR 要执行所有的探测逻辑,从而保证加载正确版本的程序集。如图:
这样做有一个好处,依赖的程序集也可以在默认加载上下文中自动被找到。这与指定加载上下文(Load-From)或者无加载上下文(Load-Without)是不同的,在这些上下文中,调用者可以显示的选择程序集,因此,更容易出现错误。
当一个程序集加载请求进入默认加载上下文时,CLR 会首先判断这个程序集是否在 GAC 中。如果是,则直接加载 GAC 中正确版本的程序集。如果不在 GAC 中,CLR 加载器将探测其他路径,包括应用程序所在的路径和私有二进制文件所在的路径等。如果在这两个位置中的某一个找到了程序集,CLR 则直接从该位置加载程序集。
4.1.4、指定加载上下文
当一个程序集被加载到指定加载上下文中时,通常是通过 Assembly.LoadFrom 等 API 的某种变化形式来实现的,此时,CLR 不会执行探测逻辑,而是由调用者负责消除程序集的冲突。程序集依赖的所有其他程序集将从同一路径加载。
被加载到指定加载上下文中的程序集可以使用默认加载上下文中的程序集。如果一个程序集已经被加载到指定加载上下文,那么当再次尝试把同一个程序集加载到默认加载上下文时,就会导致一个错误。
使用指定加载上下文时有些需要注意的,如图:
4.1.5、无加载上下文
有些程序集的加载是不需要上下文的,比如:一些通过 Reflection 命令空间和 Emit 命令空间生成的程序集。在这种情况下,CLR 是不做任何探测的。
4.2、简单的程序集加载故障
A、基础知识
在原书中讲的很多内容都发生了变化。我们首先来看一个简单的程序集加载故障,并介绍在分析程序集加载故障时可以使用的技术。我们使用与清单 ExampleCore_4_1_1 和 ExampleCore_4_1_1.1 中相同的程序,其中主程序集(ExampleCore_4_1_1.exe)只是加载另一个程序集 ExampleCore_4_1_1.1.dll。这两个文件都位于目录 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_1\bin\Debug\net8.0 下。然而,在运行这个程序之前,请首先把ExampleCore_4_1_1.1.dll 重命名为 ExampleCore_4_1_1.1.old。在重新命名后,可以继续执行程序集 ExampleCore_4_1_1.exe。
如何我们直接双击 ExampleCore_4_1_1.exe 运行程序,程序会抛出异常,效果如图:
这个很容易理解,找不到文件就抛出了异常。
B、眼见为实
调试源码:ExampleCore_4_1_1 和 ExampleCore_4_1_1.1
调试任务:通过调试器分析系统异常
1)、NTSD 调试
编译项目,打开【Visual Studio 2022 Developer Command Prompt v17.10.4】命令行工具,输入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_1\bin\Debug\net8.0\ExampleCore_4_1_1.exe】,回车直接进入调试器。
执行命令【sxe e0434352】,当有任何CLR异常发生时都将停止执行。
1 0:000> sxe e0434352 2 0:000>
【g】继续执行调试器。
1 0:000> g 2 ModLoad: 00007ffb`fb9e0000 00007ffb`fba12000 C:\Windows\System32\IMM32.DLL 3 ModLoad: 00007ffb`bdbe0000 00007ffb`bdc39000 C:\Program Files\dotnet\host\fxr\8.0.7\hostfxr.dll 4 ModLoad: 00007ffb`aa7a0000 00007ffb`aa804000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\hostpolicy.dll 5 ModLoad: 00007ffb`576a0000 00007ffb`57b85000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\coreclr.dll 6 ModLoad: 00007ffb`fb330000 00007ffb`fb45b000 C:\Windows\System32\ole32.dll 7 ModLoad: 00007ffb`fad80000 00007ffb`fb0d3000 C:\Windows\System32\combase.dll 8 ModLoad: 00007ffb`fb260000 00007ffb`fb32d000 C:\Windows\System32\OLEAUT32.dll 9 ModLoad: 00007ffb`fa260000 00007ffb`fa2e2000 C:\Windows\System32\bcryptPrimitives.dll 10 (35f4.1040): Unknown exception - code 04242420 (first chance) 11 ModLoad: 00007ffb`1e220000 00007ffb`1eead000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Private.CoreLib.dll 12 ModLoad: 00007ffb`37f70000 00007ffb`38129000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\clrjit.dll 13 ModLoad: 00007ffb`fa2f0000 00007ffb`fa302000 C:\Windows\System32\kernel.appcore.dll 14 ModLoad: 0000015c`593b0000 0000015c`593b8000 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_1\bin\Debug\net8.0\ExampleCore_4_1_1.dll 15 ModLoad: 0000015c`593c0000 0000015c`593ce000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Runtime.dll 16 ModLoad: 00007ffb`cfc00000 00007ffb`cfc28000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Console.dll 17 ModLoad: 00007ffb`d1d10000 00007ffb`d1d22000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Threading.dll 18 ModLoad: 0000015c`593d0000 0000015c`593d8000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Text.Encoding.Extensions.dll 19 ModLoad: 00007ffb`cfbb0000 00007ffb`cfbc5000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Runtime.InteropServices.dll 20 Press any key to load into load from context 21 (35f4.1040): C++ EH exception - code e06d7363 (first chance) 22 (35f4.1040): CLR exception - code e0434352 (first chance)(这里只有一个 CLR 异常,因为执行了 sxe 命令,一旦有 CLR 异常抛出调试器就会中断执行,如果后面还有 CLR 异常,也不会再抛出了) 23 First chance exceptions are reported before any exception handling. 24 This exception may be expected and handled. 25 KERNELBASE!RaiseException+0x69: 26 00007ffb`faa2cf19 0f1f440000 nop dword ptr [rax+rax] 27 0:000>
如果不执行【sxe】命令,也会抛出异常信息,调试器也会中断执行,但是还是有区别的。
1 0:000> g 2 ModLoad: 00007ffb`fb9e0000 00007ffb`fba12000 C:\Windows\System32\IMM32.DLL 3 ModLoad: 00007ffb`cea40000 00007ffb`cea99000 C:\Program Files\dotnet\host\fxr\8.0.7\hostfxr.dll 4 ModLoad: 00007ffb`ab3c0000 00007ffb`ab424000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\hostpolicy.dll 5 ModLoad: 00007ffb`576a0000 00007ffb`57b85000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\coreclr.dll 6 ModLoad: 00007ffb`fb330000 00007ffb`fb45b000 C:\Windows\System32\ole32.dll 7 ModLoad: 00007ffb`fad80000 00007ffb`fb0d3000 C:\Windows\System32\combase.dll 8 ModLoad: 00007ffb`fb260000 00007ffb`fb32d000 C:\Windows\System32\OLEAUT32.dll 9 ModLoad: 00007ffb`fa260000 00007ffb`fa2e2000 C:\Windows\System32\bcryptPrimitives.dll 10 (3d04.3e2c): Unknown exception - code 04242420 (first chance) 11 ModLoad: 00007ffb`1dfe0000 00007ffb`1ec6d000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Private.CoreLib.dll 12 ModLoad: 00007ffb`a7b60000 00007ffb`a7d19000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\clrjit.dll 13 ModLoad: 00007ffb`fa2f0000 00007ffb`fa302000 C:\Windows\System32\kernel.appcore.dll 14 ModLoad: 00000181`d3a90000 00000181`d3a98000 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_1\bin\Debug\net8.0\ExampleCore_4_1_1.dll 15 ModLoad: 00000181`d3aa0000 00000181`d3aae000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Runtime.dll 16 ModLoad: 00007ffb`cf030000 00007ffb`cf058000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Console.dll 17 ModLoad: 00007ffb`d1d10000 00007ffb`d1d22000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Threading.dll 18 ModLoad: 00000181`d3ab0000 00000181`d3ab8000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Text.Encoding.Extensions.dll 19 ModLoad: 00007ffb`cf010000 00007ffb`cf025000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Runtime.InteropServices.dll 20 Press any key to load into load from context 21 (3d04.3e2c): C++ EH exception - code e06d7363 (first chance) 22 (3d04.3e2c): CLR exception - code e0434352 (first chance) 23 (3d04.3e2c): CLR exception - code e0434352 (!!! second chance !!!)(如果不执行 sxe 命令,这里会抛出两个 CLR 异常) 24 KERNELBASE!RaiseException+0x69: 25 00007ffb`faa2cf19 0f1f440000 nop dword ptr [rax+rax] 26 0:000>
其实,我感觉执行【sxe】命令与否,关系不是很大,只是原书上是先这样执行的。
接下来,我们使用【kb】命令,转储当前线程的调用栈(非托管和托管都都包含)栈回溯并显示参数。
1 0:000> kb 2 RetAddr : Args to Child : Call Site 3 00007ffb`5775b423 : 00000181`d680a050 00000046`0137e2e0 00000046`0137e910 00000181`d6809d68 : KERNELBASE!RaiseException+0x69(抛出异常的方法) 4 00007ffb`576a9cfc : 00000181`d3a6b900 00000046`0137e608 00000181`d1fbff10 00000181`d3a8e090 : coreclr!RaiseTheExceptionInternalOnly+0x26b 5 00007ffb`5782d231 : 00000046`0137e608 00000181`d1fbff10 00000046`0137e650 00007ffb`57b268c0 : coreclr!UnwindAndContinueRethrowHelperAfterCatch+0x38 6 00007ffb`1e355f73 : 000001c2`687d051c 00000181`d6809de0 00000000`00000000 00000046`0137e608 : coreclr!AssemblyNative_LoadFromPath+0x1825a1 7 00007ffb`1e37d55b : 00000181`d6809de0 000001c2`687d0510 00000181`d6809d30 00000000`00000002 : System_Private_CoreLib!System.Reflection.Assembly System.Runtime.Loader.AssemblyLoadContext::LoadFromAssemblyPath(System.String)$##600462A+0xf3 8 00007ffa`f7c81992 : 000001c2`687d0510 00000000`00000001 00000181`d1fc6760 00000181`d6809c38 : System_Private_CoreLib!System.Reflection.Assembly System.Reflection.Assembly::LoadFrom(System.String)$##6006760+0x17b 9 00007ffb`577fbd43 : 00000181`d6808ea0 00000046`0137ed98 00000046`0137ed98 00000046`0137e989 : 0x00007ffa`f7c81992 10 00007ffb`57730ac9 : 00000000`00000000 00000000`00000130 00000046`0137e998 00007ffb`576b022e : coreclr!CallDescrWorkerInternal+0x83 11 (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!CallDescrWorkerWithHandler+0x56 12 00007ffb`5772d6e0 : 00000046`0137ea18 00000000`00000000 00000000`00000048 00007ffb`5779d176 : coreclr!MethodDescCallSite::CallTargetWorker+0x2a1 13 (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!MethodDescCallSite::Call+0xb 14 00007ffb`57752ff6 : 00000181`d6808ea0 00000181`d6808ea0 00000000`00000000 00000046`0137ed98 : coreclr!RunMainInternal+0x11c 15 00007ffb`5775332b : 00000181`d1fc6760 00000181`00000000 00000181`d1fc6760 00000000`00000000 : coreclr!RunMain+0xd2 16 00007ffb`576a9141 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000130 : coreclr!Assembly::ExecuteMainMethod+0x1bf 17 00007ffb`577be9e8 : 00000000`00000001 00000046`0137ef01 00000046`0137efc0 00007ffb`ab3c23ea : coreclr!CorHost2::ExecuteAssembly+0x281 18 00007ffb`ab3e2b76 : 00000181`d1f96260 00000181`d1f95f60 00000000`00000000 00000181`d1f95f60 : coreclr!coreclr_execute_assembly+0xd8 19 (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : hostpolicy!coreclr_t::execute_assembly+0x2a 20 00007ffb`ab3e2e5c : 00000181`d1f7cbe8 00000046`0137f1e9 00007ffb`ab41ca10 00000181`d1f7cbe8 : hostpolicy!run_app_for_context+0x596 21 00007ffb`ab3e379a : 00000000`00000000 00000181`d1f7cbe0 00000181`d1f7cbe0 00000000`00000000 : hostpolicy!run_app+0x3c 22 00007ffb`cea4b5c9 : 00000181`d1f95088 00000181`d1f94f70 00000000`00000000 00000046`0137f2e9 : hostpolicy!corehost_main+0x15a 23 00007ffb`cea4e066 : 00000181`d1f93f80 00000046`0137f670 00000000`00000000 00000000`00000000 : hostfxr!execute_app+0x2e9 24 00007ffb`cea502ec : 00007ffb`cea825e8 00000181`d1f927d0 00000046`0137f5b0 00000046`0137f560 : hostfxr!`anonymous namespace'::read_config_and_execute+0xa6 25 00007ffb`cea4e644 : 00000046`0137f670 00000046`0137f690 00000046`0137f5e1 00000181`d1f92bf0 : hostfxr!fx_muxer_t::handle_exec_host_command+0x16c 26 00007ffb`cea485a0 : 00000046`0137f690 00000181`d1f920d0 00000000`00000001 00000181`d1f70000 : hostfxr!fx_muxer_t::execute+0x494 27 *** WARNING: Unable to verify checksum for apphost.exe 28 00007ff6`b6b0f998 : 00007ffb`fa85f4e8 00007ffb`cea49b10 00000046`0137f830 00000181`d1f91dc0 : hostfxr!hostfxr_main_startupinfo+0xa0 29 00007ff6`b6b0fda6 : 00007ff6`b6b1b6b0 00000000`00000007 00000181`d1f7cbe0 00000000`0000005e : apphost!exe_start+0x878 30 00007ff6`b6b112e8 : 00000000`00000000 00000000`00000000 00000181`d1f7cbe0 00000000`00000000 : apphost!wmain+0x146 31 (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : apphost!invoke_main+0x22 32 00007ffb`fb1b7344 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : apphost!__scrt_common_main_seh+0x10c 33 00007ffb`fcf626b1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14 34 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21 35 0:000>
KERNELBASE!RaiseException+0x69 方法的第一个参数 00000181`d680a050 就是异常的对象。然后我们使用【!PrintException 00000181`d680a050】命令打印异常的详情。
1 0:000> !PrintException 00000181`d680a050 2 WARNING: SOS needs to be upgraded for this version of the runtime. Some commands may not work correctly. 3 For more information see https://go.microsoft.com/fwlink/?linkid=2135652 4 5 Exception object: 00000181d680a050 6 Exception type: System.IO.FileNotFoundException 7 Message: 8 InnerException: <none> 9 StackTrace (generated): 10 SP IP Function 11 000000460137E540 00007FFB1E355F72 System_Private_CoreLib!System.Runtime.Loader.AssemblyLoadContext.LoadFromAssemblyPath(System.String)+0xf2 12 000000460137E660 00007FFB1E37D55A System_Private_CoreLib!System.Reflection.Assembly.LoadFrom(System.String)+0x17a 13 000000460137E6E0 00007FFAF7C81991 ExampleCore_4_1_1!ExampleCore_4_1_1.Program.Main(System.String[])+0x61 14 15 StackTraceString: <none> 16 HResult: 80070002
HResult: 80070002 我们得到了这异常 HResult 值,可以使用【!error 80070002】命令输出该值的文本表示,我感觉用处不大。
1 0:000> !error 80070002 2 Error code: (HRESULT) 0x80070002 (2147942402) - <Unable to get error code text>
2)、Windbg Preview 调试
编译项目,打开【Windbg Preview】调试器,依次点击【文件】----【Launch executable】,加载我们的项目文件 ExampleCore_4_1_1.exe,直接进入调试器。
进入到调试器,【g】直接运行调试器,我们的控制台程序输出【Press any key to load into load from context】,在控制台程序中回车让程序继续运行,直到抛出异常。我们回到调试器中,调试器已经中断执行了。
1 0:000> g 2 ModLoad: 00007ffb`fb9e0000 00007ffb`fba12000 C:\Windows\System32\IMM32.DLL 3 ModLoad: 00007ffb`1a100000 00007ffb`1a159000 C:\Program Files\dotnet\host\fxr\8.0.7\hostfxr.dll 4 ModLoad: 00007ffb`1a090000 00007ffb`1a0f4000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\hostpolicy.dll 5 ModLoad: 00007ffb`19ba0000 00007ffb`1a085000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\coreclr.dll 6 ModLoad: 00007ffb`fb330000 00007ffb`fb45b000 C:\Windows\System32\ole32.dll 7 ModLoad: 00007ffb`fad80000 00007ffb`fb0d3000 C:\Windows\System32\combase.dll 8 ModLoad: 00007ffb`fb260000 00007ffb`fb32d000 C:\Windows\System32\OLEAUT32.dll 9 ModLoad: 00007ffb`fa260000 00007ffb`fa2e2000 C:\Windows\System32\bcryptPrimitives.dll 10 (3590.bec): Unknown exception - code 04242420 (first chance) 11 ModLoad: 00007ffb`0fd60000 00007ffb`109ed000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Private.CoreLib.dll 12 ModLoad: 00007ffb`193d0000 00007ffb`19589000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\clrjit.dll 13 ModLoad: 00007ffb`fa2f0000 00007ffb`fa302000 C:\Windows\System32\kernel.appcore.dll 14 ModLoad: 000001d3`dfc30000 000001d3`dfc38000 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_1\bin\Debug\net8.0\ExampleCore_4_1_1.dll 15 ModLoad: 000001d3`dfc40000 000001d3`dfc4e000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Runtime.dll 16 ModLoad: 00007ffb`2cd30000 00007ffb`2cd58000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Console.dll 17 ModLoad: 00007ffb`4fb80000 00007ffb`4fb92000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Threading.dll 18 ModLoad: 000001d3`e14f0000 000001d3`e14f8000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Text.Encoding.Extensions.dll 19 ModLoad: 00007ffb`4fa70000 00007ffb`4fa85000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Runtime.InteropServices.dll 20 (3590.bec): C++ EH exception - code e06d7363 (first chance) 21 (3590.bec): CLR exception - code e0434352 (first chance) 22 (3590.bec): CLR exception - code e0434352 (!!! second chance !!!) 23 KERNELBASE!RaiseException+0x69: 24 00007ffb`faa2cf19 0f1f440000 nop dword ptr [rax+rax]
可以执行【sxe e0434352】民工,也可以不执行,关系不是很大,如果执行了该命令,一旦抛出 CLR 异常,调试器就会立刻中断执行,后面如果还有 CLR 异常,也不会抛出了。该命令的执行时机是刚进入调试器就执行。
我们使用【kb】命令转储出当前线程(非托管和托管)的栈回溯,并显示栈的参数。
1 :000> kb 2 # RetAddr : Args to Child : Call Site 3 00 00007ffb`19c5b423 : 000001d3`e400a050 0000001a`e5b7e3c0 0000001a`e5b7e9f0 000001d3`e4009d68 : KERNELBASE!RaiseException+0x69(抛出异常的方法) 4 01 00007ffb`19ba9cfc : 000001d3`dfc5c100 0000001a`e5b7e6e8 000001d3`dfc95430 000001d3`dfd48d00 : coreclr!RaiseTheExceptionInternalOnly+0x26b [D:\a\_work\1\s\src\coreclr\vm\excep.cpp @ 2795] 5 02 00007ffb`19d2d231 : 0000001a`e5b7e6e8 000001d3`dfc95430 0000001a`e5b7e730 00007ffb`1a0268c0 : coreclr!UnwindAndContinueRethrowHelperAfterCatch+0x38 [D:\a\_work\1\s\src\coreclr\vm\excep.cpp @ 7704] 6 03 00007ffb`100d5f73 : 00000214`761e051c 000001d3`e4009de0 00000000`00000000 0000001a`e5b7e6e8 : coreclr!AssemblyNative_LoadFromPath+0x1825a1 [D:\a\_work\1\s\src\coreclr\vm\assemblynative.cpp @ 221] 7 04 00007ffb`100fd55b : 000001d3`e4009de0 00000214`761e0510 000001d3`e4009d30 00000000`00000002 : System_Private_CoreLib!System.Runtime.Loader.AssemblyLoadContext.LoadFromAssemblyPath+0xf3 [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs @ 347] 8 05 00007ffa`ba181992 : 00000214`761e0510 00000000`00000001 000001d3`dfc9c5a0 000001d3`e4009c38 : System_Private_CoreLib!System.Reflection.Assembly.LoadFrom+0x17b [/_/src/libraries/System.Private.CoreLib/src/System/Reflection/Assembly.cs @ 374] 9 06 00007ffb`19cfbd43 : 000001d3`e4008ea0 0000001a`e5b7ee78 0000001a`e5b7ee78 0000001a`e5b7ea69 : ExampleCore_4_1_1!ExampleCore_4_1_1.Program.Main+0x62 [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_1\Program.cs @ 34] 10 07 00007ffb`19c30ac9 : 00000000`00000000 00000000`00000130 0000001a`e5b7ea78 00007ffb`19bb022e : coreclr!CallDescrWorkerInternal+0x83 [D:\a\_work\1\s\src\coreclr\vm\amd64\CallDescrWorkerAMD64.asm @ 100] 11 08 (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!CallDescrWorkerWithHandler+0x56 [D:\a\_work\1\s\src\coreclr\vm\callhelpers.cpp @ 67] 12 09 00007ffb`19c2d6e0 : 0000001a`e5b7eaf8 00000000`00000000 00000000`00000048 00007ffb`19c9d176 : coreclr!MethodDescCallSite::CallTargetWorker+0x2a1 [D:\a\_work\1\s\src\coreclr\vm\callhelpers.cpp @ 570] 13 0a (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!MethodDescCallSite::Call+0xb [D:\a\_work\1\s\src\coreclr\vm\callhelpers.h @ 458] 14 0b 00007ffb`19c52ff6 : 000001d3`e4008ea0 000001d3`e4008ea0 00000000`00000000 0000001a`e5b7ee78 : coreclr!RunMainInternal+0x11c [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1304] 15 0c 00007ffb`19c5332b : 000001d3`dfc9c5a0 000001d3`00000000 000001d3`dfc9c5a0 00000000`00000000 : coreclr!RunMain+0xd2 [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1375] 16 0d 00007ffb`19ba9141 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000130 : coreclr!Assembly::ExecuteMainMethod+0x1bf [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1504] 17 0e 00007ffb`19cbe9e8 : 00000000`00000001 0000001a`e5b7f001 0000001a`e5b7f0a0 00007ffb`1a0923ea : coreclr!CorHost2::ExecuteAssembly+0x281 [D:\a\_work\1\s\src\coreclr\vm\corhost.cpp @ 349] 18 0f 00007ffb`1a0b2b76 : 000001d3`dfc6c4a0 000001d3`dfc6c1a0 00000000`00000000 000001d3`dfc6c1a0 : coreclr!coreclr_execute_assembly+0xd8 [D:\a\_work\1\s\src\coreclr\dlls\mscoree\exports.cpp @ 504] 19 10 (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : hostpolicy!coreclr_t::execute_assembly+0x2a [D:\a\_work\1\s\src\native\corehost\hostpolicy\coreclr.cpp @ 109] 20 11 00007ffb`1a0b2e5c : 000001d3`dfc58868 0000001a`e5b7f2c9 00007ffb`1a0eca10 000001d3`dfc58868 : hostpolicy!run_app_for_context+0x596 [D:\a\_work\1\s\src\native\corehost\hostpolicy\hostpolicy.cpp @ 256] 21 12 00007ffb`1a0b379a : 00000000`00000000 000001d3`dfc58860 000001d3`dfc58860 00000000`00000000 : hostpolicy!run_app+0x3c [D:\a\_work\1\s\src\native\corehost\hostpolicy\hostpolicy.cpp @ 285] 22 13 00007ffb`1a10b5c9 : 000001d3`dfc69998 000001d3`dfc69880 00000000`00000000 0000001a`e5b7f3c9 : hostpolicy!corehost_main+0x15a [D:\a\_work\1\s\src\native\corehost\hostpolicy\hostpolicy.cpp @ 426] 23 14 00007ffb`1a10e066 : 000001d3`dfc69150 0000001a`e5b7f750 00000000`00000000 00000000`00000000 : hostfxr!execute_app+0x2e9 [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 145] 24 15 00007ffb`1a1102ec : 00007ffb`1a1425e8 000001d3`dfc67b00 0000001a`e5b7f690 0000001a`e5b7f640 : hostfxr!`anonymous namespace'::read_config_and_execute+0xa6 [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 532] 25 16 00007ffb`1a10e644 : 0000001a`e5b7f750 0000001a`e5b7f770 0000001a`e5b7f6c1 000001d3`dfc67f20 : hostfxr!fx_muxer_t::handle_exec_host_command+0x16c [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 1007] 26 17 00007ffb`1a1085a0 : 0000001a`e5b7f770 000001d3`dfc67400 00000000`00000001 000001d3`dfc50000 : hostfxr!fx_muxer_t::execute+0x494 [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 578] 27 18 00007ff6`8a04f998 : 00007ffb`fa85f4e8 00007ffb`1a109b10 0000001a`e5b7f910 000001d3`dfc670f0 : hostfxr!hostfxr_main_startupinfo+0xa0 [D:\a\_work\1\s\src\native\corehost\fxr\hostfxr.cpp @ 62] 28 19 00007ff6`8a04fda6 : 00007ff6`8a05b6b0 00000000`00000007 000001d3`dfc58860 00000000`0000005e : apphost!exe_start+0x878 [D:\a\_work\1\s\src\native\corehost\corehost.cpp @ 240] 29 1a 00007ff6`8a0512e8 : 00000000`00000000 00000000`00000000 000001d3`dfc58860 00000000`00000000 : apphost!wmain+0x146 [D:\a\_work\1\s\src\native\corehost\corehost.cpp @ 311] 30 1b (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : apphost!invoke_main+0x22 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 90] 31 1c 00007ffb`fb1b7344 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : apphost!__scrt_common_main_seh+0x10c [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288] 32 1d 00007ffb`fcf626b1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14 33 1e 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
KERNELBASE!RaiseException 方法的第一个参数 000001d3`e400a050 就是异常对象的地址,我们可以使用【!PrintException 000001d3`e400a050】转储异常的详情。
1 0:000> !PrintException 000001d3`e400a050 2 Exception object: 000001d3e400a050 3 Exception type: System.IO.FileNotFoundException 4 Message: 5 InnerException: <none> 6 StackTrace (generated): 7 SP IP Function 8 0000001AE5B7E620 00007FFB100D5F72 System_Private_CoreLib!System.Runtime.Loader.AssemblyLoadContext.LoadFromAssemblyPath(System.String)+0xf2 9 0000001AE5B7E740 00007FFB100FD55A System_Private_CoreLib!System.Reflection.Assembly.LoadFrom(System.String)+0x17a 10 0000001AE5B7E7C0 00007FFABA181991 ExampleCore_4_1_1!ExampleCore_4_1_1.Program.Main(System.String[])+0x61 11 12 StackTraceString: <none> 13 HResult: 80070002
HResult: 80070002 我们有这个值,可以针对这个值使用【!error 80070002】命令,但是我感觉用处不大。
1 0:000> !error 80070002 2 Error code: (HRESULT) 0x80070002 (2147942402) - <Unable to get error code text>
3)、探测程序集的加载过程(针对 Net Framework 平台)
A、基础知识
程序集绑定日志查看器显示程序集绑定的详细信息。 这些信息有助于你诊断 .NET Framework 无法在运行时找到程序集的原因,不适合 Net 5.0 或者以上的跨平台版本了,大家作为扩展学习吧。CLR 加载器能否告诉我们在查找程序集时使用的各个探测路径?答案是肯定的,在程序集绑定日志(assemblybinding logging)中包含了这些信息,这是CLR的一个功能,即在绑定阶段进行跟踪。绑定日志功能在默认情况下是关闭着的,因此需要首先启用它。启用日志绑定的最简单方式是运行【fuslogvw.exe】命令,我们打开【Visual Studio 2022 Developer Command Prompt v17.9.4】命令行工具,直接输入【fuslogvw】,就可以打开,该文件的路径在:C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64。
效果如图:
打开【程序集绑定日志查看器】,效果如图:
主界面分为两个部分,第一部分包含了实际的日志项目,共三列:
应用程序【Application】:该列给出了日志项是来之哪个应用程序。
说明【Description】:该列给出日志项的描述信息。
日期/时间【Date/Time】:该列给出了日志项的时间戳。
在主界面的右侧有一组按钮,控制日志项和如何执行日志。【设置(Setting)】按钮控制着如何执行日志记录以及何时执行。
如图:
在默认情况下,日志是禁用的。
已禁用日志:日志功能不启用。
记录异常文本【Log in Exception Text】:程序集绑定操作会被记录在异常中。
记录失败绑定到磁盘【Log Bind failures to Disk】:只有发生故障的时候才会记录到磁盘上。
记录所有绑定到磁盘【Log All Binds to Disk】:所有绑定操作都会被记录到磁盘上。
启用自定义日志路径【Enable Custom Log Path】:改变日志文件的保存路径。如果选择了改变日志路径,必须在主界面上【自定义日志路径(Custom Log Location)】中输入日志的新路径。
其实,这些设置都是通过注册表实现的,具体事项如下:
自动启用绑定日志:
在 fuslogvw.exe 中的这些设置是通过注册表中的以下注册键来实现的:
计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Fusion,效果如图:
在这个注册表路径下存在以下合法的注册键值:
LogPath[SZ]。日志路径。
ForceLog[DWORD=1]。记录所有的绑定行为。
LogFailures[DWORD=1]。只记录失败的绑定行为。
LogResourceBinds[DWORD=1]。只记录卫星程序集中的故障。
通过创建正确的注册键值,我们同样可以控制绑定日志功能。
至此,我们完成了对一个简单程序集加载故障的分析。我们首先从应用程序发生故障时所输出的信息中得到了一些基本的诊断信息,接下来在调试器下运行这个有问题的程序,并观察抛出的异常,最后打开程序集绑定日志并找出CLR加载器将在哪些位置搜索被请求的程序集(在这种情况下,只有应用程序库这个路径)。
B、眼见为实
调试源码:Example_4_1_1(Net Framework 版本)
调试任务:观察 CLR 加载器加载程序集的时候如何探测路径。
当我们设置好【Log Bind failures to Disk】选项时,直接运行 ExampleCore_4_1_1.exe 我们的程序,直到抛出异常。
效果如图:
回到【fuslogvw.exe】工具并点击【刷新(Refresh)】按钮,如果没有反应,我们可以关闭它,再重新打开就可以在主界面中看到一条日志项。如图:
到目前为止,我们得到的故障信息非常少。要得到进一步的故障信息,双击日志项,会弹出默认的浏览器并给出详细的错误信息。输出如下:
1 *** 程序集联编程序日志项 (2024/8/8 @ 14:13:31) *** 2 3 操作失败。 4 绑定结果: hr = 0x80070002。系统找不到指定的文件。 5 6 程序集管理器加载位置: C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll 7 在可执行文件下运行 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_4_1_1\bin\Debug\Example_4_1_1.exe 8 --- 详细的错误日志如下。 9 10 === 预绑定状态信息 === 11 日志: Where-ref 绑定。位置 = E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_4_1_1\bin\Debug\ExampleCore_4_1_1.old 12 日志: Appbase = file:///E:/Visual Studio 2022/Source/Projects/AdvancedDebug.NetFramework.Test/Example_4_1_1/bin/Debug/ 13 日志: 初始 PrivatePath = NULL 14 日志: 动态基 = NULL 15 日志: 缓存基 = NULL 16 日志: AppName = Example_4_1_1.exe 17 调用程序集: (Unknown)。 18 === 19 日志: 此绑定从 LoadFrom 加载上下文开始。 20 警告: 将不在 LoadFrom 上下文中探测本机映像。仅在默认加载上下文中探测本机映像,例如,使用 Assembly.Load()。 21 日志: 正在使用应用程序配置文件: E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_4_1_1\bin\Debug\Example_4_1_1.exe.Config 22 日志: 使用主机配置文件: 23 日志: 使用 C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config 的计算机配置文件。 24 日志: 尝试下载新的 URL file:///E:/Visual Studio 2022/Source/Projects/AdvancedDebug.NetFramework.Test/Example_4_1_1/bin/Debug/ExampleCore_4_1_1.old。 25 日志: 已尝试所有探测 URLs 但全部失败。
输出信息包含三个部分,第一部分包含了日志的操作状态(操作失败)、HRESULT(hr = 0x80070002)、失败原因(系统找不到指定的文件。)、CLR 加载器的路径以及可执行程序的路径。
第二部分信息主要是【预绑定状态信息】,在这部分信息中包括加载过程可能被探测的其他路径,例如:应用程序库(Appbase)、私有路径(PrivatePath)、动态库(动态基)以及缓存库(缓存基)等。这些不同路径,表示在加载程序集的时候需要探测的路径。
第三部分包含的就是实际的探测记录。从输出信息中我们可以知道它处于指定加载上下文中(因为使用了 LoadFrom)。这些信息还指出,由于没有找到应用程序的配置文件,转而使用了全局的配置文件(使用 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config 的计算机配置文件)。找遍了所有路径,都没有找到,所以最后失败(已尝试所有探测 URLs 但全部失败。)
4.3、加载上下文故障
A、基础知识
当这个应用程序运行时,会显示一个菜单,它指定是在默认的应用程序域中运行还是在专门的应用程序域中运行。它还说明了如何在不同的应用程序域中使用 Entity 和 EntityUtil 等类。如果在默认的应用程序域中运行,那么这个类的所有实例都会在默认的应用程序域中创建,而如果在一个专门的应用程序域中运行,那么EntityUtil 实例会在一个新的应用程序域中被创建,而 Entity 实例(被传递给 EntityUtil)会在默认的应用程序域中创建。由于在不同的应用程序域之间存在一个隔离层,因此如果要跨越应用程序域来传递对象,那么就需要使用列集(marshal)机制。在这里,我们将执行简单的按值列集操作(marshaling byvalue),并在目标应用程序域中创建对象的一个副本。
当一个对象通过列集来跨越应用程序域的边界时,这个对象会被串行化(serialization),这意味着目标应用程序域必须访问包含这个类型定义的程序集。如果没有这个程序集,那么反串行化操作deserialization)就会失败。还需要注意的是,应用程序域可以控制程序集探测路径的某些特
deserialization定部分。
还讲了如何使用 MDA(托管调试助手),这个东西是针对 NET FRAMEWORK 版本的。 它不适用于 .NET 的较新版本实现,包括 .NET 6 及更高版本。
有没有其他有用的 MDA,答案是肯定的。当使用指定加载上下文时,loadFromContext这个MDA可以发出警告。通常,人们会在无意中使用指定加载上下文,这将导致一些严重的问题。当某个程序集被加载到指定加载上下文时,这个MDA会发送一些警告信息。
如果想了解 MDA 的详情,可以点击:https://learn.microsoft.com/zh-cn/dotnet/framework/debug-trace-profile/diagnosing-errors-with-managed-debugging-assistants
以上内容就是要测试的内容,但是平台发生了变化,以前是 Net Framework,现在我使用的 Net 8.0。而且在 .NET 6.0 或者以上的环境中已经不支持 AppDomian.CreateDomain 方法了。AppDomain 需要运行时支持,并且耗费的资源成本较高。 不支持创建其他应用域,也尚未计划在将来添加此功能。
如果大家想了解更详细的内容,可以去微软官网:NET Framework 技术在 .NET 上不可用
B、眼见为实
调试源码:Example_4_1_2._2(Net Framework 4.8)
调试任务:加载上下文的故障
我们打开 Example_4_1_2.2.exe 可执行文件所在的目录,双击 exe 文件来运行程序,看看执行效果。
这是控制台程序启动的初始状态。我们输入:1,回车。
在默认的应用程序域中是能正常运行的。我们继续输入:2,就是使用特定的应用程序域来运行。
我们可以看到,只要在默认的应用程序域中运行这个程序,它就会成功执行。然而,如果在一个专门的应用程序域中运行这个程序(选项2),那么将抛出一个 FileNotFoundException 异常。为什么在默认应用程序域中可以运行,而在专门应用程序域中则运行失败呢?和前面一样,我们先来分析栈回溯。这个异常似乎来自RunInDedicated 函数,它调用 CreateInstance 函数来创建一个 EntityUtil 类的实例。接下来,我们在调试器中运行程序,并观察异常(我使用了两种调试器,大家可以任选一种)。
1)、NTSD 调试
编译项目,打开【Visual Studio 2022 Developer Command Prompt v17.10.4】命令行工具,输入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_4_1_2.2\bin\Debug\Example_4_1_2.2.exe】,回车进入到调试器。
使用【g】命令直接运行调试器,直到调试器输出如图:
然后,输入2,继续。
调试器捕获异常,并中断,这里并没有提供什么有价值的异常信息。
在 NET Framework 环境下(Net 5.0 及其以上就不可以了),在处理加载器问题时,托管调试助手(Managed Debugging Assistant,MDA)也是一个很好用的工具。对于 CLR 加载器来说,有一个叫做 bindingFailure 的 MDA 可用于分析 CLR 加载器问题。要启用这个 MDA,可以将以下XML保存到Example_4_1_2.2.exe.mda. config 文件中,内容如下:
<?xml version="1.0" encoding="utf-8" ?> <mdaConfig> <assistants> <bindingFailure /> </assistants> </mdaConfig>
这个 XML 只是为程序 Example_4_1_2.2.exe 启用 bindingFailure。如果在调试器下重新运行 Example_4_1_2.2.exe,并重现这个问题(通过选择选项2),那么可以看到以下输出信息:
1 0:000> g 2 ModLoad: 00007ffb`e0a00000 00007ffb`e0ab0000 C:\Windows\System32\ADVAPI32.dll 3 ModLoad: 00007ffb`e0c20000 00007ffb`e0cbe000 C:\Windows\System32\msvcrt.dll 4 ModLoad: 00007ffb`ded20000 00007ffb`dedc0000 C:\Windows\System32\sechost.dll 5 ModLoad: 00007ffb`e0590000 00007ffb`e06b5000 C:\Windows\System32\RPCRT4.dll 6 ModLoad: 00007ffb`deb90000 00007ffb`debb7000 C:\Windows\System32\bcrypt.dll 7 ModLoad: 00007ffb`c5bd0000 00007ffb`c5c7a000 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscoreei.dll 8 ModLoad: 00007ffb`e0000000 00007ffb`e0055000 C:\Windows\System32\SHLWAPI.dll 9 ModLoad: 00007ffb`de450000 00007ffb`de462000 C:\Windows\System32\kernel.appcore.dll 10 ModLoad: 00007ffb`ddd30000 00007ffb`ddd3a000 C:\Windows\SYSTEM32\VERSION.dll 11 ModLoad: 00007ffb`c3d00000 00007ffb`c4834000 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll 12 ModLoad: 00007ffb`dfce0000 00007ffb`dfe7e000 C:\Windows\System32\USER32.dll 13 ModLoad: 00007ffb`de9a0000 00007ffb`de9c2000 C:\Windows\System32\win32u.dll 14 ModLoad: 00007ffb`c4a30000 00007ffb`c4a46000 C:\Windows\SYSTEM32\VCRUNTIME140_CLR0400.dll 15 ModLoad: 00007ffb`c37a0000 00007ffb`c385d000 C:\Windows\SYSTEM32\ucrtbase_clr0400.dll 16 ModLoad: 00007ffb`e0bd0000 00007ffb`e0bfb000 C:\Windows\System32\GDI32.dll 17 ModLoad: 00007ffb`de1f0000 00007ffb`de307000 C:\Windows\System32\gdi32full.dll 18 ModLoad: 00007ffb`de310000 00007ffb`de3ad000 C:\Windows\System32\msvcp_win.dll 19 ModLoad: 00007ffb`de850000 00007ffb`de950000 C:\Windows\System32\ucrtbase.dll 20 ModLoad: 00007ffb`e0ab0000 00007ffb`e0ae2000 C:\Windows\System32\IMM32.DLL 21 ModLoad: 00007ffb`e0060000 00007ffb`e03b3000 C:\Windows\System32\combase.dll 22 (148.d28): Unknown exception - code 04242420 (first chance) 23 ModLoad: 00007ffb`c0d10000 00007ffb`c236e000 C:\Windows\assembly\NativeImages_v4.0.30319_64\mscorlib\4bc5e5252873c08797895d5b6fe6ddfd\mscorlib.ni.dll 24 ModLoad: 00007ffb`dfbb0000 00007ffb`dfcdb000 C:\Windows\System32\ole32.dll 25 ModLoad: 00007ffb`e0060000 00007ffb`e03b3000 C:\Windows\System32\combase.dll 26 ModLoad: 00007ffb`de3c0000 00007ffb`de442000 C:\Windows\System32\bcryptPrimitives.dll 27 ModLoad: 00007ffb`c04c0000 00007ffb`c060f000 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clrjit.dll 28 1、Run in default app domain 29 2、Run in dedicated app domain 30 Q、To quit. 31 > 1 32 10 33 1、Run in default app domain 34 2、Run in dedicated app domain 35 Q、To quit. 36 > 2 37 ModLoad: 00007ffb`dda80000 00007ffb`dda98000 C:\Windows\SYSTEM32\CRYPTSP.dll 38 ModLoad: 00007ffb`dd1c0000 00007ffb`dd1f4000 C:\Windows\system32\rsaenh.dll 39 ModLoad: 00007ffb`ddbb0000 00007ffb`ddbbc000 C:\Windows\SYSTEM32\CRYPTBASE.dll 40 (148.d28): C++ EH exception - code e06d7363 (first chance) 41 (148.d28): C++ EH exception - code e06d7363 (first chance) 42 (148.d28): C++ EH exception - code e06d7363 (first chance) 43 <mda:msg xmlns:mda="http://schemas.microsoft.com/CLR/2004/10/mda"> 44 <!-- 45 显示名为“Example_4_1_2.2”的程序集未能加载到 ID 为 2 的 AppDomain 的“LoadFrom”绑定上下文中。错误的原因为: 46 System.IO.FileNotFoundException: 47 未能加载文件或程序集“Example_4_1_2.2”或它的某一个依赖项。系统找不到指定的文件。 48 --> 49 <mda:bindingFailureMsg break="true"> 50 <assemblyInfo appDomainId="2" displayName="Example_4_1_2.2" codeBase="" hResult="-2147024894" bindingContextId="1"/> 51 </mda:bindingFailureMsg> 52 </mda:msg> 53 (148.d28): Break instruction exception - code 80000003 (first chance) 54 KERNELBASE!wil::details::DebugBreak+0x2: 55 00007ffb`de63b502 cc int 3 56 0:000>
红色是需要我们特殊关注的,ID 为 2 的应用程序域没有加载 Example_4_1_2.2 ,我们可以使用【!DumpDomain】命令查看应用程序域的详情。
如果我们开启了【程序集绑定日志】功能,当我们程序崩溃的时候就会记录日志,我开启了。我们就可以在【程序集绑定日志查看器】中看到有关我们程序的日志数据,如图:
我们双击打开浏览器,查看详情,内容如下:
1 *** 程序集联编程序日志项 (2024/8/13 @ 13:57:56) *** 2 3 操作失败。 4 绑定结果: hr = 0x80070002。系统找不到指定的文件。 5 6 程序集管理器加载位置: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll 7 在可执行文件下运行 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_4_1_2.2\bin\Debug\Example_4_1_2.2.exe 8 --- 详细的错误日志如下。 9 10 === 预绑定状态信息 === 11 日志: DisplayName = Example_4_1_2.2 12 (Partial) 13 警告: 为程序集提供了部分绑定信息: 14 警告: 程序集名称: Example_4_1_2.2 | 域 ID: 2 15 警告: 当仅提供程序集显示名称的一部分时,将发生部分绑定。 16 警告: 这可能导致联编程序加载错误的程序集。 17 警告: 建议为程序集提供完全指定的文字标识, 18 警告: 并由简单名称、版本、区域性和公钥标记组成。 19 警告: 有关此问题的详细信息和常见解决方案,请参见白皮书 http://go.microsoft.com/fwlink/?LinkId=109270。 20 日志: Appbase = file:///C:/Windows/System32 21 日志: 初始 PrivatePath = NULL 22 日志: 动态基 = NULL 23 日志: 缓存基 = NULL 24 日志: AppName = Example_4_1_2.2.exe 25 调用程序集: (Unknown)。 26 === 27 日志: 此绑定从 default 加载上下文开始。 28 日志: 尝试从 file:///E:/Visual Studio 2022/Source/Projects/AdvancedDebug.NetFramework.Test/Example_4_1_2.2/bin/Debug/Example_4_1_2.2.exe.Config 下载应用程序配置文件。 29 日志: 找到应用程序配置文件(E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_4_1_2.2\bin\Debug\Example_4_1_2.2.exe.Config)。 30 日志: 正在使用应用程序配置文件: E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_4_1_2.2\bin\Debug\Example_4_1_2.2.exe.Config 31 日志: 使用主机配置文件: 32 日志: 使用 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config 的计算机配置文件。 33 日志: 此时没有为引用应用策略(私有、自定义、分部或基于位置的程序集绑定)。 34 日志: 尝试下载新的 URL file:///C:/Windows/System32/Example_4_1_2.2.DLL。 35 日志: 尝试下载新的 URL file:///C:/Windows/System32/Example_4_1_2.2/Example_4_1_2.2.DLL。 36 日志: 尝试下载新的 URL file:///C:/Windows/System32/Example_4_1_2.2.EXE。 37 日志: 尝试下载新的 URL file:///C:/Windows/System32/Example_4_1_2.2/Example_4_1_2.2.EXE。 38 日志: 已尝试所有探测 URLs 但全部失败。
从上面浏览器输出的 CLR 加载器日志中,可以看到 CLR 加载器尝试从路径 C:\Windows\System32 中加载 Example_4_1_2.2.exe 程序集。为什么会选择这个路径?答案在于,当一个对象通过列集来跨越应用程序域的边界时,这个对象会被串行化(serialization),这意味着目标应用程序域必须访问包含这个类型定义的程序集。如果没有这个程序集,那么反串行化操作(deserialization)就会失败。
还需要注意的是,应用程序域可以控制程序集探测路径的某些特定部分。具体来说,浏览器输出的 AppBase 被设置为C:\Windows\System32,红色标注的。如果进一步观察源代码中的 RunInDedicated方法,那么会看到以下代码行:
1 AppDomainSetup domaininfo =new AppDomainSetup(); 2 domaininfo.ApplicationBase="C:\Windows\System32"; 3 return AppDomain.CreateDomain("MyDomain",null, domaininfo);
【NTSD】调试的内容有些欠缺,完整的可以查看【Windbg Preview】调试。
2)、Windbg Preview 调试
编译项目,打开【Windbg Preview】调试器,依稀点击【文件】----【Launch executable】,加载我们的可执行文件。
我们在调试器中【g】直接运行程序,控制台程序提示我们选择操作,如图:
我在控制台中直接输入 2,控制台程序抛出异常,再次回到调试器,调试器也中断执行。
调试器输出如下:
1 0:000> g 2 ModLoad: 00007fff`28090000 00007fff`28140000 C:\Windows\System32\ADVAPI32.dll 3 ModLoad: 00007fff`269a0000 00007fff`26a3e000 C:\Windows\System32\msvcrt.dll 4 ModLoad: 00007fff`27350000 00007fff`273f0000 C:\Windows\System32\sechost.dll 5 ModLoad: 00007fff`27780000 00007fff`278a5000 C:\Windows\System32\RPCRT4.dll 6 ModLoad: 00007fff`263f0000 00007fff`26417000 C:\Windows\System32\bcrypt.dll 7 ModLoad: 00007fff`0cb50000 00007fff`0cbfa000 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscoreei.dll 8 ModLoad: 00007fff`26e80000 00007fff`26ed5000 C:\Windows\System32\SHLWAPI.dll 9 ModLoad: 00007fff`26090000 00007fff`260a2000 C:\Windows\System32\kernel.appcore.dll 10 ModLoad: 00007fff`258b0000 00007fff`258ba000 C:\Windows\SYSTEM32\VERSION.dll 11 ModLoad: 00007fff`0b9a0000 00007fff`0c4d4000 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll 12 ModLoad: 00007fff`26be0000 00007fff`26d7e000 C:\Windows\System32\USER32.dll 13 ModLoad: 00007fff`25d60000 00007fff`25d82000 C:\Windows\System32\win32u.dll 14 ModLoad: 00007fff`0b660000 00007fff`0b676000 C:\Windows\SYSTEM32\VCRUNTIME140_CLR0400.dll 15 ModLoad: 00007fff`27750000 00007fff`2777b000 C:\Windows\System32\GDI32.dll 16 ModLoad: 00007fff`0b5a0000 00007fff`0b65d000 C:\Windows\SYSTEM32\ucrtbase_clr0400.dll 17 ModLoad: 00007fff`261b0000 00007fff`262c7000 C:\Windows\System32\gdi32full.dll 18 ModLoad: 00007fff`262d0000 00007fff`2636d000 C:\Windows\System32\msvcp_win.dll 19 ModLoad: 00007fff`260b0000 00007fff`261b0000 C:\Windows\System32\ucrtbase.dll 20 ModLoad: 00007fff`26a40000 00007fff`26a72000 C:\Windows\System32\IMM32.DLL 21 ModLoad: 00007fff`273f0000 00007fff`27743000 C:\Windows\System32\combase.dll 22 (3c84.38fc): Unknown exception - code 04242420 (first chance) 23 ModLoad: 00007fff`08a60000 00007fff`0a0be000 C:\Windows\assembly\NativeImages_v4.0.30319_64\mscorlib\4bc5e5252873c08797895d5b6fe6ddfd\mscorlib.ni.dll 24 ModLoad: 00007fff`26ff0000 00007fff`2711b000 C:\Windows\System32\ole32.dll 25 ModLoad: 00007fff`273f0000 00007fff`27743000 C:\Windows\System32\combase.dll 26 ModLoad: 00007fff`267f0000 00007fff`26872000 C:\Windows\System32\bcryptPrimitives.dll 27 ModLoad: 00007fff`078e0000 00007fff`07a2f000 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clrjit.dll 28 ModLoad: 00007fff`25660000 00007fff`25678000 C:\Windows\SYSTEM32\CRYPTSP.dll 29 ModLoad: 00007fff`24d80000 00007fff`24db4000 C:\Windows\system32\rsaenh.dll 30 ModLoad: 00007fff`25680000 00007fff`2568c000 C:\Windows\SYSTEM32\CRYPTBASE.dll 31 (3c84.38fc): C++ EH exception - code e06d7363 (first chance) 32 (3c84.38fc): CLR exception - code e0434352 (first chance) 33 ModLoad: 00007ffe`9efd0000 00007ffe`9f142000 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\diasymreader.dll 34 ModLoad: 00007fff`06c60000 00007fff`078d4000 C:\Windows\assembly\NativeImages_v4.0.30319_64\System\266fb3707f773f9db1827aeee9e5e08e\System.ni.dll 35 ModLoad: 00007fff`05b80000 00007fff`065f5000 C:\Windows\assembly\NativeImages_v4.0.30319_64\System.Core\075d0a5a0944686684b04830fad31380\System.Core.ni.dll 36 ModLoad: 000002a0`7e5c0000 000002a0`7e6ba000 image000002a0`7e5c0000 37 ModLoad: 000002a0`7e6c0000 000002a0`7e7ba000 image000002a0`7e6c0000 38 (3c84.38fc): CLR exception - code e0434352 (first chance) 39 ModLoad: 000002a0`7e7c0000 000002a0`7e93c000 System.Core.dll 40 ModLoad: 000002a0`7e940000 000002a0`7eabc000 System.Core.dll 41 (3c84.38fc): CLR exception - code e0434352 (!!! second chance !!!) 42 KERNELBASE!RaiseException+0x69: 43 00007fff`25dbcf19 0f1f440000 nop dword ptr [rax+rax]
我们直接运行【!PrintException】命令,转储异常信息。
1 0:000> !PrintExcetpion 2 Exception object: 000001ff39cb72f8 3 Exception type: System.IO.FileNotFoundException 4 Message: 未能加载文件或程序集“Example_4_1_2.2”或它的某一个依赖项。系统找不到指定的文件。 5 InnerException: <none> 6 StackTrace (generated): 7 SP IP Function 8 000000BE1C4FEE00 0000000000000001 mscorlib_ni!System.AppDomain.CreateInstance(System.String, System.String)+0xffff80043ecbba41 9 000000BE1C4FEE00 00007FFB64800D33 Example_4_1_2_2!Example_4_1_2._2.Program.RunInDedicated()+0x73 10 000000BE1C4FEE80 00007FFB64800A86 Example_4_1_2_2!Example_4_1_2._2.Program.Run()+0x106 11 000000BE1C4FEF10 00007FFB648008F2 Example_4_1_2_2!Example_4_1_2._2.Program.Main(System.String[])+0x62 12 13 StackTraceString: <none> 14 HResult: 80070002
我们看到了异常的具体信息,知道了异常对象的地址 000001ff39cb72f8,我们针对该地址,执行【!DumpObj 000001ff39cb72f8】命令,查看异常对象的信息。
1 0:000> !do 000001ff39cb72f8 2 Name: System.IO.FileNotFoundException 3 MethodTable: 00007ffbc0db6a38 4 EEClass: 00007ffbc0ec7690 5 Size: 184(0xb8) bytes 6 File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 7 Fields: 8 MT Field Offset Type VT Attr Value Name 9 00007ffbc0d359c0 40002a4 8 System.String 0 instance 000001ff39cb4e18 _className 10 00007ffbc0db2a50 40002a5 10 ...ection.MethodBase 0 instance 0000000000000000 _exceptionMethod 11 00007ffbc0d359c0 40002a6 18 System.String 0 instance 000001ff39cb6e08 _exceptionMethodString 12 00007ffbc0d359c0 40002a7 20 System.String 0 instance 000001ff39ca0130 _message 13 00007ffbc0db83d8 40002a8 28 ...tions.IDictionary 0 instance 0000000000000000 _data 14 00007ffbc0d35b70 40002a9 30 System.Exception 0 instance 0000000000000000 _innerException 15 00007ffbc0d359c0 40002aa 38 System.String 0 instance 0000000000000000 _helpURL 16 00007ffbc0d35dd8 40002ab 40 System.Object 0 instance 000001ff39cb9b68 _stackTrace 17 00007ffbc0d35dd8 40002ac 48 System.Object 0 instance 000001ff39cb7460 _watsonBuckets 18 00007ffbc0d359c0 40002ad 50 System.String 0 instance 0000000000000000 _stackTraceString 19 00007ffbc0d359c0 40002ae 58 System.String 0 instance 000001ff39cb92f0 _remoteStackTraceString 20 00007ffbc0d385a0 40002af 88 System.Int32 1 instance 0 _remoteStackIndex 21 00007ffbc0d35dd8 40002b0 60 System.Object 0 instance 0000000000000000 _dynamicMethods 22 00007ffbc0d385a0 40002b1 8c System.Int32 1 instance -2147024894 _HResult 23 00007ffbc0d359c0 40002b2 68 System.String 0 instance 000001ff39cb4de8 _source 24 00007ffbc0db31f8 40002b3 78 System.IntPtr 1 instance 0 _xptrs 25 00007ffbc0d385a0 40002b4 90 System.Int32 1 instance 0 _xcode 26 00007ffbc0d4e720 40002b5 80 System.UIntPtr 1 instance 0 _ipForWatsonBuckets 27 00007ffbc0d25080 40002b6 70 ...ializationManager 0 instance 000001ff39cb92b8 _safeSerializationManager 28 00007ffbc0d35dd8 40002a3 108 System.Object 0 shared static s_EDILock 29 >> Domain:Value 000001ff382c25f0:NotInit 000001ff38378080:NotInit << 30 00007ffbc0d359c0 40008b3 98 System.String 0 instance 0000000000000000 _maybeFullPath 31 00007ffbc0d359c0 400083e a0 System.String 0 instance 000001ff39c9f250 _fileName 32 00007ffbc0d359c0 400083f a8 System.String 0 instance 000001ff39c9f288 _fusionLog
当打开程序集绑定日志功能时,你可能已经注意到在收集的日志项数据上设置了一个属性 FusionLog,它表示通过异常机制来传播故障。以下是当日志功能被打开时的FileNotFoundException 及其 FusionLog 属性,_fusionLog 的地址是 000001ff39c9f288 ,针对该地址继续使用【!DumpObj 000001ff39c9f288】 就可以查看程序集的绑定日志了。
1 0:000> !do 000001ff39c9f288 2 Name: System.String 3 MethodTable: 00007ffbc0d359c0 4 EEClass: 00007ffbc0d12ec0 5 Size: 2744(0xab8) bytes 6 File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 7 String: 程序集管理器加载位置: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll 8 在可执行文件下运行 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_4_1_2.2\bin\Debug\Example_4_1_2.2.exe 9 --- 详细的错误日志如下。 10 11 === 预绑定状态信息 === 12 日志: DisplayName = Example_4_1_2.2 13 (Partial) 14 警告: 为程序集提供了部分绑定信息: 15 警告: 程序集名称: Example_4_1_2.2 | 域 ID: 2 16 警告: 当仅提供程序集显示名称的一部分时,将发生部分绑定。 17 警告: 这可能导致联编程序加载错误的程序集。 18 警告: 建议为程序集提供完全指定的文字标识, 19 警告: 并由简单名称、版本、区域性和公钥标记组成。 20 警告: 有关此问题的详细信息和常见解决方案,请参见白皮书 http://go.microsoft.com/fwlink/?LinkId=109270。 21 日志: Appbase = file:///C:/Windows/System32 22 日志: 初始 PrivatePath = NULL 23 调用程序集: (Unknown)。 24 === 25 日志: 此绑定从 default 加载上下文开始。 26 日志: 找到应用程序配置文件(E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_4_1_2.2\bin\Debug\Example_4_1_2.2.exe.Config)。 27 日志: 正在使用应用程序配置文件: E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_4_1_2.2\bin\Debug\Example_4_1_2.2.exe.Config 28 日志: 使用主机配置文件: 29 日志: 使用 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config 的计算机配置文件。 30 日志: 此时没有为引用应用策略(私有、自定义、分部或基于位置的程序集绑定)。 31 日志: 尝试下载新的 URL file:///C:/Windows/System32/Example_4_1_2.2.DLL。 32 日志: 尝试下载新的 URL file:///C:/Windows/System32/Example_4_1_2.2/Example_4_1_2.2.DLL。 33 日志: 尝试下载新的 URL file:///C:/Windows/System32/Example_4_1_2.2.EXE。 34 日志: 尝试下载新的 URL file:///C:/Windows/System32/Example_4_1_2.2/Example_4_1_2.2.EXE。 35 36 Fields: 37 MT Field Offset Type VT Attr Value Name 38 00007ffbc0d385a0 4000283 8 System.Int32 1 instance 1359 m_stringLength 39 00007ffbc0d36838 4000284 c System.Char 1 instance 7a0b m_firstChar 40 00007ffbc0d359c0 4000288 e0 System.String 0 shared static Empty 41 >> Domain:Value 000001ff382c25f0:NotInit 000001ff38378080:NotInit <<
当然,以上是开启【程序集日志绑定的】,如果该功能没有开启,输出内容就是这样了。
1 0:000> !DumpObj /d 000001ff39c9f288 2 Name: System.String 3 MethodTable: 00007ffbc0d359c0 4 EEClass: 00007ffbc0d12ec0 5 Size: 404(0x194) bytes 6 File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 7 String: 警告: 程序集绑定日志记录被关闭。 8 要启用程序集绑定失败日志记录,请将注册表值 [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD)设置为 1。 9 注意: 会有一些与程序集绑定失败日志记录关联的性能损失。 10 要关闭此功能,请移除注册表值 [HKLM\Software\Microsoft\Fusion!EnableLog]。 11 12 Fields: 13 MT Field Offset Type VT Attr Value Name 14 00007ffbc0d385a0 4000283 8 System.Int32 1 instance 189 m_stringLength 15 00007ffbc0d36838 4000284 c System.Char 1 instance 8b66 m_firstChar 16 00007ffbc0d359c0 4000288 e0 System.String 0 shared static Empty 17 >> Domain:Value 0000019462a026e0:NotInit 0000019462ab4e00:NotInit <<
接下来,我们使用【fuslogvw. exe】工具试试,它也能输出同样的内容。
在【程序集绑定日志查看器】中,双击我们的程序,显示详细内容。
1 *** 程序集联编程序日志项 (2024/8/13 @ 13:16:45) *** 2 3 操作失败。 4 绑定结果: hr = 0x80070002。系统找不到指定的文件。 5 6 程序集管理器加载位置: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll 7 在可执行文件下运行 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_4_1_2.2\bin\Debug\Example_4_1_2.2.exe 8 --- 详细的错误日志如下。 9 10 === 预绑定状态信息 === 11 日志: DisplayName = Example_4_1_2.2 12 (Partial) 13 警告: 为程序集提供了部分绑定信息: 14 警告: 程序集名称: Example_4_1_2.2 | 域 ID: 2 15 警告: 当仅提供程序集显示名称的一部分时,将发生部分绑定。 16 警告: 这可能导致联编程序加载错误的程序集。 17 警告: 建议为程序集提供完全指定的文字标识, 18 警告: 并由简单名称、版本、区域性和公钥标记组成。 19 警告: 有关此问题的详细信息和常见解决方案,请参见白皮书 http://go.microsoft.com/fwlink/?LinkId=109270。 20 日志: Appbase = file:///C:/Windows/System32 21 日志: 初始 PrivatePath = NULL 22 日志: 动态基 = NULL 23 日志: 缓存基 = NULL 24 日志: AppName = Example_4_1_2.2.exe 25 调用程序集: (Unknown)。 26 === 27 日志: 此绑定从 default 加载上下文开始。 28 日志: 尝试从 file:///E:/Visual Studio 2022/Source/Projects/AdvancedDebug.NetFramework.Test/Example_4_1_2.2/bin/Debug/Example_4_1_2.2.exe.Config 下载应用程序配置文件。 29 日志: 找到应用程序配置文件(E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_4_1_2.2\bin\Debug\Example_4_1_2.2.exe.Config)。 30 日志: 正在使用应用程序配置文件: E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_4_1_2.2\bin\Debug\Example_4_1_2.2.exe.Config 31 日志: 使用主机配置文件: 32 日志: 使用 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config 的计算机配置文件。 33 日志: 此时没有为引用应用策略(私有、自定义、分部或基于位置的程序集绑定)。 34 日志: 尝试下载新的 URL file:///C:/Windows/System32/Example_4_1_2.2.DLL。 35 日志: 尝试下载新的 URL file:///C:/Windows/System32/Example_4_1_2.2/Example_4_1_2.2.DLL。 36 日志: 尝试下载新的 URL file:///C:/Windows/System32/Example_4_1_2.2.EXE。 37 日志: 尝试下载新的 URL file:///C:/Windows/System32/Example_4_1_2.2/Example_4_1_2.2.EXE。 38 日志: 已尝试所有探测 URLs 但全部失败。
从上面的 CLR 加载器日志中,可以看到 CLR 加载器尝试从路径 C:Windows\System32 中加载 Example_4_1_2.2.exe 程序集。为什么会选择这个路径?答案在于,当一个对象通过列集来跨越应用程序域的边界时,这个对象会被串行化(serialization),这意味着目标应用程序域必须访问包含这个类型定义的程序集。如果没有这个程序集,那么反串行化操作(deserialization)就会失败。
还需要注意的是,应用程序域可以控制程序集探测路径的某些特定部分。具体来说,绑定日志中的 AppBase 被设置为 C:\Windows\System32,就是我们红色标注的内容。如果进一步观察源代码中的 RunInDedicated 方法,那么会看到以下代码行:
1 AppDomainSetup domaininfo =new AppDomainSetup(); 2 domaininfo.ApplicationBase="C:Windows1System32"; 3 return AppDomain.CreateDomain("MyDomain",null, domaininfo);
在 NET Framework 环境下(Net 5.0 及其以上就不可以了),在处理加载器问题时,托管调试助手(Managed Debugging Assistant,MDA)也是一个很好用的工具。对于 CLR 加载器来说,有一个叫做 bindingFailure 的 MDA 可用于分析 CLR 加载器问题。要启用这个 MDA,可以将以下XML保存到Example_4_1_2.2.exe.mda. config 文件中,内容如下:
1 <?xml version="1.0" encoding="utf-8" ?> 3 <mdaConfig> 4 <assistants> 5 <bindingFailure /> 6 </assistants> 7 </mdaConfig>
这个 XML 只是为程序 Example_4_1_2.2.exe 启用 bindingFailure。如果在调试器下重新运行 Example_4_1_2.2.exe,并重现这个问题(通过选择选项2),那么可以看到以下输出信息:
1 :000> g 2 ModLoad: 00007ffb`e0a00000 00007ffb`e0ab0000 C:\Windows\System32\ADVAPI32.dll 3 ModLoad: 00007ffb`e0c20000 00007ffb`e0cbe000 C:\Windows\System32\msvcrt.dll 4 ModLoad: 00007ffb`ded20000 00007ffb`dedc0000 C:\Windows\System32\sechost.dll 5 ModLoad: 00007ffb`e0590000 00007ffb`e06b5000 C:\Windows\System32\RPCRT4.dll 6 ModLoad: 00007ffb`deb90000 00007ffb`debb7000 C:\Windows\System32\bcrypt.dll 7 ModLoad: 00007ffb`c5bd0000 00007ffb`c5c7a000 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscoreei.dll 8 ModLoad: 00007ffb`e0000000 00007ffb`e0055000 C:\Windows\System32\SHLWAPI.dll 9 ModLoad: 00007ffb`de450000 00007ffb`de462000 C:\Windows\System32\kernel.appcore.dll 10 ModLoad: 00007ffb`ddd30000 00007ffb`ddd3a000 C:\Windows\SYSTEM32\VERSION.dll 11 ModLoad: 00007ffb`c3d00000 00007ffb`c4834000 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll 12 ModLoad: 00007ffb`dfce0000 00007ffb`dfe7e000 C:\Windows\System32\USER32.dll 13 ModLoad: 00007ffb`c4a30000 00007ffb`c4a46000 C:\Windows\SYSTEM32\VCRUNTIME140_CLR0400.dll 14 ModLoad: 00007ffb`de9a0000 00007ffb`de9c2000 C:\Windows\System32\win32u.dll 15 ModLoad: 00007ffb`e0bd0000 00007ffb`e0bfb000 C:\Windows\System32\GDI32.dll 16 ModLoad: 00007ffb`c37a0000 00007ffb`c385d000 C:\Windows\SYSTEM32\ucrtbase_clr0400.dll 17 ModLoad: 00007ffb`de1f0000 00007ffb`de307000 C:\Windows\System32\gdi32full.dll 18 ModLoad: 00007ffb`de310000 00007ffb`de3ad000 C:\Windows\System32\msvcp_win.dll 19 ModLoad: 00007ffb`de850000 00007ffb`de950000 C:\Windows\System32\ucrtbase.dll 20 ModLoad: 00007ffb`e0ab0000 00007ffb`e0ae2000 C:\Windows\System32\IMM32.DLL 21 ModLoad: 00007ffb`e0060000 00007ffb`e03b3000 C:\Windows\System32\combase.dll 22 (3e84.220c): Unknown exception - code 04242420 (first chance) 23 ModLoad: 00007ffb`c0d10000 00007ffb`c236e000 C:\Windows\assembly\NativeImages_v4.0.30319_64\mscorlib\4bc5e5252873c08797895d5b6fe6ddfd\mscorlib.ni.dll 24 ModLoad: 00007ffb`dfbb0000 00007ffb`dfcdb000 C:\Windows\System32\ole32.dll 25 ModLoad: 00007ffb`e0060000 00007ffb`e03b3000 C:\Windows\System32\combase.dll 26 ModLoad: 00007ffb`de3c0000 00007ffb`de442000 C:\Windows\System32\bcryptPrimitives.dll 27 ModLoad: 00007ffb`c04c0000 00007ffb`c060f000 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clrjit.dll 28 ModLoad: 00007ffb`dda80000 00007ffb`dda98000 C:\Windows\SYSTEM32\CRYPTSP.dll 29 ModLoad: 00007ffb`dd1c0000 00007ffb`dd1f4000 C:\Windows\system32\rsaenh.dll 30 ModLoad: 00007ffb`ddbb0000 00007ffb`ddbbc000 C:\Windows\SYSTEM32\CRYPTBASE.dll 31 (3e84.220c): C++ EH exception - code e06d7363 (first chance) 32 (3e84.220c): C++ EH exception - code e06d7363 (first chance) 33 (3e84.220c): C++ EH exception - code e06d7363 (first chance) 34 <mda:msg xmlns:mda="http://schemas.microsoft.com/CLR/2004/10/mda"> 35 <!-- 36 显示名为“Example_4_1_2.2”的程序集未能加载到 ID 为 2 的 AppDomain 的“LoadFrom”绑定上下文中。错误的原因为: 37 System.IO.FileNotFoundException: 38 未能加载文件或程序集“Example_4_1_2.2”或它的某一个依赖项。系统找不到指定的文件。 39 --> 40 <mda:bindingFailureMsg break="true"> 41 <assemblyInfo appDomainId="2" displayName="Example_4_1_2.2" codeBase="" hResult="-2147024894" bindingContextId="1"/> 42 </mda:bindingFailureMsg> 43 </mda:msg> 44 (3e84.220c): Break instruction exception - code 80000003 (first chance) 45 KERNELBASE!wil::details::DebugBreak+0x2: 46 00007ffb`de63b502 cc int 3
当调试器由于抛出异常而中断时,我们可以看到一些XML形式的信息。其中大部分信息都是我们之前通过观察异常本身得到的,除此之外还输出了其他一些有用的信息,例如应用程序域的ID。我们可以使用DumpDomain 命令来得到关于应用程序域的更详细的信息:
1 0:000> !DumpDomain 2 -------------------------------------- 3 System Domain: 00007ffbc479d7a0 4 LowFrequencyHeap: 00007ffbc479dd18 5 HighFrequencyHeap: 00007ffbc479dda8 6 StubHeap: 00007ffbc479de38 7 Stage: OPEN 8 Name: None 9 -------------------------------------- 10 Shared Domain: 00007ffbc479d1d0 11 LowFrequencyHeap: 00007ffbc479dd18 12 HighFrequencyHeap: 00007ffbc479dda8 13 StubHeap: 00007ffbc479de38 14 Stage: OPEN 15 Name: None 16 Assembly: 000002890730f870 [C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll] 17 ClassLoader: 000002890730f9c0 18 Module Name 19 00007ffbc0d11000 C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 20 21 -------------------------------------- 22 Domain 1: 0000028907272540 23 LowFrequencyHeap: 0000028907272d38 24 HighFrequencyHeap: 0000028907272dc8 25 StubHeap: 0000028907272e58 26 Stage: OPEN 27 SecurityDescriptor: 0000028907274b80 28 Name: Example_4_1_2.2.exe 29 Assembly: 000002890730f870 [C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll] 30 ClassLoader: 000002890730f9c0 31 SecurityDescriptor: 000002890730dd00 32 Module Name 33 00007ffbc0d11000 C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 34 35 Assembly: 0000028907324020 [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_4_1_2.2\bin\Debug\Example_4_1_2.2.exe] 36 ClassLoader: 0000028907324170 37 SecurityDescriptor: 00000289073233f0 38 Module Name 39 00007ffb647141b0 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_4_1_2.2\bin\Debug\Example_4_1_2.2.exe 40 41 -------------------------------------- 42 Domain 2: 0000028907326f70(这个就是发生异常的应用程序域) 43 LowFrequencyHeap: 0000028907327768 44 HighFrequencyHeap: 00000289073277f8 45 StubHeap: 0000028907327888 46 Stage: OPEN 47 SecurityDescriptor: 000002890732dfe0 48 Name: MyDomain 49 Assembly: 000002890730f870 [C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll] 50 ClassLoader: 000002890730f9c0 51 SecurityDescriptor: 00000289073301d0 52 Module Name 53 00007ffbc0d11000 C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
4.4、互用性与 DllNotFoundException
A、基础知识
CLR 提供了一个互用性层,可以让 .NET 代码和底层的非托管代码模块相互调用。根据不同类型的非托管代码,可以有两种不同的互用性机制。第一种:平台调用(Platform Invocation,P/Invoke),这种机制可以使开发人员调用非托管代码模块(DLL)中导出的函数。要实现这种机制,必须定义与托管代码等价的方法原型,并使用【DllImport】特性来说明这是一个 P/Invoke 方法。
第二种:COM 互用性。正是由于有这个特性,才可以使托管代码和非托管 COM 对象一起工作。
当我们使用 P/Invoke 调用非托管方法时,如果没有找到指定的 dll,就会抛出 System.DllNotFoundException 异常。这个没什么好说的,也很容易理解。
我们直接运行源码,我们可以看到抛出了一个 DIlNotFoundException异常。我们介绍了 CLR 在绑定到正确程序集时使用的程序集加载逻辑。那么,这种逻辑是否同样适用于非托管 DLL?答案是不适用。在 P/Invoke 调用过程中使用的任何非托管 DLL,都将通过底层的 kermel32!LoadLibrary 来加载,这个API使用了与 Windows 加载动态库时相同的探测逻辑。关于加载逻辑的详细讨论,请参见MSDN文档中 LoadLibrary 部分。事实上,在前面分析加载问题时使用的 fuslogvw.exe 工作在分析 P/Invoke 加载问题时不会起任何作用。
与P/Invoke 依赖于 Windows加载逻辑非常类似,COM 互用性层依赖于COM 加载器在COM对象能够由托管代码访问之前,必须在注册表中正确地注册它们,并且遵循COM子系统使用的相同加载逻辑。
B、眼见为实
调试源码:ExampleCore_4_1_3 和 ExampleCore_4_1_4(C++)
调试任务:探讨 P/Invoke 平台调用找不到 DLL 的反应
如果大家想测试,我提供源码,Net8.0 项目:ExampleCore_4_1_3,C++ 项目:ExampleCore_4_1_4,该项目必须设置成输出 dll。将生成的 dll 拷贝到我们 Net 项目 debug 目录下,为了能出错,我们 ExampleCore_4_1_4.dll 的名称改成 ExampleCore_4_1_4.old,直接运行程序就可以了。如果想查看正确输出结果,把后缀名改成 .dll 就可以了。
给大家一个直观的认识,效果如图:
说明一下,在 P/Invoke 调用过程中使用的非托管 DLL,都是通过底层 kernel32!LoadLibrary 来加载的,这个 API 使用了与 Windows 加载动态库时相同的探测逻辑。如果我们想使用【fuslogvw.exe】分析 P/Invoke 加载问题时是不起作用的。
编译项目,打开【Windbg Preview】调试器,依次点击【文件】---【Launch executable】,加载我们的可执行程序 ExampleCore_4_1_3.exe,进入调试器。
进入调试器后,【g】命令直接运行,调试器会自己中断执行,因为程序抛出了异常。
1 0:000> g 2 ModLoad: 00007ff9`33270000 00007ff9`332a2000 C:\Windows\System32\IMM32.DLL 3 ModLoad: 00007ff9`15100000 00007ff9`15159000 C:\Program Files\dotnet\host\fxr\8.0.7\hostfxr.dll 4 ModLoad: 00007ff9`06f40000 00007ff9`06fa4000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\hostpolicy.dll 5 ModLoad: 00007ff8`384a0000 00007ff8`38985000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\coreclr.dll 6 ModLoad: 00007ff9`32550000 00007ff9`3267b000 C:\Windows\System32\ole32.dll 7 ModLoad: 00007ff9`329c0000 00007ff9`32d13000 C:\Windows\System32\combase.dll 8 ModLoad: 00007ff9`32d30000 00007ff9`32dfd000 C:\Windows\System32\OLEAUT32.dll 9 ModLoad: 00007ff9`32160000 00007ff9`321e2000 C:\Windows\System32\bcryptPrimitives.dll 10 (3cd4.3eb4): Unknown exception - code 04242420 (first chance) 11 ModLoad: 00007ff8`37340000 00007ff8`37fcd000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Private.CoreLib.dll 12 ModLoad: 00007ff8`37180000 00007ff8`37339000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\clrjit.dll 13 ModLoad: 00007ff9`32220000 00007ff9`32232000 C:\Windows\System32\kernel.appcore.dll 14 ModLoad: 0000018e`50dd0000 0000018e`50dd8000 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_3\bin\Debug\net8.0\ExampleCore_4_1_3.dll 15 ModLoad: 0000018e`50de0000 0000018e`50dee000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Runtime.dll 16 ModLoad: 00007ff9`07a90000 00007ff9`07ab8000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Console.dll 17 (3cd4.3eb4): C++ EH exception - code e06d7363 (first chance) 18 ModLoad: 00007ff8`f7e40000 00007ff8`f806e000 C:\Windows\SYSTEM32\icu.dll 19 (3cd4.3eb4): CLR exception - code e0434352 (first chance) 20 (3cd4.3eb4): CLR exception - code e0434352 (!!! second chance !!!) 21 KERNELBASE!RaiseException+0x69: 22 00007ff9`31bacf19 0f1f440000 nop dword ptr [rax+rax]
我们使用【kb】命令,转储当前线程的调用栈和参数。
1 0:000> kb 2 # RetAddr : Args to Child : Call Site 3 00 00007ff8`3855b423 : 0000018e`538096b0 0000008c`45fce570 00000000`00000000 0000018e`4f412fb0 : KERNELBASE!RaiseException+0x69 4 01 00007ff8`384a9cfc : 00000000`00000000 0000018e`4f4195f0 00000000`00000006 0000018e`4f435f90 : coreclr!RaiseTheExceptionInternalOnly+0x26b [D:\a\_work\1\s\src\coreclr\vm\excep.cpp @ 2795] 5 02 00007ff8`38636503 : 0000018e`4f4195f0 0000018e`4f4195f0 00000000`00000000 00007ff8`388633c8 : coreclr!UnwindAndContinueRethrowHelperAfterCatch+0x38 [D:\a\_work\1\s\src\coreclr\vm\excep.cpp @ 7704] 6 03 00007ff8`385fd085 : 0000008c`45fcea88 0000008c`45fcea38 0000008c`45fceb50 00000000`00000001 : coreclr!PreStubWorker+0x171e23 [D:\a\_work\1\s\src\coreclr\vm\prestub.cpp @ 2427] 7 04 00007ff7`d8aa196e : 000001ce`e5b104a0 0000008c`45fcefd8 0000008c`45fcefd8 0000008c`45fcebc9 : coreclr!ThePreStub+0x55 [D:\a\_work\1\s\src\coreclr\vm\amd64\ThePreStubAMD64.asm @ 21] 8 05 00007ff8`385fbd43 : 0000018e`53808ea0 0000008c`45fcefd8 0000008c`45fcefd8 0000008c`45fcebc9 : ExampleCore_4_1_3!ExampleCore_4_1_3.Program.Main+0x3e [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_3\Program.cs @ 14] 9 06 00007ff8`38530ac9 : 00000000`00000000 00000000`00000130 0000008c`45fcebd8 00007ff8`384b022e : coreclr!CallDescrWorkerInternal+0x83 [D:\a\_work\1\s\src\coreclr\vm\amd64\CallDescrWorkerAMD64.asm @ 100] 10 07 (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!CallDescrWorkerWithHandler+0x56 [D:\a\_work\1\s\src\coreclr\vm\callhelpers.cpp @ 67] 11 08 00007ff8`3852d6e0 : 0000008c`45fcec58 00000000`00000000 00000000`00000048 00007ff8`3859d176 : coreclr!MethodDescCallSite::CallTargetWorker+0x2a1 [D:\a\_work\1\s\src\coreclr\vm\callhelpers.cpp @ 570] 12 09 (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!MethodDescCallSite::Call+0xb [D:\a\_work\1\s\src\coreclr\vm\callhelpers.h @ 458] 13 0a 00007ff8`38552ff6 : 0000018e`53808ea0 0000018e`53808ea0 00000000`00000000 0000008c`45fcefd8 : coreclr!RunMainInternal+0x11c [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1304] 14 0b 00007ff8`3855332b : 0000018e`4f4195f0 0000018e`00000000 0000018e`4f4195f0 00000000`00000000 : coreclr!RunMain+0xd2 [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1375] 15 0c 00007ff8`384a9141 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000130 : coreclr!Assembly::ExecuteMainMethod+0x1bf [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1504] 16 0d 00007ff8`385be9e8 : 00000000`00000001 0000008c`45fcf201 0000008c`45fcf200 00007ff9`06f423ea : coreclr!CorHost2::ExecuteAssembly+0x281 [D:\a\_work\1\s\src\coreclr\vm\corhost.cpp @ 349] 17 0e 00007ff9`06f62b76 : 0000018e`4f3ec4a0 0000018e`4f3ec1a0 00000000`00000000 0000018e`4f3ec1a0 : coreclr!coreclr_execute_assembly+0xd8 [D:\a\_work\1\s\src\coreclr\dlls\mscoree\exports.cpp @ 504] 18 0f (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : hostpolicy!coreclr_t::execute_assembly+0x2a [D:\a\_work\1\s\src\native\corehost\hostpolicy\coreclr.cpp @ 109] 19 10 00007ff9`06f62e5c : 0000018e`4f3d8868 0000008c`45fcf429 00007ff9`06f9ca10 0000018e`4f3d8868 : hostpolicy!run_app_for_context+0x596 [D:\a\_work\1\s\src\native\corehost\hostpolicy\hostpolicy.cpp @ 256] 20 11 00007ff9`06f6379a : 00000000`00000000 0000018e`4f3d8860 0000018e`4f3d8860 00000000`00000000 : hostpolicy!run_app+0x3c [D:\a\_work\1\s\src\native\corehost\hostpolicy\hostpolicy.cpp @ 285] 21 12 00007ff9`1510b5c9 : 0000018e`4f3e9998 0000018e`4f3e9880 00000000`00000000 0000008c`45fcf529 : hostpolicy!corehost_main+0x15a [D:\a\_work\1\s\src\native\corehost\hostpolicy\hostpolicy.cpp @ 426] 22 13 00007ff9`1510e066 : 0000018e`4f3e9150 0000008c`45fcf8b0 00000000`00000000 00000000`00000000 : hostfxr!execute_app+0x2e9 [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 145] 23 14 00007ff9`151102ec : 00007ff9`151425e8 0000018e`4f3e7b00 0000008c`45fcf7f0 0000008c`45fcf7a0 : hostfxr!`anonymous namespace'::read_config_and_execute+0xa6 [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 532] 24 15 00007ff9`1510e644 : 0000008c`45fcf8b0 0000008c`45fcf8d0 0000008c`45fcf821 0000018e`4f3e7f20 : hostfxr!fx_muxer_t::handle_exec_host_command+0x16c [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 1007] 25 16 00007ff9`151085a0 : 0000008c`45fcf8d0 0000018e`4f3e7400 00000000`00000001 0000018e`4f3d0000 : hostfxr!fx_muxer_t::execute+0x494 [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 578] 26 17 00007ff7`fbe5f998 : 00007ff9`31fef4e8 00007ff9`15109b10 0000008c`45fcfa70 0000018e`4f3e70f0 : hostfxr!hostfxr_main_startupinfo+0xa0 [D:\a\_work\1\s\src\native\corehost\fxr\hostfxr.cpp @ 62] 27 18 00007ff7`fbe5fda6 : 00007ff7`fbe6b6b0 00000000`00000007 0000018e`4f3d8860 00000000`0000005e : apphost!exe_start+0x878 [D:\a\_work\1\s\src\native\corehost\corehost.cpp @ 240] 28 19 00007ff7`fbe612e8 : 00000000`00000000 00000000`00000000 0000018e`4f3d8860 00000000`00000000 : apphost!wmain+0x146 [D:\a\_work\1\s\src\native\corehost\corehost.cpp @ 311] 29 1a (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : apphost!invoke_main+0x22 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 90] 30 1b 00007ff9`324a7344 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : apphost!__scrt_common_main_seh+0x10c [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288] 31 1c 00007ff9`344226b1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14 32 1d 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
红色标记的方法 KERNELBASE!RaiseException 抛出了异常,第一个参数就是异常对象的地址 0000018e`538096b0,针对该地址我们使用【!do 0000018e`538096b0】命令查看异常的详情。
1 0:000> !do 0000018e`538096b0 2 Name: System.DllNotFoundException(异常的对象) 3 MethodTable: 00007ff7d8b5c9e0 4 EEClass: 00007ff7d8b648b0 5 Tracked Type: false 6 Size: 160(0xa0) bytes 7 File: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.7\System.Private.CoreLib.dll 8 Fields: 9 MT Field Offset Type VT Attr Value Name 10 00007ff7d8bb48f0 4000264 8 ...ection.MethodBase 0 instance 0000000000000000 _exceptionMethod 11 00007ff7d8a8ec08 4000265 10 System.String 0 instance 0000018e5380ce18 _message 12 00007ff7d8b23060 4000266 18 ...tions.IDictionary 0 instance 0000000000000000 _data 13 00007ff7d8b20820 4000267 20 System.Exception 0 instance 0000000000000000 _innerException 14 00007ff7d8a8ec08 4000268 28 System.String 0 instance 0000000000000000 _helpURL 15 00007ff7d8bd3180 4000269 30 System.Byte[] 0 instance 0000018e5380cf30 _stackTrace 16 00007ff7d8bd3180 400026a 38 System.Byte[] 0 instance 0000000000000000 _watsonBuckets 17 00007ff7d8a8ec08 400026b 40 System.String 0 instance 0000000000000000 _stackTraceString 18 00007ff7d8a8ec08 400026c 48 System.String 0 instance 0000000000000000 _remoteStackTraceString 19 00007ff7d89dc4d8 400026d 50 System.Object[] 0 instance 0000000000000000 _dynamicMethods 20 00007ff7d8a8ec08 400026e 58 System.String 0 instance 0000000000000000 _source 21 00007ff7d8a88b78 400026f 60 System.UIntPtr 1 instance 00007FF7D8AA196D _ipForWatsonBuckets 22 00007ff7d8a870a0 4000270 68 System.IntPtr 1 instance 0000000000000000 _xptrs 23 00007ff7d8a11188 4000271 70 System.Int32 1 instance -532462766 _xcode 24 00007ff7d8a11188 4000272 74 System.Int32 1 instance -2146233052 _HResult 25 00007ff7d8a8ec08 4000348 78 System.String 0 instance 0000000000000000 _className 26 00007ff7d8a8ec08 4000349 80 System.String 0 instance 0000000000000000 _assemblyName 27 00007ff7d8a8ec08 400034a 88 System.String 0 instance 0000000000000000 _messageArg 28 00007ff7d8a11188 400034b 90 System.Int32 1 instance 0 _resourceId
当然,我们也可以使用【!PrintException】或者【!pe】命令,直接打印异常。
1 0:000> !pe 2 Exception object: 0000018e538096b0 3 Exception type: System.DllNotFoundException 4 Message: 5 InnerException: <none> 6 StackTrace (generated): 7 SP IP Function 8 0000008C45FCE8A0 0000000000000000 ExampleCore_4_1_3!ExampleCore_4_1_3.Program.Alloc(System.String)+0x1 9 0000008C45FCE950 00007FF7D8AA196D ExampleCore_4_1_3!ExampleCore_4_1_3.Program.Main(System.String[])+0x3d 10 11 StackTraceString: <none> 12 HResult: 80131524
由于我们使用的 Net 8.0 版本和P/Invoke 的原因,【fuslogvw.exe】工具是不起作用的。
4.5、轻量级代码生成的调试
A、基础知识
CLR 提供了一种既方便又高效动态生成代码的机制(也称为 CodeGen)。在 CLR 2.0 之前,开发人员如果想实现动态生成代码,必须自己维护在 CLR 上运行的任何代码段的结构完整性。比如:如果我们想定义一个方法,该方法接受两个整型参数,返回两个参数之和,我们要做的工作是,必须定义方法本身的实现逻辑(包括 IL)、这个方法使用的类型、这个类型定义所在的模块以及程序集所在的模块等。当我们只希望生成一下规模很小或者是不是很复杂的方法时就很不方便。
在 CLR 2.0 引入了【轻量级代码生成机制(Light Weight Code Generation,LCG)】。当使用 LCG 时,我们定义代码更简单,不需要关注和代码无关的东西(模块、类型、程序集等),并通过委托机制来调用它。
System.Reflection.Emit 命名空间下的 API 可以高效、动态的生成代码。
很庆幸,使用非托管调试器调试动态生成的代码并不困难。如果我们想使用【!bpmd】命令为方法设置断点,必须先找到方法的描述符。但是,对于 LCG 代码,我们需要做一些手动工作。具体来说,我们需要通过 JIT 组件中某个函数来获得方法描述符。这个函数就是:clrjit!CILJit::compileMethod,如果我们在这个方法上设置一个非托管断点(通过 bp 命令),那么可以从这个函数的第一个参数中获得方法描述符。
B、眼见为实
调试代码:ExampleCore_4_1_5
调试任务:调试由 System.Reflection.Emit 生成轻量级代码,在 Add 方法上设置断点
1)、NTSD 调试
编译项目,打开【Visual Studio 2022 Developer Command Prompt v17.9.4】命令行工具,输入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_5\bin\Debug\net8.0\ExampleCore_4_1_5.EXE】,如图:
打开【NTSD】 调试器窗口,效果如图:
我们使用【g】命令,运行调试器,直到调试器自动中断执行,开始我们的调试。
首先,我们使用【x clrjit!*compileMethod*】命令,查找【compileMethod】方法,因为 CLR 使用该方法编译我们代码。
1 0:000> x clrjit!*compileMethod* 2 00007ffb`c13abc60 clrjit!CILJit::compileMethod (class ICorJitInfo *, struct CORINFO_METHOD_INFO *, unsigned int, unsigned char **, unsigned int *)
我们找到了这个方法,可以针对 clrjit!CILJit::compileMethod 这个方法下一个断点。
1 0:000> bp clrjit!CILJit::compileMethod
【g】继续运行,直到调试器在断点处暂停。
1 0:000> g 2 Breakpoint 0 hit 3 clrjit!CILJit::compileMethod: 4 00007ffb`c13abc60 48895c2408 mov qword ptr [rsp+8],rbx ss:00000008`889fda00=00007ffb62a444a0
我们继续使用【kb】命令,查看线程调用栈,并查看第1个参数。当然也可以为【kb 1】只显示一条栈帧。
1 0:000> kb 2 RetAddr : Args to Child : Call Site 3 00007ffb`c23789fe : 00007ffb`62a444a0 00007ffb`62a444a0 0000019a`516b8550 00000000`00000000 : clrjit!CILJit::compileMethod 4 (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!invokeCompileMethodHelper+0x70 5 (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!invokeCompileMethod+0xb0 6 00007ffb`c2365507 : 00007ffb`62a10008 00000000`00000000 00007ffb`62a10008 00000000`00000000 : coreclr!UnsafeJitFunction+0x7ee 7 00007ffb`c2365327 : 00010000`00000001 00007b33`88000001 00000000`00007b33 00007ffb`c2749b98 : coreclr!MethodDesc::JitCompileCodeLocked+0xef 8 00007ffb`c2364ffc : 000001da`e7d321d0 00000008`889fe2f0 000001da`e7d321d0 000001da`e7d321d0 : coreclr!MethodDesc::JitCompileCodeLockedEventWrapper+0x17b 9 00007ffb`c2364c43 : 00000008`889fe3f0 00000008`00000000 00000000`00000000 00000000`00000000 : coreclr!MethodDesc::JitCompileCode+0x2bc 10 00007ffb`c23fb146 : 00007ffb`62a44430 00000000`00000000 00000000`00000000 00000002`51650000 : coreclr!MethodDesc::PrepareILBasedCode+0xc3 11 (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!MethodDesc::PrepareCode+0x12 12 00007ffb`c2374c6f : 00007ffb`62a44430 00007ffb`627d4190 0000019a`516a04c0 00007ffb`6283fc18 : coreclr!MethodDesc::PrepareInitialCode+0x4e 13 00007ffb`c23748d9 : 0000019a`5169b7e0 0000019a`516a04c0 00007ffb`0002a020 00007ffb`62a10008 : coreclr!MethodDesc::DoPrestub+0x26f 14 00007ffb`c24acc05 : 00000008`889fe9e8 00000008`889fe998 01e2ff49`20c38349 ff800180`02800080 : coreclr!PreStubWorker+0x1f9 15 00007ffb`62931c5a : 00000000`00000001 00000000`00000002 00000000`00000002 00000000`00000000 : coreclr!ThePreStub+0x55 16 00007ffb`c24ab8c3 : 0000019a`55c08ea0 00000008`889fef38 00000008`889fef38 00000008`889feb29 : 0x00007ffb`62931c5a 17 00007ffb`c23e0b19 : 00000000`00000000 00000000`00000130 00000008`889feb38 00007ffb`c2360232 : coreclr!CallDescrWorkerInternal+0x83 18 (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!CallDescrWorkerWithHandler+0x56 19 00007ffb`c23dd730 : 00000008`889febb8 00000000`00000000 00000000`00000048 00007ffb`c244d046 : coreclr!MethodDescCallSite::CallTargetWorker+0x2a1 20 (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!MethodDescCallSite::Call+0xb 21 00007ffb`c2402fc6 : 0000019a`55c08ea0 0000019a`55c08ea0 00000000`00000000 00000008`889fef38 : coreclr!RunMainInternal+0x11c 22 00007ffb`c24032fb : 0000019a`516a04c0 0000019a`00000000 0000019a`516a04c0 00000000`00000000 : coreclr!RunMain+0xd2 23 00007ffb`c2359141 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000130 : coreclr!Assembly::ExecuteMainMethod+0x1bf 24 00007ffb`c246e8b8 : 00000000`00000001 00000008`889ff101 00000008`889ff160 00007ffb`c28423ea : coreclr!CorHost2::ExecuteAssembly+0x281 25 00007ffb`c2862b76 : 0000019a`51671980 0000019a`51671680 00000000`00000000 0000019a`51671680 : coreclr!coreclr_execute_assembly+0xd8 26 (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : hostpolicy!coreclr_t::execute_assembly+0x2a 27 00007ffb`c2862e5c : 0000019a`5165c358 00000008`889ff389 00007ffb`c289c9c0 0000019a`5165c358 : hostpolicy!run_app_for_context+0x596 28 00007ffb`c286379a : 00000000`00000000 0000019a`5165c350 0000019a`5165c350 00000000`00000000 : hostpolicy!run_app+0x3c 29 00007ffb`c28bb5c9 : 0000019a`516707a8 0000019a`51670690 00000000`00000000 00000008`889ff489 : hostpolicy!corehost_main+0x15a 30 00007ffb`c28be066 : 0000019a`5166f6a0 00000008`889ff810 00000000`00000000 00000000`00000000 : hostfxr!execute_app+0x2e9 31 00007ffb`c28c02ec : 00007ffb`c28f25f8 0000019a`5166df30 00000008`889ff750 00000008`889ff700 : hostfxr!`anonymous namespace'::read_config_and_execute+0xa6 32 00007ffb`c28be644 : 00000008`889ff810 00000008`889ff830 00000008`889ff781 0000019a`5166e350 : hostfxr!fx_muxer_t::handle_exec_host_command+0x16c 33 00007ffb`c28b85a0 : 00000008`889ff830 0000019a`5166d830 00000000`00000001 0000019a`51650000 : hostfxr!fx_muxer_t::execute+0x494 34 *** WARNING: Unable to verify checksum for apphost.exe 35 00007ff6`2595f998 : 00007ffc`9a0df4e8 00007ffb`c28b9b10 00000008`889ff9d0 0000019a`5166d520 : hostfxr!hostfxr_main_startupinfo+0xa0 36 00007ff6`2595fda6 : 00007ff6`2596b6c0 00000000`00000007 0000019a`5165c350 00000000`0000005e : apphost!exe_start+0x878 37 00007ff6`259612e8 : 00000000`00000000 00000000`00000000 0000019a`5165c350 00000000`00000000 : apphost!wmain+0x146 38 (Inline Function) : --------`-------- --------`-------- --------`-------- --------`-------- : apphost!invoke_main+0x22 39 00007ffc`9a997344 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : apphost!__scrt_common_main_seh+0x10c 40 00007ffc`9c4226b1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14 41 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
clrjit!CILJit::compileMethod 方法的第一个参数就是我们 Add 方法的方法描述符。我们可以使用【!DumpMD】命令来验证。
1 0:000> !dumpmd 00007ffb`62a444a0 2 Method Name: DynamicClass.Add(Int32, Int32)(这就是我们动态生成的方法) 3 Class: 00007ffb62a443b8 4 MethodTable: 00007ffb62a44430 5 mdToken: 0000000006000000 6 Module: 00007ffb62a1b070 7 IsJitted: no(还没有编译) 8 Current CodeAddr: ffffffffffffffff(因为没有编译,所以地址就是 f) 9 Version History: 10 ILCodeVersion: 0000000000000000 11 ReJIT ID: 0 12 IL Addr: 0000000000000000 13 CodeAddr: 0000000000000000 (Optimized) 14 NativeCodeVersion: 0000000000000000
我们有了 MD,就是方法描述符,就可以使用【!bpmd -md 00007ffb`62a444a0】命令,为 Add 方法设置断点了。
1 0:000> !bpmd -md 00007ffb`62a444a0 2 MethodDesc = 00007FFB62A444A0 3 This DynamicMethodDesc is not yet JITTED. Placing memory breakpoint at 00007FFB62A444D0
【g】继续运行,会到我们到 Add 方法开始暂停,我们可以使用【!clrstack -a】命令,查看托管线程调用栈来确认。
1 0:000> g 2 Unable to insert breakpoint 2 at 00007ffb`62a50040, Win32 error 0n998 3 "内存位置访问无效。" 4 The breakpoint was set with BP. If you want breakpoints 5 to track module load/unload state you must use BU. 6 bp2 at 00007ffb`62a50040 failed 7 WaitForEvent failed, Win32 error 0n998 8 内存位置访问无效。 9 coreclr!MethodDesc::SetNativeCodeInterlocked+0x24 [inlined in coreclr!PrepareCodeConfig::SetNativeCode+0x49]: 10 00007ffb`c245dfe9 7523 jne coreclr!PrepareCodeConfig::SetNativeCode+0x6e (00007ffb`c245e00e) [br=0]
我这里测试有点问题,所有使用【NTSD】做的断点测试都失败,还没有找到原因。
2)、Windbg Preview 调试
编译项目,打开【Windbg Preview】,依次点击【文件】---->【launch executable】加载我们的项目文件:ExampleCore_4_1_5.exe。进入调试器后,继续使用【g】命令,运行调试器,调试会自动进入中断模式,也就是在这行源代码【Debugger.Break();】,我们的代码已经编译,但是还没有执行,也可以说,此时的代码还没有经过 JIT 编译。
我们先使用【lm】命令查看一下我们的 CLR 和 JIT 是否已经加载了。
1 0:000> lm 2 start end module name 3 0000029f`73ad0000 0000029f`73ad8000 ExampleCore_4_1_5 C (service symbols: CLR Symbols with PDB: C:\ProgramData\Dbg\sym\ExampleCore_4_1_5.pdb\11A5E302B38142129CE69ACABA26395C1\ExampleCore_4_1_5.pdb) 4 0000029f`73ae0000 0000029f`73aee000 System_Runtime (deferred) 5 0000029f`73af0000 0000029f`73af8000 System_Reflection_Emit_Lightweight (deferred) 6 0000029f`73b00000 0000029f`73b08000 System_Reflection_Emit_ILGeneration (deferred) 7 0000029f`73b10000 0000029f`73b18000 System_Reflection_Primitives (deferred) 8 00007ff7`4bf90000 00007ff7`4bfb9000 apphost C (private pdb symbols) C:\ProgramData\Dbg\sym\apphost.pdb\5633DAB747FE452D91289F0AE5A53DEB1\apphost.pdb 9 00007ffd`4be50000 00007ffd`4cadc000 System_Private_CoreLib (service symbols: CLR Symbols with PDB: C:\ProgramData\Dbg\sym\System.Private.CoreLib.pdb\44580BF6DF5DBDE0A6AF2B06590C0AF21\System.Private.CoreLib.pdb) 10 00007ffd`4d7f0000 00007ffd`4d9a9000 clrjit (deferred) 11 00007ffd`4dfc0000 00007ffd`4e4a6000 coreclr (private pdb symbols) C:\ProgramData\Dbg\sym\coreclr.pdb\0FCDE80BC2AB441FBFBD19C1354D99E71\coreclr.pdb 12 00007ffd`4f350000 00007ffd`4f3b4000 hostpolicy (deferred) 13 00007ffd`81620000 00007ffd`81648000 System_Console (deferred) 14 00007ffd`91b40000 00007ffd`91b99000 hostfxr (deferred) 15 00007ffe`35ec0000 00007ffe`35fd7000 gdi32full (deferred) 16 00007ffe`35fe0000 00007ffe`35ff2000 kernel_appcore (deferred) 17 00007ffe`36050000 00007ffe`36346000 KERNELBASE (pdb symbols) C:\ProgramData\Dbg\sym\kernelbase.pdb\4D43CAB87CB573B14B1F37FFEAF274731\kernelbase.pdb 18 00007ffe`36350000 00007ffe`363ed000 msvcp_win (deferred) 19 00007ffe`364a0000 00007ffe`364c2000 win32u (deferred) 20 00007ffe`364d0000 00007ffe`36552000 bcryptPrimitives (deferred) 21 00007ffe`366c0000 00007ffe`367c0000 ucrtbase (deferred) 22 00007ffe`36810000 00007ffe`36837000 bcrypt (deferred) 23 00007ffe`36d90000 00007ffe`36ebb000 ole32 (deferred) 24 00007ffe`36f20000 00007ffe`37045000 RPCRT4 (deferred) 25 00007ffe`37050000 00007ffe`37082000 IMM32 (deferred) 26 00007ffe`37090000 00007ffe`3722e000 USER32 (deferred) 27 00007ffe`37380000 00007ffe`3741e000 msvcrt (deferred) 28 00007ffe`37420000 00007ffe`37b8b000 SHELL32 (deferred) 29 00007ffe`37c90000 00007ffe`37d40000 ADVAPI32 (deferred) 30 00007ffe`37e00000 00007ffe`37ecd000 OLEAUT32 (deferred) 31 00007ffe`37ed0000 00007ffe`37f70000 sechost (deferred) 32 00007ffe`38460000 00007ffe`387b3000 combase (deferred) 33 00007ffe`387c0000 00007ffe`3887d000 KERNEL32 (deferred) 34 00007ffe`389a0000 00007ffe`389cb000 GDI32 (deferred) 35 00007ffe`38a10000 00007ffe`38c08000 ntdll (pdb symbols) C:\ProgramData\Dbg\sym\ntdll.pdb\90C2362B9D1F1F5088ABFA3BDE69BAAF1\ntdll.pdb
ExampleCore_4_1_5 我们的项目已经加载了,说明 CLR 和 JIT 也已经加载了,clrjit 红色标注的就是我们处理器,说明我的猜测也是对的。
然后,我们通过【x clrjit!*CompileMethod*】命令查找 CompileMethod方法。
1 0:000> x clrjit!*CompileMethod* 2 00007ffd`4d8dbc60 clrjit!CILJit::compileMethod (class ICorJitInfo *, struct CORINFO_METHOD_INFO *, unsigned int, unsigned char **, unsigned int *)
我们找到了 CompileMethod 方法,给这个方法下一个断点。
1 0:000> bp clrjit!CILJit::compileMethod
我们继续【g】运行调试器,已经在 CILJit::compileMethod 方法处断住了。
1 0:000> g 2 Breakpoint 0 hit 3 clrjit!CILJit::compileMethod: 4 00007ffd`4d09bc60 48895c2408 mov qword ptr [rsp+8],rbx ss:0000005a`bc97d8a0=00007ffcee8644a0
我们在 CILJit::compileMethod 这个方法已经断住了,然后我们使用【kb 1】命令,查看一下它的参数。
1 0:000> kb 1 2 # RetAddr : Args to Child : Call Site 3 00 00007ffd`4e1989fe : 00007ffc`ee8644a0 00007ffc`ee8644a0 0000024a`1f3716a0 00000000`00000000 : clrjit!CILJit::compileMethod [D:\a\_work\1\s\src\coreclr\jit\ee_il_dll.cpp @ 279]
00007ffc`ee8644a0 这个地址就是我们 Add 方法的描述符地址。
1 0:000> !dumpmd 00007ffc`ee8644a0 2 Method Name: DynamicClass.Add(Int32, Int32) 这就是我们要查找的方法 3 Class: 00007ffcee8643b8 4 MethodTable: 00007ffcee864430 5 mdToken: 0000000006000000 6 Module: 00007ffcee83b070 7 IsJitted: no(说明还没有被 JIT 编译) 8 Current CodeAddr: ffffffffffffffff 9 Version History: 10 ILCodeVersion: 0000000000000000 11 ReJIT ID: 0 12 IL Addr: 0000000000000000 13 CodeAddr: 0000000000000000 (Optimized) 14 NativeCodeVersion: 0000000000000000
我们使用【!bpmd -md 00007ffb`5d3244a0】命令为 Add 方法设置断点。
1 0:000> !bpmd -md 00007ffb`5d3244a0 2 MethodDesc = 00007FFB5D3244A0 3 This DynamicMethodDesc is not yet JITTED. Placing memory breakpoint at 00007FFB5D3244D0
我们继续【g】运行调试器,直到调试器在断点处中断。
1 0:000> g 2 Breakpoint 2 hit 3 00007ffb`5d330040 8d0411 lea eax,[rcx+rdx]
【g】继续运行,我们使用【!clrstack -a】命令查看一下托管调用栈。
1 0:000> !clrstack -a 2 OS Thread Id: 0x4d70 (0) 3 Child SP IP Call Site 4 0000008E08B7E558 00007ffb5d330040 SOS Warning: Loading symbols for dynamic assemblies is not yet supported 5 DynamicClass.Add(Int32, Int32) 6 PARAMETERS: 7 <no data> 8 <no data> 9 10 0000008E08B7E560 00007ffb5d211c5a ExampleCore_4_1_5.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_4_1_5\Program.cs @ 23] 11 PARAMETERS: 12 args (0x0000008E08B7E6A0) = 0x000001f28e808ea0 13 LOCALS: 14 0x0000008E08B7E688 = 0x000001f28e809640 15 0x0000008E08B7E680 = 0x000001f28e809668 16 0x0000008E08B7E678 = 0x000001f28e809bc8 17 0x0000008E08B7E670 = 0x000001f28e809ea8 18 0x0000008E08B7E66C = 0x0000000000000000 19 0x0000008E08B7E640 = 0x0000000000000000
五、总结
这篇文章的终于写完了,这篇文章的内容相对来说,不是很多。写完一篇,就说明进步了一点点。Net 高级调试这条路,也刚刚起步,还有很多要学的地方。皇天不负有心人,努力,不辜负自己,我相信付出就有回报,再者说,学习的过程,有时候,虽然很痛苦,但是,学有所成,学有所懂,这个开心的感觉还是不可言喻的。不忘初心,继续努力。做自己喜欢做的,开心就好。