【问题标题】:prevent DynamicMethod VerificationException - operation could destabilize the runtime防止 DynamicMethod VerificationException - 操作可能会破坏运行时
【发布时间】:2012-07-27 01:59:52
【问题描述】:

我正在使用 IL 生成创建一个简单的反序列化器方法,该方法从 Lucene 文档中取出字符串并设置引用类型对象 (POCO) 的属性或字段。

每当我尝试运行生成的方法时,都会收到 VerificationException 错误。关于此错误还有其他问题,其中一些与 DynamicMethods 有关,但据我所知,我遇到的问题有所不同。

operation-could-destablize-the-runtime-and-dynamicmethod-with-value-types

msil-operation-could-destabilize-the-runtime-exception

虽然这个方法将来会变得更复杂,但我现在只是在做字符串赋值。我试图动态创建的方法看起来就像这样:

public static PocoObject ExampleMethod(Document document)
{
    var poco = new PocoObject();
    poco.ID = document.Get("ID");
    poco.DisplayText = document.Get("DisplayText");

    poco.PropId = document.Get("PropId");
    return poco;
}

PocoObject 看起来像这样:

class PocoObject
{
    public string ID;
    public string DisplayText;

    public string PropId { get; set; }

}

我尝试完全复制从编译时代码生成的 IL(甚至是不必要的位),它看起来像这样:

.method public instance object  'Deserializebe6d500b-d35f-4f7a-a9b3-88f6bca5fb93'(class [Lucene.Net]Lucene.Net.Documents.Document A_1) cil managed
{
  // Code size       65 (0x41)
  .maxstack  4
  IL_0000:  nop
  IL_0001:  newobj     instance void [LukeMapperTest]LukeMapperTest.PocoObject::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  ldarg.0
  IL_0009:  ldstr      "ID"
  IL_000e:  callvirt   instance string [Lucene.Net]Lucene.Net.Documents.Document::Get(string)
  IL_0013:  stfld      string [LukeMapperTest]LukeMapperTest.PocoObject::ID
  IL_0018:  ldloc.0
  IL_0019:  ldarg.0
  IL_001a:  ldstr      "DisplayText"
  IL_001f:  callvirt   instance string [Lucene.Net]Lucene.Net.Documents.Document::Get(string)
  IL_0024:  stfld      string [LukeMapperTest]LukeMapperTest.PocoObject::DisplayText
  IL_0029:  ldloc.0
  IL_002a:  ldarg.0
  IL_002b:  ldstr      "PropId"
  IL_0030:  callvirt   instance string [Lucene.Net]Lucene.Net.Documents.Document::Get(string)
  IL_0035:  callvirt   instance void [LukeMapperTest]LukeMapperTest.PocoObject::set_PropId(string)
  IL_003a:  nop
  IL_003b:  ldloc.0
  IL_003c:  stloc.1
  IL_003d:  br.s       IL_003f
  IL_003f:  ldloc.1
  IL_0040:  ret
} // end of method Test::'Deserializebe6d500b-d35f-4f7a-a9b3-88f6bca5fb93'

我已经设法将 DynamicMethod 保存到磁盘上的程序集,检查它,这正是它带来的。逐行它与编译时方法IL相同。

但是,在执行动态方法时,会抛出上述错误。有谁知道我该如何解决这个问题?

注意:如果有人想仔细查看,我在 GitHub 上有源代码。 IL 生成代码在 LukeMapper.cs:GetDumbDeserializer() 中(第 133 行)

LukeMapper GitHub repo

感谢所有帮助!谢谢!


编辑:所以我简化了 IL 生成代码,基本上如下:

private static Func<Document, object> GetDumbDeserializer(Type type)
{
    var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), typeof(object), new[] { typeof(Document) }, true);

    var il = dm.GetILGenerator();

    var ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
    il.Emit(OpCodes.Nop);
    il.DeclareLocal(type);
    il.Emit(OpCodes.Newobj, ctor);
    il.Emit(OpCodes.Stloc_0);
    Label returnLabel = il.DefineLabel();

    //stack is [target]

    var getFieldValue = typeof(Document).GetMethod("Get", BindingFlags.Instance | BindingFlags.Public);

    foreach (var setter in settableProperties)
    {
        il.Emit(OpCodes.Ldloc_0);// [target]
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldstr, setter.Name);
        il.Emit(OpCodes.Callvirt, getFieldValue);
        il.Emit(OpCodes.Stfld, setter.Field);
    }

    il.Emit(OpCodes.Nop);
    il.Emit(OpCodes.Ldloc_0);
    il.Emit(OpCodes.Stloc_1); // stack is empty
    il.Emit(OpCodes.Br_S, returnLabel);
    il.MarkLabel(returnLabel);
    il.Emit(OpCodes.Ldloc_1); // stack is [rval]
    il.Emit(OpCodes.Ret);
    return (Func<Document, object>)dm.CreateDelegate(typeof(Func<Document, object>));
}

我根据 kvbs 的评论添加了 il.DeclareLocal(type)(其中类型是 PocoObject),但我不确定我是否将其放在正确的位置(或者是否重要)。

【问题讨论】:

  • PEVerify 对您生成的代码有何评论?
  • @svick:所以我以前从未使用过 PEVerify,但将其指向已保存的 .dll 时注册了一个错误:方法不可见。这是一个相关的错误吗?另外,svick:你是反对者吗?如果是这样,你能告诉我我可以做些什么不同的事情吗?
  • @kvb:我相信是这样的......真的没有任何本地人。字符串(lucene doc 字段名称)被呈现为常量,所以唯一的本地变量是那些和返回的对象,它被正确加载(我相信)
  • @LelandRichardson 您发布的 CIL 中肯定有本地人:stlocldloc 指令使用它们。
  • 注意:关于 PEVerify 错误:我认为这无关紧要,因为这是在查看 saved 程序集与动态方法,后者是使用“restructedSkipVisibility”参数设置为true...

标签: c# cil dynamicmethod


【解决方案1】:

最后是乱码,用Stfld调用属性;我不知道最终的东西来自哪里,但我相信来自ExampleMethod - 我认为这可能来自你以前的构建。特别是,您还没有定义第二个本地,所以Ldloc_1 没有意义;但这里绝对不需要任何标签/分支。这是我所拥有的,有效的(注意我不知道你的 settableProperties 是什么,所以我只是使用 FieldInfo / PropertyInfo 做到了:

    var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), typeof(object), new[] { typeof(Document) }, true);

    var il = dm.GetILGenerator();

    var ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
    il.DeclareLocal(type);
    il.Emit(OpCodes.Newobj, ctor);
    il.Emit(OpCodes.Stloc_0);

    var getFieldValue = typeof(Document).GetMethod("Get", BindingFlags.Instance | BindingFlags.Public);

    var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
    foreach (var field in fields)
    {
        il.Emit(OpCodes.Ldloc_0);// [target]
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldstr, field.Name);
        il.Emit(OpCodes.Callvirt, getFieldValue);
        il.Emit(OpCodes.Stfld, field);
    }
    var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
    foreach (var prop in props)
    {
        var setter = prop.GetSetMethod();
        if (setter == null) continue;
        il.Emit(OpCodes.Ldloc_0);// [target]
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldstr, prop.Name);
        il.Emit(OpCodes.Callvirt, getFieldValue);
        il.EmitCall(OpCodes.Callvirt, setter, null);
    }

    il.Emit(OpCodes.Ldloc_0);
    il.Emit(OpCodes.Ret);
    return (Func<Document, object>)dm.CreateDelegate(typeof(Func<Document, object>));

出于比较目的,这是我在反射器中查看ExampleMethod 时得到的结果(在发布版本等中):

.method public hidebysig static class PocoObject ExampleMethod(class Document document) cil managed
{
    .maxstack 3
    .locals init (
        [0] class PocoObject poco)
    L_0000: newobj instance void PocoObject::.ctor()
    L_0005: stloc.0 
    L_0006: ldloc.0 
    L_0007: ldarg.0 
    L_0008: ldstr "ID"
    L_000d: callvirt instance string Document::Get(string)
    L_0012: stfld string PocoObject::ID
    L_0017: ldloc.0 
    L_0018: ldarg.0 
    L_0019: ldstr "DisplayText"
    L_001e: callvirt instance string Document::Get(string)
    L_0023: stfld string PocoObject::DisplayText
    L_0028: ldloc.0 
    L_0029: ldarg.0 
    L_002a: ldstr "PropId"
    L_002f: callvirt instance string Document::Get(string)
    L_0034: callvirt instance void PocoObject::set_PropId(string)
    L_0039: ldloc.0 
    L_003a: ret 
}

注意事项:

  • 没有标签/分支(我不知道这是从哪里来的,但那不是你发布的)
  • 它定义了一个本地
  • 没有nop

【讨论】:

  • 谢谢马克!这行得通......另外 - 我把nops 和最后的分支放在那里只是因为我很沮丧并且想让 IL 和我在 ildasm 中找到的完全一样。 - 不知道它们为什么在那里,但它们看起来毫无用处(更改为发布版本会摆脱它们......)。最后,我遇到的问题是Ldloc.1,它是从我不能完全理解的 sam 代码中复制的。再次感谢。
  • 哦,还有-我没有在属性上使用stfld...我只是在我发布的“简化”中取出了那部分代码。干杯。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-22
相关资源
最近更新 更多