关于手工编码IL问题的基础上拆卸简单的C#代码 [英] Questions about hand coded IL based on disassembled simple C# code

查看:159
本文介绍了关于手工编码IL问题的基础上拆卸简单的C#代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚开始看IL一点,我很好奇,如果我尝试(如下图所示),从编译器的输出有任何意外的副作用去除多余的代码。



一个关于结果的情侣quesiton的:




  1. 什么是NOP操作都必须在原有的目的

  2. 什么是br.s在在原来的方法是什么?

  3. 结束,目的是重新编写的版本,以任何方式不当?



原来的C#代码:

 类节目{
公共静态INT的Main(){
返回添加(1,2);
}
公共静态INT添加(INT A,INT B){
返回A + B;
}
}



编译时 CSC.EXE 程序Ildasm.exe 拆卸它(原文):

 。方法公开hidebysig静态INT32的Main()CIL管理
{
.entrypoint
.maxstack 2
.locals的init(INT32 V_0)
IL_0000 :NOP
IL_0001:ldc.i4.1
IL_0002:ldc.i4.2
IL_0003:调用INT32程序::加入(INT32,INT32)
IL_0008:stloc.0
IL_0009:br.s IL_000b
IL_000b:ldloc.0
IL_000c:RET
}
。方法公开hidebysig静态INT32添加(INT32一,
INT32二)CIL管理
{
.maxstack 2
.locals的init(INT32 V_0)
IL_0000:NOP
IL_0001:ldarg.0
IL_0002: ldarg.1
IL_0003:加
IL_0004:stloc.0
IL_0005:br.s IL_0007
IL_0007:ldloc.0
IL_0008:RET
}

重新编写(产生相同的输出):

 。方法公开hidebysig静态INT32的Main()CIL管理
{
.entrypoint
.maxstack 2
ldc.i4.1
ldc.i4.2
调用INT32程序::加入(INT32,INT32)

}

。方法公开hidebysig静态INT32添加(一个INT32,INT32 b)CIL管理
{
.maxstack 2
ldarg.0
ldarg.1


$} b $ b


解决方案

你看到所有的'多余'的代码是特定于调试构建(一般被优化掉的发布版本),并允许您执行的东西,你不能在发行版本通常做的。



调试生成的代码是这样的,它允许最大的独立性在调试会话过程中设置断点和更改/检查堆栈值。也IL代码应尽可能模仿更高级别的代码,使得每一个原因和效果可以被映射到较高级别代码行



现在到具体到你的问题:




什么是NOP操作都必须在原有的目的




NOP允许你在不属于执行的地方设置断点。对于如的方法,循环语句,或者如果开括号。在这些非可执行的指令,打破在开括号,可以修改/只有一个街区开始之前检查堆栈(但无可否认,你可以通过在块执行的第一线突破,而不是左括号很容易做到这一点,但它还可以让你在开幕括号破)

独立


什么是br.s在在方法结束的宗旨原?




综观原代码,你会发现它是毫无意义的跳到下一行,而不是让代码以自然秋天到下一行。但把它读作:



在调试版本,每当一个方法需要返回,跳转到方法的末尾,读取堆栈中,然后返回返回值值



所以它提供什么优势的调试?



如果您有多个return语句在你的代码,他们都将从栈中读取返回值前跳到代码的末尾。这允许你到底同一个地方(该方法的右大括号),在那里你可以把一个破发点,并修改返回值之前它实际上是返回给调用方法。相当有帮助的,不是吗?




是重新编写的版本,不当以任何方式?




有什么不当的在你的代码。事实上,如果你建立了原在释放模式和检查产生CIL,你会发现,它主要是和你一样的。


I just started looking at IL a bit and I'm curious if my attempt (shown below) to remove excess code from the output of the compiler had any unintended side effects.

A couple of quesiton about the results:

  1. What is the purpose of the nop operations in the original?
  2. What is the purpose of the br.s at the end of the methods in the original?
  3. Is the re-written version improper in any way?

Original C# Code:

class Program {
    public static int Main() {
        return Add(1, 2);
    }
    public static int Add(int a, int b) {
        return a + b;
    }
}

Compiled with csc.exe and disassembled it with ildasm.exe (Original):

  .method public hidebysig static int32  Main() cil managed
  {
    .entrypoint
    .maxstack  2
    .locals init (int32 V_0)
    IL_0000:  nop
    IL_0001:  ldc.i4.1
    IL_0002:  ldc.i4.2
    IL_0003:  call       int32 Program::Add(int32, int32)
    IL_0008:  stloc.0
    IL_0009:  br.s       IL_000b
    IL_000b:  ldloc.0
    IL_000c:  ret
  } 
  .method public hidebysig static int32  Add(int32 a,
                                             int32 b) cil managed
  {
    .maxstack  2
    .locals init (int32 V_0)
    IL_0000:  nop
    IL_0001:  ldarg.0
    IL_0002:  ldarg.1
    IL_0003:  add
    IL_0004:  stloc.0
    IL_0005:  br.s       IL_0007
    IL_0007:  ldloc.0
    IL_0008:  ret
  }

Re-written (produces identical output):

  .method public hidebysig static int32  Main() cil managed
  {
    .entrypoint
        .maxstack  2
    ldc.i4.1
    ldc.i4.2
    call int32 Program::Add(int32, int32)
    ret
  }

  .method public hidebysig static int32  Add(int32 a, int32 b) cil managed
  {
    .maxstack  2
      ldarg.0
      ldarg.1
    add
    ret
  }

解决方案

All the 'excess' code you see is specific to debug builds (and generally gets optimized away for release builds) and allows you to perform something that you can't ordinarily do in release build.

Debug build code is such that it allows maximum independence in setting up breakpoints and changing/examining stack values during a debug session. Also the IL code should mimic the higher level code as far as possible so that every 'cause' and 'effect' can be mapped to higher level code lines.

Now to be specific to your questions:

What is the purpose of the nop operations in the original?

NOP allows you to set breakpoints at places which are not 'executed'. For e.g. the opening braces of a method, loop or if statement. Among these non-executable instructions, breaking at the opening brace allows you to modify/examine stack just before a block starts (though admittedly you can achieve this very easily by breaking at the first line of execution of the block instead of opening brace but it still allows you independence of breaking at the opening brace)

What is the purpose of the br.s at the end of the methods in the original?

Looking at the original code, you might find it to be nonsensical to 'jump' to the next line instead of allowing the code to naturally 'fall' to the next line. But read it as:

"In a debug build, whenever a method needs to return, jump to the end of the method, read the return value from stack and then return the value"

So what advantage does it offer to debugging?

If you have more than one return statement in your code, both of them will 'jump' to the end of the code before reading the return value from stack. This allows you exactly one place (the closing brace of the method) where you can put a break-point and modify the return value before it is actually returned to the calling method. Pretty helpful isn't it?

Is the re-written version improper in any way?

There is nothing improper in your code. In fact if you build the original in release mode and check generated CIL, you will notice that it is mostly same as yours.

这篇关于关于手工编码IL问题的基础上拆卸简单的C#代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆