This project is read-only.

Problem with short braches (Br.S and so on)

Topics: Metadata Model
Sep 8, 2014 at 2:59 PM
Hi,

I am currently working on a project which needs to parse methods on instruction level.

At the moment I just copy every method on instruction level. The code looks just like this:
var testIlGenerator = new ILGenerator(host, method);
   foreach (var operation in method.Body.Operations) {
     switch (operation.OperationCode) {
         case OperationCode.Br_S:
              testIlGenerator.Emit(operation.OperationCode, operation.Value);
         break;
         default:
              testIlGenerator.Emit(operation.OperationCode, operation.Value);
         break;
      }
   }
var newBody = new ILGeneratorMethodBody(testIlGenerator, true, 8, method, Enumerable<ILocalDefinition>.Empty, Enumerable<ITypeDefinition>.Empty);
method.Body = newBody;
Of course the code does nothing at the moment (later it should change instructions and insert stuff). But here is already the problem. It does not work and throws an exception.

I could narrow it down to short branches (the reason above is an extra case for it). For some reason the c# compiler gives me CIL code like this:
[...]
4.     br.s   5 -> ldloc.0
5.     ldloc.0
[...]
This branch does not really do anything, but it is valid code. When I parse it via the code above, I get an exception when the operation is emitted to the generator. The reason seems to be that operation.value is an object of type "uint" and

public void Emit(OperationCode opcode, object value)

in IlGenerator.cs does not have any case for it and finally reaches the line
[...]
throw new InvalidOperationException();
[...]
I thought "OK, test it by changing the uint to a byte" because br.s only needs a byte. But when I do that, the PeWriter throws an exception in

private MemoryStream SerializeMethodBodyIL(IMethodBody methodBody) {

in this case:
          case OperationCode.Beq_S:
          case OperationCode.Bge_S:
          case OperationCode.Bge_Un_S:
          case OperationCode.Bgt_S:
          case OperationCode.Bgt_Un_S:
          case OperationCode.Ble_S:
          case OperationCode.Ble_Un_S:
          case OperationCode.Blt_S:
          case OperationCode.Blt_Un_S:
          case OperationCode.Bne_Un_S:
          case OperationCode.Br_S:
          case OperationCode.Brfalse_S:
          case OperationCode.Brtrue_S:
          case OperationCode.Leave_S:
            //^ assume operation.Value is uint;
            writer.WriteSbyte((sbyte)((uint)operation.Value-mbody.Position-1)); break;
The exception tells me this:

"An unhandled exception of type 'System.InvalidCastException' occurred in Microsoft.Cci.PeWriter.dll"

So I am stuck at the moment. I try to fix it but I do not have enough insides to make a clean fix for it.
Sep 8, 2014 at 3:22 PM
Ok,

I fixed it as far as I can see. But it would be nice if you could look over my fix.

In the function

public void Emit(OperationCode opcode, object value)

in IlGenerator.cs i added this case:
[...]
      switch (System.Convert.GetTypeCode(value)) {
        case TypeCode.Byte: this.Emit(opcode, (byte)value); break;
        case TypeCode.Double: this.Emit(opcode, (double)value); break;
        case TypeCode.Single: this.Emit(opcode, (float)value); break;
        case TypeCode.Int32: this.Emit(opcode, (int)value); break;
        case TypeCode.Int64: this.Emit(opcode, (long)value); break;
        case TypeCode.SByte: this.Emit(opcode, (sbyte)value); break;
        case TypeCode.Int16: this.Emit(opcode, (short)value); break;
        case TypeCode.String: this.Emit(opcode, (string)value); break;
        case TypeCode.Empty: this.Emit(opcode); break;


        // TODO
        // fixed by me
        case TypeCode.UInt32: this.Emit(opcode, (uint)value); break;


        default:
[...]
But then I still got the exception in

private MemoryStream SerializeMethodBodyIL(IMethodBody methodBody)

in PeWriter.cs. After I debugged a little, I figured out, that the exception occurs if the value is a "long" object. I do not know why the cast does not work when the object is of type "long", but I fixed it quickly with this test code:
[...]
          case OperationCode.Beq_S:
          case OperationCode.Bge_S:
          case OperationCode.Bge_Un_S:
          case OperationCode.Bgt_S:
          case OperationCode.Bgt_Un_S:
          case OperationCode.Ble_S:
          case OperationCode.Ble_Un_S:
          case OperationCode.Blt_S:
          case OperationCode.Blt_Un_S:
          case OperationCode.Bne_Un_S:
          case OperationCode.Br_S:
          case OperationCode.Brfalse_S:
          case OperationCode.Brtrue_S:
          case OperationCode.Leave_S:
            //^ assume operation.Value is uint;


            // TODO
            // fixed by me
              if (operation.Value is long) {
                  var temp = Convert.ToUInt32(operation.Value);
                  writer.WriteSbyte((sbyte)((uint)temp - mbody.Position - 1));
                  break;
              }
              else {
                  writer.WriteSbyte((sbyte)((uint)operation.Value - mbody.Position - 1));
                  break;
              }
[...]
and now it gets translated by CCI.
Sep 8, 2014 at 4:28 PM
It is a bit confusing, particularly when you start out, but stick with it and you'll get the hang of it.

Right now, the thing to do is to realize that ILGenerator is kind of like an assembler and it needs the calls to Emit to have instruction appropriate operands. For branch instructions that operands should be ILGeneratorLabel instances.

For a leg up, have a look at the ILMutator sample.

Good luck.

Herman
Sep 12, 2014 at 1:30 PM
Not really the answer I was hoping for. But I realize, it was the wrong place to post it (because it is an issue with the CCI metadata framework itself and not with my code).

So I created an issue for it.

Thanks.