【问题标题】:Using Br_S OpCode to point to next instruction using Reflection.Emit.Label使用 Br_S OpCode 使用 Reflection.Emit.Label 指向下一条指令
【发布时间】:2011-07-20 18:42:46
【问题描述】:

我正在尝试解析 IL 以发出一个方法。我在 string[] 中获得了一个方法的 IL 代码,其中每个字符串都是一条 IL 指令。我正在循环这个数组并使用 ILGenerator 添加操作码:

        foreach (string ins in instructions) //string representations of IL          
        {
            string opCode = ins.Split(':').ElementAt(1);

            // other conditions omitted

            if (opCode.Contains("br.s"))
            {
                Label targetInstruction = ilGenerator.DefineLabel();

                ilGenerator.MarkLabel(targetInstruction);

                ilGenerator.Emit(OpCodes.Br_S, targetInstruction); 
            }

这是我需要重现的 IL:

Source IL:
IL_0000: nop
IL_0001: ldstr "Hello, World!"
IL_0006: stloc.0
IL_0007: br.s IL_0009
IL_0009: ldloc.0
IL_000a: ret

这是我得到的输出:

Target IL:
IL_0000: nop
IL_0001: ldstr "Hello, World!"
IL_0006: stloc.0
IL_0007: br.s IL_0007   // this is wrong -- needs to point to IL_0009
IL_0009: ldloc.0
IL_000a: ret

如您所见, br.s 调用指向自身,这当然会导致无限循环。如何让它指向源代码中的以下指令?这与使用 Reflection.Emit.Label 有关,但我不确定它是如何工作的。

EDIT顺便说一下上面看到的IL就是针对这个简单的方法,

    public string HelloWorld()
    {
            return "Hello, World!";
    }

【问题讨论】:

  • 这里肯定有什么我没看到的东西……为什么要跳转到下一条指令?这不只是一个稍微昂贵的无操作吗?另外,如果您不希望它成为目标,为什么要将分支指令标记为标签目标?
  • 在 Emit() 调用之后移动 MarkLabel() 调用。或者只是完全省略分支,它什么都不做。
  • @Sean 我想你很快就会意识到你已经不知所措了。如果您计划实现一个完整的 IL 汇编器,那么对于分支指令,您将不得不比“string.Contains”做得更好。您基本上需要创建一个标签,并为它找到正确的位置。并非所有分支都“用于下一行”。
  • @Lasse,我知道,我只是湿了脚......
  • @Sean 然后 IL 是让他们湿透的好方法,并了解 .NET 程序的底层发生了什么:)

标签: c# reflection reflection.emit il opcode


【解决方案1】:

这段代码:

ilGenerator.MarkLabel(targetInstruction);
ilGenerator.Emit(OpCodes.Br_S, targetInstruction); 

清楚地说“在此处标记标签”,然后在标记标签处添加说明

如果这不是你想要的,你为什么要这样做?

MarkLabel 标记当前位置,即你输出的next指令的位置,作为标签的目标。

在这种情况下,要获得“你想要的”,只需将这两行反转,在标记标签之前输出分支指令。

我把“你想要的”放在引号里,因为我不明白那个分支指令的意义。机器会很高兴自己“移动”到下一条指令,不需要添加“分支到下一条指令”指令。

【讨论】:

    【解决方案2】:

    您需要在发出要跳转的操作码之前立即调用ilGenerator.MarkLabel()。您将它放在分支之前,这意味着它将分支到自身,从而有效地创建无限循环。但正如 Lasse 所说,如果你正确地发出 IL,它将是一个空操作。

    有趣的是,整个方法很容易是:

    ldstr "Hello, World!"
    ret
    

    任何编译器发出的原始代码都需要对其作者进行 LART。

    【讨论】:

    • 我明白了。我不清楚什么是无操作?为什么 IL 包含简单的 HelloWorld() 方法的那一行?似乎它应该直接从 IL_0006 到 IL_0009。
    • 无操作 = 无操作。这是一段不会完成任何事情的代码。为什么 IL 包含本地或分支完全超出了我的范围;两者都不是必需的。
    • “nop”或no-op,意思是“无操作”,它基本上是一个伪指令,一个填充指令,什么都不做。机器会跳过它。它通常是因为对齐而添加的,一些优化可能已经重新排列了一些代码并制造了“洞”,或类似的。
    • 好的 - 谢谢。你认为我用来将方法反汇编成 IL 的 3rd 方库引入了那些不必要的 no-op 代码吗?
    • @Sean:不,它们是由编译器编写的。
    【解决方案3】:

    在您的 ILGenerator 上调用 MarkLabel() 方法可用于标记分支点,然后通过 Emit(OpCodes.Br_S, [label]) 分支到该点。

    我假设您用于监视 Hello World 方法的 IL 指令的任何 API 都是在调试模式下完成的,因为添加了 nop 和分支指令以帮助确保调试器涵盖每个步骤。

    在 DynamicMethod 中,不需要附加调试器,并且根据平台,在发布模式下使用额外指令运行它可能会导致 InvalidProgramException。

    “Hello World”方法只需要 2 条指令(而且非常直观)

    Ldstr "Hello, World!"
    Ret
    

    【讨论】:

      猜你喜欢
      • 2014-12-23
      • 2019-01-20
      • 1970-01-01
      • 2020-03-02
      • 1970-01-01
      • 2016-09-26
      • 2011-12-04
      • 2021-06-21
      • 1970-01-01
      相关资源
      最近更新 更多