【问题标题】:CLR IL-significance of square bracket on .locals init.locals init 上方括号的 CLR IL 意义
【发布时间】:2013-09-23 17:36:11
【问题描述】:

我正在尝试使用 .NET 中的反射和发射来生成动态程序集。我收到错误消息“公共语言运行时检测到无效程序”。我创建了另一个程序,它具有我想要使用硬编码类型的功能。我尝试编写的功能最终将使用动态类型,但我可以使用 ILDasm 来查看我需要生成的 IL。我正在将我生成的 IL 与编译器生成的 IL 进行比较。在一种方法的 .locals 初始化声明中,我看到编译器生成的代码中有一个额外的项目,

编译器生成:

.locals init ([0] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0000,
           [1] class [System.Core]System.Linq.Expressions.ParameterExpression[] CS$0$0001)

我的:

.locals init (class [System.Core]System.Linq.Expressions.ParameterExpression V_0,  
       class [System.Core]System.Linq.Expressions.ParameterExpression[] V_1)

我不明白编译器生成的代码中“[0]”和“[1]”的意义。谁能告诉我这是什么意思?

作为一个更普遍的问题,我可以毫不费力地跟踪大多数 ILDasm 输出。但是我经常遇到一个有问题的表达式。例如,在 ILDasm 的这一行中

callvirt   instance class [EntityFramework]System.Data.Entity.ModelConfiguration.EntityTypeConfiguration`1<!!0> [EntityFramework]System.Data.Entity.DbModelBuilder::Entity<class DynamicEdmxTrial.HardFooAsset>()

“!!0”可能指的是 Entity 的泛型类型,但我不确定,我想知道 ILDasm 输出是否有一个键可以向我解释其更晦涩的输出.

【问题讨论】:

  • “我收到一个错误,“公共语言运行时检测到一个无效程序。””您是否尝试过在您的程序集上运行 PEVerify?
  • 另外,您究竟是如何获得这些.locals 的?都来自 ILDasm?
  • “无效程序”我不是很在意。我想我已经找到了。两个 .local 声明都来自 ILDasm。它显然发现了 C# 代码和我的 Reflection.Emit 之间的一些差异。
  • 考虑到 ILDasm 输出包含变量名(在 IL 中没有表示),也许这些数字与调试信息有关?
  • 对于那些寻求关闭这类事情的人来说,无效程序的事情与“[0]”无关。我正在调用一个基本构造函数,它接受一个参数而不将一个参数压入堆栈。我已经改变了我需要调用哪个构造函数的想法,但忘记添加参数了。

标签: reflection.emit il ildasm


【解决方案1】:

该规范可免费获得here。这需要一点时间来适应,但一旦你弄清楚了结构,大部分细节都很容易找到。

!! 列在II.7.1 类型中:

Type ::=       | Description                             | Clause
  ‘!’ Int32    | Generic parameter in a type definition, | §II.9.1
               | accessed by index from 0                |
| ‘!!’ Int32   | Generic parameter in a method           | §II.9.2
               | definition, accessed by index from 0    |
...

换句话说,在 C# 调用的方法中 f&lt;T, U&gt;()!!0 将是 T!!1 将是 U

不过,[0] 是个好问题。该规范似乎没有解决它。 .locals 指令在II.15.4.1.3 .locals 指令中进行了描述,该指令将语法列为

MethodBodyItem ::= ...
 | .locals [ init ] ‘(’ LocalsSignature ‘)’
LocalsSignature ::= Local [ ‘,’ Local ]*
Local ::= Type [ Id ]

似乎没有任何东西允许[0] 出现,除非它是Type 的一部分,并且Type 也不允许任何以[ 开头的东西。我的猜测是,这是 Microsoft 实现特有的一个未记录的特性,旨在帮助人类读者看到位置 0 是局部变量 CS$0$0000,当生成的指令通过索引访问局部变量时。

用 ILAsm 进行实验表明这正是它的意思。以一个简单的 C# 程序为例:

static class Program {
    static void Main() {
        int i = 0, j = 1;
    }
}

编译然后反汇编它(csc test.cs &amp;&amp; ildasm /text test.exe &gt;test.il)显示:

....
.locals init (int32 V_0,
         int32 V_1)
IL_0000:  nop
IL_0001:  ldc.i4.0
IL_0002:  stloc.0
IL_0003:  ldc.i4.1
IL_0004:  stloc.1
IL_0005:  ret
....

修改.locals

.locals init ([0] int32 V_0, [0] int32 V_1)

给出一个有用的警告信息:

test.il(41) : warning : Local var slot 0 is in use

事实上,声明不同类型的变量,然后使用[2][1][0] 对它们重新排序,组装并立即反汇编结果,表明变量已重新排序。

【讨论】:

  • 我有规范,但正如你所说,它似乎没有解释“[0]”,所以我没有尝试“!!”。
  • 我是否正确 [0] 描述了变量所在的本地插槽,这对于 ldloc 和 stloc 指令很重要?只要 ldloc & stloc 指令加载并存储到正确的变量槽,[0] 和 [1] 很好但没必要?
  • @jrv 差不多。如果使用[0] 使其成为它已经隐式具有的显式槽,则它不会添加任何内容(除了清晰度)。
  • 我浏览了规范。在完整的语言规范(在后面)中,.locals 中的每个参数都是一个“sigArg”,它将由一个“paramAttr 类型”组成。一种类型的 paramAttr 是“'[' int32 ']'”,这正是我所看到的。我在 LocalBuilder 中看不到任何添加此参数属性的方法,但很高兴看到语言规范包含它。
  • @jrv 不错的发现。 VI.C.3 开头是“该语法提供了 Partition II 语法中未提供的许多易用特性,并支持一些不能跨实现移植的特性,因此不属于本标准的一部分。”尽管。至于当你使用 LocalBuilder 时,槽位将基于你添加本地人的顺序,所以只需按照你想要的顺序添加它们:)
猜你喜欢
  • 2018-06-15
  • 1970-01-01
  • 2012-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-23
相关资源
最近更新 更多