Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Questions about hand coded IL based on disassembled simple C# code

Tags:

c#

il

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
  }
like image 757
Aaron Anodide Avatar asked Oct 01 '12 02:10

Aaron Anodide


2 Answers

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.

like image 193
Amit Mittal Avatar answered Sep 22 '22 11:09

Amit Mittal


Disclaimer: I'm not an IL expert by any means.

  1. What is the purpose of the nop operation?

    There was a big discussion about this in terms of x86 ASM on programmers.stackexchange.com a little while ago, see here: Purpose of NOP instruction and align statement in x86 assembly. It would essentially be the same.

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

    This is just a branch to the end of the method. If you were to have multiple return paths in this function it would make more sense to look at. As it stands, the compiler has included it instead of optimizing it away (possibly a compiler switch will optimize it away).

  3. Is the re-written version improper in any way?

    Not that I can see. You've just stripped the bulk of the compiler's work out that isn't required for such a simple application. If you were to make any further additions to this code then the extra IL here would be required to complete it's tasks.

like image 45
Simon Whitehead Avatar answered Sep 22 '22 11:09

Simon Whitehead