首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么PEVerify不识别有效代码?

为什么PEVerify不识别有效代码?
EN

Stack Overflow用户
提问于 2019-11-18 12:19:46
回答 1查看 86关注 0票数 1

我创建了简单的程序,它动态地生成GenericEmitExample1.dll程序集。这类程序集定义了以下类型:

代码语言:javascript
复制
public class Sample
{
    public static string test()
    {
        int num = default(int);
        return num.ToString();
    }
}

以下是此类程序的源代码:

代码语言:javascript
复制
using System;
using System.Reflection;
using System.Reflection.Emit;

public class Example
{
    public static void Main()
    {
        AppDomain myDomain = AppDomain.CurrentDomain;
        AssemblyName myAsmName = new AssemblyName("GenericEmitExample1");
        AssemblyBuilder myAssembly = myDomain.DefineDynamicAssembly(myAsmName, AssemblyBuilderAccess.RunAndSave);
        ModuleBuilder myModule =
            myAssembly.DefineDynamicModule(myAsmName.Name,
               myAsmName.Name + ".dll");
        TypeBuilder myType = myModule.DefineType("Sample", TypeAttributes.Public);
        var test_method = myType.DefineMethod("test", MethodAttributes.Public | MethodAttributes.Static, typeof(String), Type.EmptyTypes);
        var gen = test_method.GetILGenerator();
        var local = gen.DeclareLocal(typeof(int));
        gen.Emit(OpCodes.Ldloca, local);
        gen.Emit(OpCodes.Constrained, typeof(int));
        gen.Emit(OpCodes.Callvirt, typeof(int).GetMethod(nameof(int.ToString), Type.EmptyTypes));
        gen.Emit(OpCodes.Ret);
        myType.CreateType();
        myAssembly.Save(myAsmName.Name + ".dll");
    }
}

有内置工具,名为PEVerify (https://learn.microsoft.com/en-us/dotnet/framework/tools/peverify-exe-peverify-tool)。它有助于确定它们的MSIL代码和相关元数据是否满足类型安全要求。我决定测试它,在它对生成的程序集进行调用之后,它显示了以下错误消息:

IL: Error: GenericEmitExample1.dll : Sample::test Callvirt on value type方法。 1错误验证GenericEmitExample1.dll

这样的报告令我吃惊。下面是生成类型的IL代码:

代码语言:javascript
复制
.class public auto ansi Sample
    extends [mscorlib]System.Object
{
    // Methods
    .method public static 
        string test () cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 14 (0xe)
        .maxstack 1
        .locals init (
            [0] int32
        )

        IL_0000: ldloca.s 0
        IL_0002: constrained. [mscorlib]System.Int32
        IL_0008: callvirt instance string [mscorlib]System.Int32::ToString()
        IL_000d: ret
    } // end of method Sample::test

    .method public specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x206c
        // Code size 7 (0x7)
        .maxstack 2

        IL_0000: ldarg.0
        IL_0001: call instance void [mscorlib]System.Object::.ctor()
        IL_0006: ret
    } // end of method Sample::.ctor

} // end of class Sample

我没有看到任何禁止的诡计/无效的IL码。callvirtconstrained前缀一起使用。文档(https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.constrained?view=netframework-4.8)验证了这一技巧。这是III.2.1 constrained. – (prefix) invoke a member on a value of a variable type的报价

受约束的操作码允许IL编译器以统一的方式调用虚拟函数,而不依赖于ptr是值类型还是引用类型。虽然thisType是泛型类型变量,但约束前缀也适用于非通用类型,并且可以减少在语言中生成虚拟调用的复杂性,这些语言隐藏了值类型和引用类型之间的区别。

那么,PEVerify有什么问题呢?是虫子吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-11-18 12:29:44

来自ECMA-335的第三.2.1节(讨论constrained前缀)

Verifiability: ptr参数将是指向thisType的托管指针(&)。此外,如上所述,callvirt指令的所有正常验证规则都适用于ptr转换之后。这相当于要求装箱thisType必须是method所属类的子类。

我认为这与“这相当于要求一个装箱的thisType必须是method所属类的子类”是不对的。

在您的例子中,methodInt32::ToString(),而不是Object::ToString(),因此属于int。但是,装箱int不是int的子类。

为了在这里使用受约束的虚拟调用,您必须调用Object::ToString(),而不是Int32::ToString()

我通过将callvirt指令更改为:

代码语言:javascript
复制
gen.Emit(OpCodes.Callvirt, typeof(object).GetMethod(nameof(object.ToString), Type.EmptyTypes));

这证明了。

此外:

I.12.1.6.2.4调用方法 值类型上的静态方法与普通类上的静态方法没有什么不同:使用带有元数据令牌的调用指令,将值类型指定为方法的类。值类型支持非静态方法(即实例方法和虚拟方法),但对它们给予特殊处理。引用类型(而不是值类型)上的非静态方法需要一个这个指针,它是该类的一个实例。这对于引用类型来说是有意义的,因为它们具有标识,而--此指针表示该标识。但是,值类型只有在装箱时才具有标识。为了解决这个问题,值类型的非静态方法上的 this 指针是值类型的byref参数,而不是普通的按值参数。 可以通过以下方式调用值类型上的非静态方法:

  • 对于值类型的未装箱实例,确切的类型是静态已知的。call指令可用于调用函数,将实例的地址作为第一个参数( this指针)传递。与调用指令一起使用的元数据令牌应该将值类型本身指定为方法的类。
  • 给定一个值类型的装箱实例,有三种情况需要考虑:
    • 实例或在值类型本身上引入的虚拟方法:打开实例并直接调用方法,使用值类型作为方法的类。
    • 从基类继承的虚拟方法:使用callvirt指令,并酌情在System.ObjectSystem.ValueTypeSystem.Enum类上指定方法。
    • 由值类型实现的接口上的虚拟方法:使用callvirt指令并在接口类型上指定方法。

您直接在值类型上调用一个方法(而不是在它的一个框上),所以您应该使用call

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58914430

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档