.NET Core分析程序集最優美的方法,不用Assembly.LoadFile(),超越ReflectionOnlyLoad


在編寫.NET程序的時候,如果需要對一個程序集文件進行分析,我們可以使用Assembly.LoadFile()來加載這個程序集,然后對LoadFile()方法返回的Assembly對象進行進一步的分析。但是Assembly.LoadFile()方法會以執行為目的把程序集加載到程序中,因此它對於被加載的程序集文件有嚴格的要求,比如,如果被程序集所依賴的程序集不存在,那么LoadFile()會拋出異常,再比如,在.NET Core中加載.NET Framework的程序集,LoadFile()也會拋出異常。如果我們只想分析程序集,但是並不需要執行程序集,那么我們就需要一種單純地分析程序集文件的方式。

.NET Framework提供了Assembly.ReflectionOnlyLoad()來實現類似的效果,但是這個方法由於依賴於AppDomain,因此在.NET Core中不被支持。微軟曾經在實驗室項目中提出過一個在.NET Core中實現這個功能的System.Reflection.TypeLoader,但不知道什么原因,沒有在.NET Core的正式版中提供這個類。

我們知道,.NET程序集是PE格式的文件,.NET中提供了用來分析PE文件的類PEReader(位於System.Reflection.Metadata這個NuGet包中),因此我們可以用PEReader來分析程序集文件。

在PEReader中,我們可以通過TypeDefinitions獲取到程序集中的所有類,我們可以用GetMethods()獲取某個類中定義的所有方法。為了提升效率,TypeDefinitions、GetMethods()等成員獲得到的對象都是TypeDefinitionHandle、MethodDefinitionHandle等句柄類型的,這些對象只包含地址信息,並不包含類型的名字、方法的名字、方法的參數等詳細信息,要獲取這些信息,我們需要調用MetadataReader的GetTypeDefinition()、GetMethodDefinition()等方法來獲取。如下的代碼用來加載一個程序集,並且輸出程序集中所有的類型信息以及類型中定義的方法:

//Install-Package System.Reflection.Metadata
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;

string file = @"E:\Microsoft.AspNetCore.Components.Web.dll";
using FileStream fileStream = File.OpenRead(file);
using PEReader peReader = new PEReader(fileStream);
if(!peReader.HasMetadata)
{
    Console.WriteLine($"{file} doesn't contain CLI metadata.");
    return;
}
var mdReader = peReader.GetMetadataReader();
if (!mdReader.IsAssembly)
{
    Console.WriteLine($"{file} is not an assembly.");
    return;
}
foreach (var typeHandler in mdReader.TypeDefinitions)
{
    var typeDef = mdReader.GetTypeDefinition(typeHandler);
    string name = mdReader.GetString(typeDef.Name);
    string nameSpace = mdReader.GetString(typeDef.Namespace);
    Console.WriteLine($"***********{nameSpace}.{name}***********");
    foreach (var methodHandler in typeDef.GetMethods())
    {
        var methodDef = mdReader.GetMethodDefinition(methodHandler);
        Console.WriteLine(mdReader.GetString(methodDef.Name));
    }
}

使用PEReader的時候,我們需要先獲得XXXHandler,然后再調用MetadataReader獲取句柄的詳細信息,這樣做盡管性能比較高,但是代碼比較繁瑣,而且在實現某些高級操作的時候比較麻煩。比如,如果我們要獲取一個程序集的CustomAttribute信息,PEReader並沒有提供比較簡單的方法,需要我們對PE格式非常精通,才能編寫出來對應的代碼。
我們可以使用AsmResolver.DotNet這個第三方Nuget包來簡化程序集文件的讀取分析,它是對PEReader的一個高級封裝。如下的代碼用來加載一個程序集,輸出程序集的公司信息,並且輸出程序集中所有的類型信息以及類型中定義的方法:

string file = @"E:\Microsoft.AspNetCore.Components.Web.dll";
var moduleDef = AsmResolver.DotNet.ModuleDefinition.FromFile(file);//用的不是System.Reflection.Metadata命名空間下的ModuleDefinition類
var asmCompanyAttr = moduleDef.Assembly.CustomAttributes.FirstOrDefault(c => c.Constructor.DeclaringType.FullName == "System.Reflection.AssemblyCompanyAttribute");
var utf8Value = (Utf8String?)asmCompanyAttr.Signature.FixedArguments[0].Element;
var strValue = (string?)utf8Value;
Console.WriteLine($"company name:{strValue}");
foreach(var typeDef in moduleDef.GetAllTypes())
{
    string name = typeDef.Name;
    string nameSpace = typeDef.Namespace;
    Console.WriteLine($"***********{nameSpace}.{name}***********");
    foreach (var methodDef in typeDef.Methods)
    {
        Console.WriteLine(methodDef.Name);
    }
}

總之,如果我們需要分析一個程序集並且要運行其中的代碼,我們可以使用Assembly.LoadFile();如果我們不需要運行程序集,只是想分析程序集,那么使用PEReader是更好的選擇,當然我們也可以選擇對PEReader進行封裝的AsmResolver.DotNet這個NuGet包。本文作者楊中科在Zack.Commons這個開源項目中實現“判斷一個程序集是否是微軟開發的”這個功能的時候就用到了AsmResolver.DotNet,大家可以查看這個項目的GitHub代碼倉庫來查看源代碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM