【问题标题】:Understanding CIL & the workings of ldelem.ref了解 CIL 和 ldelem.ref 的工作原理
【发布时间】:2018-12-27 23:59:31
【问题描述】:

我想解释一下ldelem.ref 的作用。到目前为止,我已经将 index 处的元素作为 O 加载到堆栈的顶部。

什么是索引?而且我认为 O 类型意味着对象的类型将保持不变,例如,如果它是一个字符串,它将保持一个字符串。

我在下面有一个我正在处理的代码的示例,我非常感谢您的理解。我评论了我相信我知道的。 所以在这种情况下是什么

.locals init (
    string V_0,
    bool V_1,
    string V_2,
    bool V_3,
    string V_4,
    string V_5,
    string V_6)              // Declared 6 variables

.try 
{
    IL_0000:  nop
    IL_0001:  nop                   // Does nothing - Debug build
    IL_0002:  ldarg.0               // Loads Argument 0 into memory/stack
    IL_0003:  ldc.i4.0              // Push Constant Value 0 into memory [Possibly from a variable]
    IL_0004:  ldelem.ref            // Loads element at index onto the top of the stack as an O
    IL_0005:  stloc.0               // Pop value from stack into local Variable 0
    IL_0006:  ldloc.0               // Load local variable 0 onto stack
    IL_0007:  ldstr      "del"      // Loads string "del" in to top of stack
    IL_000c:  call       bool [mscorlib]System.String::op_Equality(string, string)  // Compares strings to see if they are equal
    IL_0011:  stloc.1               // Pop value from stack into local variable 1
    IL_0012:  ldloc.1               // Load local variable 1 onto the stack
    IL_0013:  brfalse.s  IL_004e    // If variable 1 is true keep going else jump to IL_004e

ldelem.ref 在这里做什么? op_Equality 是否将字符串“del”与变量 0 的内容进行比较? 我认为在调用完成后,操作的布尔值然后存储在堆栈的顶部,stloc.1 弹出布尔值并将其存储在变量 1 中,然后ldloc.1 将该变量加载到stack 和 brfalse.s 检查 Bool 值,如果 false “跳转”到 IL_004e,是这样吗?

【问题讨论】:

  • 什么是索引?它是0,因为它被IL_0003: ldc.i4.0放在堆栈上。
  • @PetSerAl 感谢您澄清这一点。这个“元素”是从哪里拉出来的?
  • 来自此指令放置在堆栈上的数组:IL_0002: ldarg.0.
  • @PetSerAl 我们怎么知道 ldarg.0 是一个数组?这个数组是从哪里来的?我们怎么知道它的内容?
  • 我们怎么知道 ldarg.0 是一个数组? 我们不知道。要知道您需要显示方法签名。 这个数组来自哪里?来自第一个方法参数。

标签: .net .net-assembly disassembly cil


【解决方案1】:

ldelem.ref 将对数组元素的引用推入堆栈。这意味着它会复制它而不是实际存储的引用。阅读System.Reflection.Emit documentation 以了解更多信息可能会有所帮助。

另一件可能有助于理解的事情是,每条 MSIL 指令都需要堆栈中的 N 个值,其中 N 由所使用的特定指令确定(除其他外)。然后可以将其用于在心理上对事物进行分组以便于理解。

IL_0002:  ldarg.0               
IL_0003:  ldc.i4.0              
IL_0004:  ldelem.ref 

ldelem.ref 要求堆栈按顺序具有: 数组引用,该数组的索引。它将弹出这些值,然后将引用推送到堆栈上。堆栈现在包含一个东西。

IL_0005:  stloc.0 

堆栈上的唯一值现在被弹出并放入本地存储。堆栈为空。

IL_0006:  ldloc.0               
IL_0007:  ldstr      "del"      
IL_000c:  call       bool [mscorlib]System.String::op_Equality(string, string)  

调用指令将在堆栈中弹出与参数一样多的项目(包括实例,如果调用的方法是实例方法)。在这种情况下,它需要两个字符串参数,并将弹出局部变量的内容以及文字字符串“del”。静态方法 op_Equality 返回一个布尔值,因此它将被压入堆栈。堆栈现在包含一个东西。

IL_0011:  stloc.1               

堆栈上的唯一值现在被弹出并放入本地存储。堆栈为空。

IL_0012:  ldloc.1               
IL_0013:  brfalse.s  IL_004e    

然后加载该值并应用分支逻辑。

在 C# 中,MSIL 执行的操作等同于以下内容:

if (array[0] == "del")

MSIL 中的一切都是一种平衡行为。由于这是一个调试版本,与优化版本相比,可能会有更多的 nop 指令和更多的本地使用(这对您有好处,因为您可以更轻松地进行操作)。

【讨论】:

  • 有没有办法找出该阵列上的内容?是 Main 传递的 String[] 吗?很棒的解释,这真的帮助我理解了它。
  • 就像其他编程语言一样,假设您正在编写正在执行的 MSIL,您可以编写任何您想要的东西。如果这是来自您无法控制的程序集的代码,您必须找到一种方法将代码注入其中(或使用您的更改运行重新编译的版本),但这完全是一个不同的问题。
  • 我现在明白了一个数组是通过 ldarg.0 传递到堆栈的,数组 ldarg.0 指的是哪里?我想这将是一个更好的方式来澄清我在问什么。
  • 顺便说一句,ldarg.0 很特别。对于实例方法,它指的是方法正在其中执行的实例。对于静态方法,它指的是方法的第一个参数。因为我们将第 0 个参数视为一个数组,所以可以肯定地说您在一个静态方法中。至于它来自哪里,它来自谁调用了该方法,理论上可以在任何地方。
  • 在我的情况下,由于方法是 main ,是否可以安全地假设所讨论的数组是传递给程序的参数数组?
猜你喜欢
  • 2016-02-20
  • 2012-12-11
  • 2011-02-18
  • 2018-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多