lundi 18 novembre 2019

Why does PEVerify not recognize valid code?

I created simple program, which dynamically generates GenericEmitExample1.dll assembly. Such assembly defines following type:

public class Sample
{
    public static string test()
    {
        int num = default(int);
        return num.ToString();
    }
}

Here is source code of such program:

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");
    }
}

There is builtin tool, named PEVerify (https://docs.microsoft.com/en-us/dotnet/framework/tools/peverify-exe-peverify-tool). It helps to determine whether their MSIL code and associated metadata meet type safety requirements. I decided to test it, after its calling on generated assembly it shows following error message:

[IL]: Error: [GenericEmitExample1.dll : Sample::test][offset 0x00000008] Callvirt on a value type method.

1 Error(s) Verifying GenericEmitExample1.dll

Such report surprised me. Here is IL code of generated type:

.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

I don't see any forbiden tricks/invalid IL code. callvirt was used with constrained prefix. Documentation (https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.constrained?view=netframework-4.8) valids this trick. Here is quote:

The constrained opcode allows IL compilers to make a call to a virtual function in a uniform way independent of whether ptr is a value type or a reference type. Although it is intended for the case where thisType is a generic type variable, the constrained prefix also works for nongeneric types and can reduce the complexity of generating virtual calls in languages that hide the distinction between value types and reference types.

So, what's the problem with PEVerify? Is it bug?





Aucun commentaire:

Enregistrer un commentaire