Weirdness of C# compiler

kao

I've been quite busy lately. I made OMF file template I promised few weeks ago, found a remote code execution vulnerability in One Big Company's product and spent some time breaking keygenme by li0nsar3c00l. I'll make a blog post about most of these findings sooner or later.

But today I want to show you something that made me go WTF..

I needed to see how the loop "while true do nothing" looks like in IL. Since C# compiler and .NET JIT compiler are quite smart and optimize code that's certainly an eternal loop, I needed to get creative:

using System;

static class Test
{
  static void bla(Int32 param)
  {
     while (param != 0) {};  // loop loop loop!
     Console.WriteLine("1");
  }

  static void Main()
  {
     bla(123);
  }
}

Nothing fancy, but C# & JIT compiler don't track param values, so they both generate proper code..

Well, I thought it's a proper code, until I looked at generated MSIL:

.method private hidebysig static void  bla(int32 param) cil managed
  {
    // Code size       28 (0x1c)
    .maxstack  2
    .locals init (bool V_0)
    IL_0000:  nop
    IL_0001:  br.s       IL_0005

    IL_0003:  nop
    IL_0004:  nop
    IL_0005:  ldarg.0
    IL_0006:  ldc.i4.0
    IL_0007:  ceq
    IL_0009:  ldc.i4.0
    IL_000a:  ceq
    IL_000c:  stloc.0
    IL_000d:  ldloc.0
    IL_000e:  brtrue.s   IL_0003

    IL_0010:  ldstr      "1"
    IL_0015:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_001a:  nop
    IL_001b:  ret
  } // end of method Test::bla

WTF WTF WTF? Can anyone explain to me why there are 2 ceq instructions?

I could understand extra nop and stloc/ldloc instructions, but that ceq completely blows my mind.. And it's the same for .NET 2.0 and 4.5 compilers.

4 thoughts on “Weirdness of C# compiler

  1. Just my comment, nothing fo prooving this. I assume, the concept behind is:
    IL_0005: ldarg.0
    IL_0006: ldc.i4.0
    IL_0007: ceq
    this is your comparison, as written in code.

    (stack)
    IL_0009: ldc.i4.0
    IL_000a: ceq
    this is the boolean comparison for the loop, to make sure eigher 0 or 1 is on stack. The storing of the result is quite unnecessary.

    i dont understand eigher, why the whole code haven't been optimized.

  2. Well, the entire 2nd part is unnecessary. MSIL has a wonderful "brfalse" opcode, I really don't understand why it's not used here.. 😉 Something like this:

    ldarg.0
    ldc.i4.0
    ceq
    brfalse.s IL_0003

Leave a Reply

  • Be nice to me and everyone else.
  • If you are reporting a problem in my tool, please upload the file which causes the problem.
    I can`t help you without seeing the file.
  • Links in comments are visible only to me. Other visitors cannot see them.

 −  one  =  1