目录
技术1、检测程序加载的程序集和类技术2、删除程序集中用不到的类Dnlib使用的其他问题收获一、Dnlib保存含有本地代码的程序集时候遇到的问题收获二、Dnlib的其他应用十天前,我发布了对.NET Core程序进行瘦身的开源软件Zack.DotNetTrimmer,与.NET Core内置的剪裁器相比,Zack.DotNetTrimmer不仅对程序的剪裁效果更好,而且还支持WPF、WinForm程序。
很多朋友对于这个开源项目的原理很感兴趣,因此我将通过这篇文章为大家介绍它的工作原理。
技术1、检测程序加载的程序集和类
微软提供了用于对.NET Core的运行时行为进行分析的库Diagnostics,它可以获取丰富的运行时信息,比如类的实例创建、程序集加载、类加载、方法调用、GC运行、文件读写操作、网络连接等。Visual Studio中对每个方法的调用时间进行评估的工具就是使用Diagnostics实现的。
要使用Diagnostics库,我们首先需要安装Microsoft.Diagnostics.NETCore.Client和Microsoft.Diagnostics.Tracing.TraceEvent这两个程序集,然后使用DiagnosticsClient类来连接被分析的.NET Core程序的进程。代码如下所示:
using Microsoft.Diagnostics.NETCore.Client;using Microsoft.Diagnostics.Tracing;using Microsoft.Diagnostics.Tracing.Parsers;using Microsoft.Diagnostics.Tracing.Parsers.Clr;using System.Diagnostics;using System.Diagnostics.Tracing;string filepath = @"E:temptest6ConsoleApp1.exe";//被分析的程序路径ProcessStartInfo psInfo = new ProcessStartInfo(filepath);psInfo.UseShellExecute = true;using Process? p = Process.Start(psInfo);//启动程序var prov//获取类名 Console.WriteLine($"Type Loaded:{typeName}");};source.Process();不同类型的消息对应source.Clr.All事件中的不同类型的对象,这些类都继承自TraceEvent,我这里分析的是程序集加载事件ModuleLoadUnloadTraceData和类加载事件TypeLoadStopTraceData。
这样我们就可以得知程序运行过程中加载的程序集和类型信息,这样就知道哪些程序集和类型没有被加载,从而我们就知道要删除哪些程序集和类型了。
技术2、删除程序集中用不到的类
Zack.DotNetTrimmer中提供了可以删除程序集中用不到的类的IL的功能,这个功能使用dnlib这个库来完成的程序集文件的编辑。Dnlib是一个对.NET程序集文件进行读、写、编辑的开源项目。
在Dnlib中,我们使用ModuleDefMD.Load来加载一个现有的程序集,Load方法的返回值是ModuleDefMD类型。ModuleDefMD代表程序集信息,比如其中的Types属性就代表程序集中的所有的类型。我们可以对ModuleDefMD以及其中的对象进行修改后,把修改完成的程序集调用Write方法再保存到磁盘中。
比如,下面的代码用来把一个程序集中的所有非public类型都给改成public类型,并且把方法上修饰的Attribute全部清除:
using dnlib.DotNet;string filename = @"E:tempnet6.0AppToBeTested1.dll";ModuleDefMD module = ModuleDefMD.Load(filename);foreach(var typeDef in module.Types){ if (typeDef.IsPublic == false) { typeDef.Attributes |= TypeAttributes.Public;//修改类的访问级别 } foreach(var methodDef in typeDef.Methods) methodDef.CustomAttributes.Clear();//清除方法的Attribute}module.Write(@"E:tempnet6.01.dll");//保存修改下面是待测试的程序集的源代码:
internal class Class1{ [DisplayName("AAA")] public void AA() { Console.WriteLine("hello"); }}如下是修改后的程序集的反编译结果:
public class Class1{public void AA(){Console.WriteLine("hello");}}可以看到我们对于程序集的修改起作用了。
掌握了使用Dnlib对程序集进行修改的方法,我们就可以实现删除程序集中用不到的类型的功能了,我们只要把对应的类型从ModuleDefMD的Types属性中删除掉即可。不过在实际操作中,这样做会遇到问题,因为我们要删除的类可能被其他的地方引用,尽管那些地方只是引用我们要删除的类,并没有真的调用,但是为了保证修改后程序集的校验合法性,ModuleDefMD的Write方法仍然会做合法性校验,否则Write方法就会抛出ModuleWriterException异常,比如:
动失败,查看Windows事件日志,我发现是程序启动的时候CLR启动失败造成的。根据Washi1337所说,如果只是程序集中含有ReadyToRun的本地代码,那么只要去掉程序集中的ILLibrary标志,让CLR跳过ReadyToRun本地代码,而直接执行IL代码就行了,毕竟对于ReadyToRun优化后的程序集仍然保存了原始的IL代码。但是我如Washi1337所说的操作之后,程序依旧启动失败,不清楚是什么原因,因为含有本地代码的程序集无法被很好的剪裁,因此我没有再深入研究,欢迎对CLR精通的朋友分享经验。收获二、Dnlib的其他应用
由于DnLib可以修改程序集,因此我们可以使用它做很多的事情,比如修改程序的默认行为(你懂的)。我们可以使用DnLib编写一个自己的代码混淆器或者实现面向切面编程(AOP)的静态织入。
你还想到了哪些DnLib的应用场景?欢迎分享。








