// Emulate one specific operation from the given method. private static void Explore(MethodDefinition methodDef, List <OpState> processingQueue, List <int> processedOps, OpState operation) { // Get specific values from current operation; int offset = operation.Offset; // Do not simulate processed instructions again. var ins = methodDef.Body.Instructions.First(o => o.Offset == offset); if (processedOps.Contains(ins.Offset)) { return; } processedOps.Add(ins.Offset); List <Condition> conditions = operation.Conditions; List <byte> writtenBytes = operation.BytesWritten; switch (ins.OpCode.Code) { case Code.Dup: // Most of the time this element is a reference. References are clone-able without side-effects. object element = operation.StackPop(); operation.StackPush(element); operation.StackPush(element); break; case Code.Ldnull: operation.StackPush("null"); break; case Code.Ldc_I4: // The operand is a normal int. operation.StackPush(ins.Operand); break; case Code.Ldc_I4_S: // Combine 0 and the sbyte to force a conversion. // The operand is a signed byte. int intVal = 0 | (sbyte)ins.Operand; operation.StackPush(intVal); break; case Code.Ldc_R4: float flVal = (float)ins.Operand; operation.StackPush(flVal); break; case Code.Ldstr: operation.StackPush(ins.Operand); break; case Code.Ldc_I4_0: case Code.Ldc_I4_1: case Code.Ldc_I4_2: case Code.Ldc_I4_3: case Code.Ldc_I4_4: case Code.Ldc_I4_5: case Code.Ldc_I4_6: case Code.Ldc_I4_7: case Code.Ldc_I4_8: // OP2 is the second byte representing our opcode. // Ldc_i4_0 starts at OP2 = 0x16, so we subtract 0x16 from OP2 for each // matching opcode to find the index of the argument. int value = (int)(ins.OpCode.Op2 - 0x16); operation.StackPush(value); break; case Code.Ldc_I4_M1: // Pushes integer value -1 onto the stack. operation.StackPush(-1); break; case Code.Ldloca: operation.StackPush(String.Format("&{0}", (ins.Operand as VariableReference).ToString())); break; case Code.Ldloc: case Code.Ldloc_S: case Code.Ldloca_S: operation.StackPush((ins.Operand as VariableReference).ToString()); break; case Code.Ldloc_0: case Code.Ldloc_1: case Code.Ldloc_2: case Code.Ldloc_3: // LdLoc_0 starts at 0x06 for OP2. int localIdx = (int)(ins.OpCode.Op2 - 0x06); operation.StackPush(String.Format("ldArg{0}", localIdx)); break; case Code.Ldfld: var loadedObject = operation.StackPop(); var field = String.Format("{0}.{1}", loadedObject, (ins.Operand as FieldReference).Name); operation.StackPush(field); break; case Code.Ldsfld: operation.StackPush((ins.Operand as FieldReference).FullName); break; case Code.Ldarg: { var idx = (ins.Operand as ParameterReference).Index; if (idx == -1) { operation.StackPush("this"); } else { operation.StackPush(String.Format("arg{0}", idx)); } } break; case Code.Ldarg_0: case Code.Ldarg_1: case Code.Ldarg_2: case Code.Ldarg_3: case Code.Ldarg_S: { // OP2 is the second byte representing our opcode. // Ldarg_0 starts at OP2 = 2, so we subtract 2 from OP2 for each // matching opcode to find the index of the argument. operation.StackPush(String.Format("arg{0}", (ins.OpCode.Op2 - 2).ToString())); } break; case Code.Ldelem_Ref: { var idx = operation.StackPop(); var arr = operation.StackPop(); operation.StackPush(String.Format("{0}[{1}]", arr, idx)); } break; case Code.Ldftn: operation.StackPush(String.Format("&({0})", ins.Operand)); break; case Code.Ldtoken: // Not sure what to do with the operand, it could be anything.. A FieldDefinition or TypeDefinition.. var valueType = ins.Operand; operation.StackPush(valueType); break; case Code.Newobj: { var mr = ins.Operand as MethodReference; var numParam = mr.Parameters.Count; var stackIdx = operation.Stack.Count - numParam; var args = operation.Stack.GetRange(stackIdx, numParam); operation.Stack.RemoveRange(stackIdx, numParam); var callString = String.Format("new {0}({1})", mr.DeclaringType.Name, String.Join(", ", args)); operation.StackPush(callString); args.Insert(0, "this"); var info = new CallInfo { Conditions = new List <Condition>(conditions), Method = mr, Arguments = args, String = callString }; // Process data collected up until now _onCall(info, writtenBytes); } break; case Code.Newarr: TypeReference operand = ins.Operand as TypeDefinition ?? ins.Operand as TypeReference; var arrayAmount = Int32.Parse(operation.StackPop().ToString()); var arrayName = String.Format("new {0}[{1}]", operand.FullName, arrayAmount); var openArray = new OpenArray() { StackName = arrayName, Contents = new List <object>(arrayAmount), }; // Prefill the array with nulls for (int i = 0; i < arrayAmount; ++i) { openArray.Contents.Push(null); } operation.StackPush(openArray); break; case Code.Brfalse: { var lhs = operation.StackPop().ToString(); var src = ins.Offset; var tgt = (ins.Operand as Instruction).Offset; var cond = new Condition(src, lhs, Comparison.IsFalse); var ncond = new Condition(src, lhs, Comparison.IsTrue); Branch(tgt, operation, cond, ncond, processingQueue); } break; case Code.Brtrue: { var lhs = operation.StackPop().ToString(); var src = ins.Offset; var tgt = (ins.Operand as Instruction).Offset; var cond = new Condition(src, lhs, Comparison.IsTrue); var ncond = new Condition(src, lhs, Comparison.IsFalse); Branch(tgt, operation, cond, ncond, processingQueue); } break; case Code.Beq: case Code.Bne_Un: case Code.Ble: case Code.Bge: case Code.Blt: case Code.Bgt: { var rhs = operation.StackPop().ToString(); var lhs = operation.StackPop().ToString(); var src = ins.Offset; var tgt = (ins.Operand as Instruction).Offset; Condition cond = null, ncond = null; switch (ins.OpCode.Code) { case Code.Beq: cond = new Condition(src, lhs, Comparison.Equal, rhs); ncond = new Condition(src, lhs, Comparison.Inequal, rhs); break; case Code.Bne_Un: cond = new Condition(src, lhs, Comparison.Inequal, rhs); ncond = new Condition(src, lhs, Comparison.Equal, rhs); break; case Code.Ble: // x <= y --> y >= x; !(x <= y) --> x > y cond = new Condition(src, rhs, Comparison.GreaterThanEqual, lhs); ncond = new Condition(src, lhs, Comparison.GreaterThan, rhs); break; case Code.Bge: cond = new Condition(src, lhs, Comparison.GreaterThanEqual, rhs); // !(x >= y) --> y > x ncond = new Condition(src, rhs, Comparison.GreaterThan, lhs); break; case Code.Blt: // x < y --> y > x; !(x < y) --> x >= y cond = new Condition(src, rhs, Comparison.GreaterThan, lhs); ncond = new Condition(src, lhs, Comparison.GreaterThanEqual, rhs); break; case Code.Bgt: // !(x > y) --> y >= x cond = new Condition(src, lhs, Comparison.GreaterThan, rhs); ncond = new Condition(src, rhs, Comparison.GreaterThanEqual, lhs); break; } Branch(tgt, operation, cond, ncond, processingQueue); } break; case Code.Br: // Jump to other location (unconditionally). operation.Offset = (ins.Operand as Instruction).Offset; Explore(methodDef, processingQueue, processedOps, operation); return; case Code.Stsfld: { var arg = operation.StackPop(); // Don't pop for the object pointer, because this is a static // set. var info = new StoreInfo { Conditions = new List <Condition>(conditions), Field = ins.Operand as FieldReference, Argument = arg.ToString(), RawObject = arg, }; _onStore(info, null); } break; case Code.Stfld: { var arg = operation.StackPop(); /*var obj = */ operation.StackPop(); var info = new StoreInfo { Conditions = new List <Condition>(conditions), Field = ins.Operand as FieldReference, Argument = arg.ToString(), RawObject = arg, }; _onStore(info, null); } break; case Code.Stelem_Ref: { /*var val = */ var arr_value = operation.StackPop(); /*var idx = */ var idx = Int32.Parse(operation.StackPop().ToString()); /*var arr = */ var arr = operation.StackPop(); if (!(arr is OpenArray)) { throw new InvalidOperationException("The popped object must be of type OpenArray!"); } (arr as OpenArray).Contents[idx] = arr_value; } break; case Code.Stelem_I4: { /*var val = */ // This value is guaranteed to be an integer, because of the opcode. var arr_value = Int32.Parse(operation.StackPop().ToString()); /*var idx = */ var idx = Int32.Parse(operation.StackPop().ToString()); /*var arr = */ var arr = operation.StackPop(); if (!(arr is OpenArray)) { throw new InvalidOperationException("The popped object must be of type OpenArray!"); } (arr as OpenArray).Contents[idx] = arr_value; } break; case Code.Mul: { var rhs = operation.StackPop().ToString(); var lhs = operation.StackPop().ToString(); operation.StackPush(String.Format("{0} * {1}", lhs, rhs)); } break; case Code.Call: case Code.Callvirt: { var mr = ins.Operand as MethodReference; var args = new List <object>(); for (var i = 0; i < mr.Parameters.Count; i++) { args.Add(operation.StackPop()); } if (mr.HasThis) { if (operation.Stack.Count < 1) { throw new InvalidOperationException("The stack count should be 1 or higer"); } args.Add(operation.StackPop()); } args.Reverse(); var callString = String.Format("{0}.{1}({2})", mr.HasThis ? args.First().ToString() : mr.DeclaringType.Name, mr.Name, String.Join(", ", mr.HasThis ? args.Skip(1) : args)); if (mr.ReturnType.FullName != "System.Void") { operation.StackPush(callString); } var info = new CallInfo { Conditions = new List <Condition>(conditions), Method = mr, Arguments = args, String = callString }; // Process data collected up until now _onCall(info, writtenBytes); } break; } if (ins.Next != null) { // Update the current operation with next offset and requeue. operation.Offset = ins.Next.Offset; processingQueue.Add(operation); } }