• 运用Mono.Cecil 反射读取.NET程序集元数据


    运用Mono.Cecil 反射读取.NET程序集元数据

    CLR自带的反射机智和API可以很轻松的读取.NET程序集信息,但是不能对程序集进行修改。CLR提供的是只读的API,但是开源项目Mono.Cecil不仅仅可以读取.NET程序集的元数据,还可以进行修改。

    1 读取程序集的版本信息

    CLR定义的对象模型是以程序集为编译和部署单元。一个程序集下面可以有多个模块,模块下面再包含类型,类型中定义方法,属性和事件等。直接传入一个完整的字符串表示的程序集,MONO可以解析它的基本信息:

    var name = AssemblyNameReference.Parse ("Foo, version=2.0.0.0, culture=fr-FR");
    Assert.AreEqual ("Foo", name.Name);
    Assert.AreEqual (2, name.Version.Major);
    Assert.AreEqual (0, name.Version.Minor);
    Assert.AreEqual ("fr-FR", name.Culture);
     
     

    你可以用.NET Reflector载入程序集,拷贝红色方框所表示的内容,它是一个程序集的完整名称

    image

    2  创建程序集

    BLC自带的API不能新建一个程序集文件,必须用源代码,借助编译器来生成.NET平台的MSIL程序集文件。请看下面的代码

    var module = ModuleDefinition.CreateModule ("Test.dll", ModuleKind.Dll);
    Assert.AreEqual ("Test", module.Assembly.Name.Name);
    
    module = ModuleDefinition.CreateModule ("Test.exe", ModuleKind.Console);
    Assert.AreEqual ("Test", module.Assembly.Name.Name);
    module.Write (file);

    一句代码创建程序集,传入需要的属性,另一句代码保存创建的程序集对象。API背后完成了复杂的功能,把这二行代码生成的程序集用.NET Reflector载入,可以正常打开,仅仅是没有类型和方法定义。

    3  读取Attribute特性

    请先看下面的代码,比BCL自带的反射读取特性的代码要省略很多,几乎就是要什么,直接一个属性就可以获取到

    ModuleDefinition module=......
    var hamster = module.GetType ("Hamster");
    Assert.IsTrue (hamster.HasCustomAttributes);
    Assert.AreEqual (1, hamster.CustomAttributes.Count);
    var argument = attribute.ConstructorArguments [0];
     
     

    4 读取类型成员的元数据

    依然保持简洁的API,所需要的值都可以直接从属性中获取,下面的代码演示读取方法的元数据:

    ModuleDefinition module=......
    var type = module.Types [1];
    Assert.AreEqual ("Foo", type.Name);
    Assert.AreEqual (2, type.Methods.Count);
    var method = type.GetMethod ("Bar");
    Assert.AreEqual ("Bar", method.Name);
    Assert.IsTrue (method.IsAbstract);
    Assert.IsNotNull (method.ReturnType);
    Assert.AreEqual (1, method.Parameters.Count);
    var parameter = method.Parameters [0];
    Assert.AreEqual ("a", parameter.Name);
    Assert.AreEqual ("System.Int32", parameter.ParameterType.FullName);
     
     

    下面的代码演示读取字段的元数据:

    var type = module.Types [1];
    Assert.AreEqual ("Foo", type.Name);
    Assert.AreEqual (1, type.Fields.Count);
    
    var field = type.Fields [0];
    Assert.AreEqual ("bar", field.Name);
    Assert.AreEqual (1, field.MetadataToken.RID);
    Assert.IsNotNull (field.FieldType);
    Assert.AreEqual ("Bar", field.FieldType.FullName);
    Assert.AreEqual (TokenType.Field, field.MetadataToken.TokenType);
    Assert.IsFalse (field.HasConstant);
    Assert.IsNull (field.Constant);
     
     

    5 创建方法定义,对现有的类型或方法进行代码注入或修改的起点

    显然,这个用途是Mono.Cecil流行起来的主要原因之一,可以进行代码注入(Code Injection):

    先来看,如何创建一个方法体

    var object_ref = new TypeReference ("System", "Object", null, null, false);
    var method = new MethodDefinition ("foo", MethodAttributes.Static, object_ref);
    var body = new MethodBody (method);
    
    var il = body.GetILProcessor ();
    
    var first = il.Create (OpCodes.Nop);
    var second = il.Create (OpCodes.Nop);
    var third = il.Create (OpCodes.Nop);
    
    body.Instructions.Add (first);
    body.Instructions.Add (third);
    
    body.Instructions.Insert (1, second);
     

    把这段代码翻译成C#语言的方法定义,它应该是这样的:

    static object  foo ()
    {
        
    }

    三个空指令,在C#中相当于空语句,对应的MSIL语句是NOP指令。

    把这里延伸一下,我获取到现有类型的方法引用,调用方法把它的MSIL指令全部清空,再插入我需要的指令,做到代码注入:

    body.Instructions.Clear();
    body.Instructions.Add (myInstruction);
    //or insert     
    body.Instructions.Insert (2, myInstruction);
     

    在我的前一篇文章中,讲解如何应用这个技巧,做一个字符串混淆程序的模型,参考下面的地址
    字符串混淆技术应用 设计一个字符串混淆程序 可混淆.NET程序集中的字符串

    6  类型推断

    我们在写C#程序时,当使用usiing指令后,可以不用全名称来表示一个类型,比如下面的代码

    Console.WriteLine()
    或是
    System.Console.WriteLine()
     

    从.NET Reflector中加载程序集,MSIL代码中看到的类型或是方法调用,都是以全名称表示的。编译器在编译时,自动帮我们将命名空间和类型连接到一起,编译成MSIL代码中。

     
    image 
     

    所以,参考下面的代码,

    Assert.AreEqual ("System.String System.String::Empty", string_empty.FullName);
    var definition = string_empty.Resolve ();
    Assert.AreEqual ("System.String System.String::Empty", definition.FullName);
     
    var definition = string_length.Resolve ();
    Assert.AreEqual ("get_Length", definition.Name);
    Assert.AreEqual ("System.String", definition.DeclaringType.FullName);
    
    var definition = list_add.Resolve ();
    Assert.AreEqual ("System.Void System.Collections.Generic.List`1::Add(T)", definition.FullName);
    
    var definition = try_get_value.Resolve ();
    Assert.AreEqual ("System.Boolean System.Collections.Generic.Dictionary`2::TryGetValue(TKey,TValue&)", definition.FullName);
     

    当有返型参数时,它的名字看起来有点怪怪的。旧的版本的.NET Reflector在反编译成BCL类型时,没有处理到这一点,导致反编译后的源代码不能编译,新版本已经解决这个问题。.NET Reflector自从商业化之后,功能精进不休,配合.NET平台的进步,实为.NET开发中的必备工具之一。

    .NET Reflector 8.1.0.35  : .NET Reflector

     
     
  • 相关阅读:
    给Clouderamanager集群里安装基于Hive的大数据实时分析查询引擎工具Impala步骤(图文详解)
    给Clouderamanager集群里安装可视化分析利器工具Hue步骤(图文详解)
    appium----【已解决】【Mac】ANDROID_HOME的环境变量配置
    appium---【已解决】【Mac】如何查看java的安装路径及JAVA_HOME环境变量的配置
    appium----【已解决】【Mac】环境配置提示“Xcode Command Line Tools are NOT installed!"
    appium----【已解决】【Mac】安装sudo npm install -g appium-doctor总是提示“Error: EACCES: permission denied........”
    appium---【已解决】【Mac】from appium import webdriver报错提示“Unresolved import webdriver”
    Jmeter----A接口response中body的某一个参数传递给B接口request的body中使用(参数的传递)
    Jmeter----创建第一个接口测试流程
    Jmeter----【Mac电脑】环境配置与打开Jmeter界面
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3172666.html
Copyright © 2020-2023  润新知