C# 为什么PEVerify不能识别有效代码?

C# 为什么PEVerify不能识别有效代码?,c#,reflection,cil,reflection.emit,C#,Reflection,Cil,Reflection.emit,我创建了一个简单的程序,它动态地生成GenericEmitExample1.dllassembly。此类组件定义了以下类型: public class Sample { public static string test() { int num = default(int); return num.ToString(); } } 以下是该程序的源代码: using System; using System.Reflection; usin

我创建了一个简单的程序,它动态地生成
GenericEmitExample1.dll
assembly。此类组件定义了以下类型:

public class Sample
{
    public static string test()
    {
        int num = default(int);
        return num.ToString();
    }
}
以下是该程序的源代码:

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
()。它有助于确定它们的MSIL代码和相关元数据是否满足类型安全要求。我决定测试它,在调用生成的程序集后,它显示以下错误消息:

[IL]:值类型方法上的错误:[GenericEmitExample1.dll:Sample::test][offset 0x00000008]Callvirt

验证GenericEmitExample1.dll时出现1个错误

这样的报道使我吃惊。以下是生成类型的
IL
代码:

.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代码
callvirt
constrated
前缀一起使用。文档()验证了这个技巧。以下是报价
III.2.1(前缀)对变量类型的值调用成员

受约束的操作码允许IL编译器以统一的方式调用虚拟函数,而与ptr是值类型还是引用类型无关。虽然它适用于thisType是泛型类型变量的情况,但约束前缀也适用于非泛型类型,并可以降低在隐藏值类型和引用类型之间的区别的语言中生成虚拟调用的复杂性

那么,PEVerify有什么问题?它是bug吗?

来自的第III.2.1节(该节讨论了
约束的
前缀)

可验证性

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

我认为您违反了“这相当于要求装箱的
thisType
必须是
method
所属类的子类”

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

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

我已通过将
callvirt
指令更改为:

gen.Emit(OpCodes.Callvirt, typeof(object).GetMethod(nameof(object.ToString), Type.EmptyTypes));
这证实了这一点


此外:

I.12.1.6.2.4调用方法

处理值类型上的静态方法与处理普通类上的静态方法没有什么不同:使用带有元数据标记的调用指令,将值类型指定为方法的类。非静态方法(即实例和虚拟方法)在值类型上受支持,但对它们进行了特殊处理。引用类型(而不是值类型)上的非静态方法需要作为该类实例的this指针。这对于引用类型是有意义的,因为它们具有标识,而This指针表示该标识。但是,值类型只有在装箱时才具有标识。为了解决这个问题,值类型的非静态方法上的this指针是值类型的byref参数,而不是普通的by-value参数

可以通过以下方式调用值类型上的非静态方法:

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

您直接在值类型上调用方法(而不是在其框上),因此应该使用
call

@canton7,这是错误的。您可以参考(参见注释部分-它注释了这种情况)编译示例类产生
IL_0000:ldc.i4.0 IL_0001:stloc.0//num IL_0002:ldloca.s 00//num IL_0004:call System.Int32.ToString IL_0009:ret
,它使用普通调用操作码。@ckuri,c编译器生成的代码可能不同于我自己的代码。这很正常。此外,您可以在评论中看到我的链接,DOCS允许生成不同的代码(如我的清单)@ LMtyytoon,当您评论时,我正在更新中,请参阅我的编辑。