public static StackSlot[] ModifyStack(StackSlot[] stack, int popCount, int pushCount, ByteCode pushDefinition) { var newStack = new StackSlot[stack.Length - popCount + pushCount]; Array.Copy(stack, newStack, stack.Length - popCount); for (int i = stack.Length - popCount; i < newStack.Length; i++) { newStack[i] = new StackSlot(new[] { pushDefinition }, null); } return newStack; }
/// <summary> /// Find all targets the given bytecode can branch to. /// </summary> private static List<ByteCode> FindBranchTargets(ByteCode byteCode, Dictionary<Instruction, ByteCode> instrToByteCode, HashSet<ByteCode> exceptionHandlerStarts) { // Find all successors var branchTargets = new List<ByteCode>(); if (!byteCode.Code.IsUnconditionalControlFlow()) { if (exceptionHandlerStarts.Contains(byteCode.Next)) { // Do not fall though down to exception handler // It is invalid IL as per ECMA-335 ยง12.4.2.8.1, but some obfuscators produce it } else { branchTargets.Add(byteCode.Next); } } var operandAsInstructionArray = byteCode.Operand as Instruction[]; if (operandAsInstructionArray != null) { branchTargets.AddRange(operandAsInstructionArray.Select(inst => instrToByteCode[inst])); } else { var operandAsInstruction = byteCode.Operand as Instruction; if (operandAsInstruction != null) { var target = instrToByteCode[operandAsInstruction]; branchTargets.Add(target); } else { var operandAsLookupSwitchData = byteCode.Operand as LookupSwitchData; if (operandAsLookupSwitchData != null) { branchTargets.AddRange(operandAsLookupSwitchData.Pairs.Select(pair => instrToByteCode[pair.Target])); // Default target is ignored here because a a Br follows. } } } return branchTargets; }
List<ByteCode> StackAnalysis(MethodDefinition methodDef) { // Create temporary structure for the stack analysis List<ByteCode> body = new List<ByteCode>(methodDef.Body.Instructions.Count); foreach(Instruction inst in methodDef.Body.Instructions) { OpCode opCode = inst.OpCode; object operand = inst.Operand; MethodBodyRocks.ExpandMacro(ref opCode, ref operand, methodDef.Body); ByteCode byteCode = new ByteCode() { Offset = inst.Offset, EndOffset = inst.Next != null ? inst.Next.Offset : methodDef.Body.CodeSize, OpCode = opCode, Operand = operand, PopCount = inst.GetPopCount(), PushCount = inst.GetPushCount() }; instrToByteCode[inst] = byteCode; body.Add(byteCode); } for (int i = 0; i < body.Count - 1; i++) { body[i].Next = body[i + 1]; } Queue<ByteCode> agenda = new Queue<ByteCode>(); // Add known states body[0].StackBefore = new List<StackSlot>(); agenda.Enqueue(body[0]); if(methodDef.Body.HasExceptionHandlers) { foreach(ExceptionHandler ex in methodDef.Body.ExceptionHandlers) { ByteCode tryStart = instrToByteCode[ex.TryStart]; tryStart.StackBefore = new List<StackSlot>(); agenda.Enqueue(tryStart); ByteCode handlerStart = instrToByteCode[ex.HandlerType == ExceptionHandlerType.Filter ? ex.FilterStart : ex.HandlerStart]; handlerStart.StackBefore = new List<StackSlot>(); if (ex.HandlerType == ExceptionHandlerType.Catch || ex.HandlerType == ExceptionHandlerType.Filter) { handlerStart.StackBefore.Add(new StackSlot(null)); } agenda.Enqueue(handlerStart); // Control flow is not required to reach endfilter if (ex.HandlerType == ExceptionHandlerType.Filter) { ByteCode endFilter = instrToByteCode[ex.FilterEnd.Previous]; endFilter.StackBefore = new List<StackSlot>(); } } } // Process agenda while(agenda.Count > 0) { ByteCode byteCode = agenda.Dequeue(); // Calculate new stack List<StackSlot> newStack = byteCode.CloneStack(byteCode.PopCount); for (int i = 0; i < byteCode.PushCount; i++) { newStack.Add(new StackSlot(byteCode)); } // Apply the state to any successors List<ByteCode> branchTargets = new List<ByteCode>(); if (byteCode.OpCode.CanFallThough()) { branchTargets.Add(byteCode.Next); } if (byteCode.OpCode.IsBranch()) { if (byteCode.Operand is Instruction[]) { foreach(Instruction inst in (Instruction[])byteCode.Operand) { ByteCode target = instrToByteCode[inst]; branchTargets.Add(target); // The target of a branch must have label if (target.Label == null) { target.Label = new ILLabel() { Name = target.Name }; } } } else { ByteCode target = instrToByteCode[(Instruction)byteCode.Operand]; branchTargets.Add(target); // The target of a branch must have label if (target.Label == null) { target.Label = new ILLabel() { Name = target.Name }; } } } foreach (ByteCode branchTarget in branchTargets) { if (branchTarget.StackBefore == null) { branchTarget.StackBefore = newStack; // Do not share one stack for several bytecodes if (branchTargets.Count > 1) { branchTarget.StackBefore = branchTarget.CloneStack(0); } agenda.Enqueue(branchTarget); } else { if (branchTarget.StackBefore.Count != newStack.Count) { throw new Exception("Inconsistent stack size at " + byteCode.Name); } // Merge stacks bool modified = false; for (int i = 0; i < newStack.Count; i++) { List<ByteCode> oldPushedBy = branchTarget.StackBefore[i].PushedBy; List<ByteCode> newPushedBy = oldPushedBy.Union(newStack[i].PushedBy).ToList(); if (newPushedBy.Count > oldPushedBy.Count) { branchTarget.StackBefore[i].PushedBy = newPushedBy; modified = true; } } if (modified) { agenda.Enqueue(branchTarget); } } } } // Genertate temporary variables to replace stack foreach(ByteCode byteCode in body) { int argIdx = 0; int popCount = byteCode.PopCount ?? byteCode.StackBefore.Count; for (int i = byteCode.StackBefore.Count - popCount; i < byteCode.StackBefore.Count; i++) { StackSlot arg = byteCode.StackBefore[i]; ILVariable tmpVar = new ILVariable() { Name = string.Format("arg_{0:X2}_{1}", byteCode.Offset, argIdx), IsGenerated = true }; arg.LoadFrom = tmpVar; foreach(ByteCode pushedBy in arg.PushedBy) { // TODO: Handle exception variables if (pushedBy != null) { if (pushedBy.StoreTo == null) { pushedBy.StoreTo = new List<ILVariable>(1); } pushedBy.StoreTo.Add(tmpVar); } } if (arg.PushedBy.Count == 1) { allowInline[tmpVar] = true; } argIdx++; } } // Convert local varibles Variables = methodDef.Body.Variables.Select(v => new ILVariable() { Name = string.IsNullOrEmpty(v.Name) ? "var_" + v.Index : v.Name, Type = v.VariableType }).ToList(); int[] numReads = new int[Variables.Count]; int[] numWrites = new int[Variables.Count]; foreach(ByteCode byteCode in body) { if (byteCode.OpCode == OpCodes.Ldloc) { int index = ((VariableDefinition)byteCode.Operand).Index; byteCode.Operand = Variables[index]; numReads[index]++; } if (byteCode.OpCode == OpCodes.Stloc) { int index = ((VariableDefinition)byteCode.Operand).Index; byteCode.Operand = Variables[index]; numWrites[index]++; } } // Find which variables we can inline if (this.optimize) { for (int i = 0; i < Variables.Count; i++) { if (numReads[i] == 1 && numWrites[i] == 1) { allowInline[Variables[i]] = true; } } } // Convert branch targets to labels foreach(ByteCode byteCode in body) { if (byteCode.Operand is Instruction[]) { List<ILLabel> newOperand = new List<ILLabel>(); foreach(Instruction target in (Instruction[])byteCode.Operand) { newOperand.Add(instrToByteCode[target].Label); } byteCode.Operand = newOperand.ToArray(); } else if (byteCode.Operand is Instruction) { byteCode.Operand = instrToByteCode[(Instruction)byteCode.Operand].Label; } } return body; }
internal static NormalizedByteCode GetNormalizedByteCode(ByteCode bc) { return data[(int)bc].normbc; }
internal static int GetArg(ByteCode bc, int arg) { if((data[(int)bc].flags & ByteCodeFlags.FixedArg) != 0) { return data[(int)bc].arg; } return arg; }
private ByteCodeMetaData(ByteCode bc, ByteCodeMode reg, ByteCodeModeWide wide, bool cannotThrow) { this.reg = reg; this.wide = wide; this.normbc = (NormalizedByteCode)bc; this.arg = 0; this.flags = ByteCodeFlags.None; if(cannotThrow) { this.flags |= ByteCodeFlags.CannotThrow; } data[(int)bc] = this; }
/// <summary> /// Analyse the instructions in the method code and convert them to a ByteCode list. /// </summary> private List<ByteCode> StackAnalysis() { var instrToByteCode = new Dictionary<Instruction, ByteCode>(); // Create temporary structure for the stack analysis var body = new List<ByteCode>(methodDef.Body.Instructions.Count); List<Instruction> prefixes = null; foreach (var inst in methodDef.Body.Instructions) { if (inst.OpCode.OpCodeType == OpCodeType.Prefix) { if (prefixes == null) prefixes = new List<Instruction>(1); prefixes.Add(inst); continue; } var code = (AstCode)inst.OpCode.Code; var operand = inst.Operand; AstCodeUtil.ExpandMacro(ref code, ref operand, methodDef.Body); var byteCode = new ByteCode { Offset = inst.Offset, EndOffset = inst.Next != null ? inst.Next.Offset : methodDef.Body.CodeSize, Code = code, Operand = operand, PopCount = inst.GetPopDelta(methodDef), PushCount = inst.GetPushDelta(), SequencePoint = SequencePointWrapper.Wrap(inst.SequencePoint) }; if (prefixes != null) { instrToByteCode[prefixes[0]] = byteCode; byteCode.Offset = prefixes[0].Offset; byteCode.Prefixes = prefixes.ToArray(); prefixes = null; } else { instrToByteCode[inst] = byteCode; } body.Add(byteCode); } for (int i = 0; i < body.Count - 1; i++) { body[i].Next = body[i + 1]; } var agenda = new Stack<ByteCode>(); var varCount = methodDef.Body.Variables.Count; var exceptionHandlerStarts = new HashSet<ByteCode>(methodDef.Body.ExceptionHandlers.Select(eh => instrToByteCode[eh.HandlerStart])); // Add known states if (methodDef.Body.HasExceptionHandlers) { foreach (var ex in methodDef.Body.ExceptionHandlers) { var handlerStart = instrToByteCode[ex.HandlerStart]; handlerStart.StackBefore = new StackSlot[0]; handlerStart.VariablesBefore = VariableSlot.MakeUknownState(varCount); if (ex.HandlerType == ExceptionHandlerType.Catch || ex.HandlerType == ExceptionHandlerType.Filter) { // Catch and Filter handlers start with the exeption on the stack var ldexception = new ByteCode() { Code = AstCode.Ldexception, Operand = ex.CatchType, PopCount = 0, PushCount = 1 }; ldexceptions[ex] = ldexception; handlerStart.StackBefore = new[] { new StackSlot(new[] { ldexception }, null) }; } agenda.Push(handlerStart); if (ex.HandlerType == ExceptionHandlerType.Filter) { var filterStart = instrToByteCode[ex.FilterStart]; var ldexception = new ByteCode { Code = AstCode.Ldexception, Operand = ex.CatchType, PopCount = 0, PushCount = 1 }; // TODO: ldexceptions[ex] = ldexception; filterStart.StackBefore = new[] { new StackSlot(new[] { ldexception }, null) }; filterStart.VariablesBefore = VariableSlot.MakeUknownState(varCount); agenda.Push(filterStart); } } } body[0].StackBefore = new StackSlot[0]; body[0].VariablesBefore = VariableSlot.MakeUknownState(varCount); agenda.Push(body[0]); // Process agenda while (agenda.Count > 0) { var byteCode = agenda.Pop(); // Calculate new stack var newStack = StackSlot.ModifyStack(byteCode.StackBefore, byteCode.PopCount ?? byteCode.StackBefore.Length, byteCode.PushCount, byteCode); // Calculate new variable state var newVariableState = VariableSlot.CloneVariableState(byteCode.VariablesBefore); if (byteCode.IsVariableDefinition) { newVariableState[((VariableReference)byteCode.Operand).Index] = new VariableSlot(new[] { byteCode }, false); } // After the leave, finally block might have touched the variables if (byteCode.Code == AstCode.Leave) { newVariableState = VariableSlot.MakeUknownState(varCount); } // Find all successors var branchTargets = new List<ByteCode>(); if (!byteCode.Code.IsUnconditionalControlFlow()) { if (exceptionHandlerStarts.Contains(byteCode.Next)) { // Do not fall though down to exception handler // It is invalid IL as per ECMA-335 ยง12.4.2.8.1, but some obfuscators produce it } else { branchTargets.Add(byteCode.Next); } } if (byteCode.Operand is Instruction[]) { foreach (var inst in (Instruction[])byteCode.Operand) { var target = instrToByteCode[inst]; branchTargets.Add(target); // The target of a branch must have label if (target.Label == null) { target.Label = new AstLabel(target.SequencePoint, target.Name); } } } else if (byteCode.Operand is Instruction) { var target = instrToByteCode[(Instruction)byteCode.Operand]; branchTargets.Add(target); // The target of a branch must have label if (target.Label == null) { target.Label = new AstLabel(target.SequencePoint, target.Name); } } // Apply the state to successors foreach (var branchTarget in branchTargets) { if (branchTarget.StackBefore == null && branchTarget.VariablesBefore == null) { if (branchTargets.Count == 1) { branchTarget.StackBefore = newStack; branchTarget.VariablesBefore = newVariableState; } else { // Do not share data for several bytecodes branchTarget.StackBefore = StackSlot.ModifyStack(newStack, 0, 0, null); branchTarget.VariablesBefore = VariableSlot.CloneVariableState(newVariableState); } agenda.Push(branchTarget); } else { if (branchTarget.StackBefore.Length != newStack.Length) { throw new Exception("Inconsistent stack size at " + byteCode.Name); } // Be careful not to change our new data - it might be reused for several branch targets. // In general, be careful that two bytecodes never share data structures. bool modified = false; // Merge stacks - modify the target for (int i = 0; i < newStack.Length; i++) { var oldDefs = branchTarget.StackBefore[i].Definitions; var newDefs = oldDefs.Union(newStack[i].Definitions); if (newDefs.Length > oldDefs.Length) { branchTarget.StackBefore[i] = new StackSlot(newDefs, null); modified = true; } } // Merge variables - modify the target for (int i = 0; i < newVariableState.Length; i++) { var oldSlot = branchTarget.VariablesBefore[i]; var newSlot = newVariableState[i]; if (!oldSlot.UnknownDefinition) { if (newSlot.UnknownDefinition) { branchTarget.VariablesBefore[i] = newSlot; modified = true; } else { ByteCode[] oldDefs = oldSlot.Definitions; ByteCode[] newDefs = CollectionExtensions.Union(oldDefs, newSlot.Definitions); if (newDefs.Length > oldDefs.Length) { branchTarget.VariablesBefore[i] = new VariableSlot(newDefs, false); modified = true; } } } } if (modified) { agenda.Push(branchTarget); } } } } // Occasionally the compilers or obfuscators generate unreachable code (which might be intentonally invalid) // I belive it is safe to just remove it body.RemoveAll(b => b.StackBefore == null); // Genertate temporary variables to replace stack foreach (var byteCode in body) { int argIdx = 0; int popCount = byteCode.PopCount ?? byteCode.StackBefore.Length; for (int i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) { var tmpVar = new AstGeneratedVariable(string.Format("arg_{0:X2}_{1}", byteCode.Offset, argIdx), null); byteCode.StackBefore[i] = new StackSlot(byteCode.StackBefore[i].Definitions, tmpVar); foreach (ByteCode pushedBy in byteCode.StackBefore[i].Definitions) { if (pushedBy.StoreTo == null) { pushedBy.StoreTo = new List<AstVariable>(1); } pushedBy.StoreTo.Add(tmpVar); } argIdx++; } } // Try to use single temporary variable insted of several if possilbe (especially useful for dup) // This has to be done after all temporary variables are assigned so we know about all loads foreach (var byteCode in body) { if (byteCode.StoreTo != null && byteCode.StoreTo.Count > 1) { var locVars = byteCode.StoreTo; // For each of the variables, find the location where it is loaded - there should be preciesly one var loadedBy = locVars.Select(locVar => body.SelectMany(bc => bc.StackBefore).Single(s => s.LoadFrom == locVar)).ToList(); // We now know that all the variables have a single load, // Let's make sure that they have also a single store - us if (loadedBy.All(slot => slot.Definitions.Length == 1 && slot.Definitions[0] == byteCode)) { // Great - we can reduce everything into single variable var tmpVar = new AstGeneratedVariable(string.Format("expr_{0:X2}", byteCode.Offset), locVars.Select(x => x.OriginalName).FirstOrDefault()); byteCode.StoreTo = new List<AstVariable>() { tmpVar }; foreach (var bc in body) { for (int i = 0; i < bc.StackBefore.Length; i++) { // Is it one of the variable to be merged? if (locVars.Contains(bc.StackBefore[i].LoadFrom)) { // Replace with the new temp variable bc.StackBefore[i] = new StackSlot(bc.StackBefore[i].Definitions, tmpVar); } } } } } } // Split and convert the normal local variables ConvertLocalVariables(body); // Convert branch targets to labels and references to xreferences foreach (var byteCode in body) { if (byteCode.Operand is Instruction[]) { byteCode.Operand = (from target in (Instruction[])byteCode.Operand select instrToByteCode[target].Label).ToArray(); } else if (byteCode.Operand is Instruction) { byteCode.Operand = instrToByteCode[(Instruction)byteCode.Operand].Label; } else if (byteCode.Operand is FieldReference) { byteCode.Operand = XBuilder.AsFieldReference(module, (FieldReference)byteCode.Operand); } else if (byteCode.Operand is MethodReference) { byteCode.Operand = XBuilder.AsMethodReference(module, (MethodReference)byteCode.Operand); } else if (byteCode.Operand is TypeReference) { byteCode.Operand = XBuilder.AsTypeReference(module, (TypeReference)byteCode.Operand); } } // Convert parameters to ILVariables ConvertParameters(body); return body; }
/// <summary> /// Given a string value of Code, find the Opcode Type that uses that as its CodeName. /// </summary> /// <param name="code">ByteCode to look up</param> /// <returns>Type, one of the subclasses of Opcode, or PseudoNull if there was no match</returns> public static Type TypeFromCode(ByteCode code) { Type returnValue; if (! mapCodeToType.TryGetValue(code, out returnValue)) { returnValue = typeof(PseudoNull); // flag telling the caller "not found". } return returnValue; }
internal void SetTermNop(ushort pc) { // TODO what happens if we already have exactly the maximum number of instructions? this.pc = pc; this.opcode = ByteCode.__nop; }
public ByteCodeBlock(ByteCode first, ByteCode last) { this.first = first; this.last = last; }
public readonly AstVariable LoadFrom; // Variable used for storage of the value public StackSlot(ByteCode[] definitions, AstVariable loadFrom) { Definitions = definitions; LoadFrom = loadFrom; }
/// <summary> /// Gets all variables that are defined in the given range of bytes (start - (exclusive) end). /// </summary> private static void GetDefinedVariables(ByteCode start, ByteCode end, HashSet<LocalVariableReference> defs) { while ((start != null) && (start != end)) { if (start.IsVariableDefinition) { var varDef = start.Operand as LocalVariableReference; if (varDef != null) defs.Add(varDef); } start = start.Next; } }
/// <summary> /// Update the variable state of handlers for the exception handlers that start at the given branch target. /// </summary> private void UpdateTryStartBranchTarget(ByteCode branchTarget, IEnumerable<ExceptionHandler> exceptionHandlers, Dictionary<Instruction, ByteCode> instrToByteCode, VariableSlot[] newVariableState, Stack<ByteCode> agenda) { // The branch target is the start of a try block. // Collection all exception handler that share the same handler as this try block. var ehs = new HashSet<ExceptionHandler>(exceptionHandlers); foreach (var eh in validExceptionHandlers.Where(x => ehs.Any(y => y.HandlerPc == x.HandlerPc))) { ehs.Add(eh); } // Collection the variables defined in all try block's of the collected exception handlers. var defs = new HashSet<LocalVariableReference>(); foreach (var eh in ehs) { GetDefinedVariables(instrToByteCode[eh.Start], instrToByteCode[eh.End], defs); } // Merge variables of handlers - modify the target foreach (var handler in ehs.Select(x => instrToByteCode[x.Handler])) { var modified = false; for (var i = 0; i < newVariableState.Length; i++) { // Update variables unless it is defined if (defs.Any(x => x.Index == i)) continue; var oldSlot = handler.VariablesBefore[i]; var newSlot = newVariableState[i]; if (newSlot.UnknownDefinition) { if (!oldSlot.UnknownDefinition) { handler.VariablesBefore[i] = newSlot; modified = true; } } else { var oldDefs = oldSlot.Definitions; var newDefs = oldDefs.Union(newSlot.Definitions); if (newDefs.Length > oldDefs.Length) { handler.VariablesBefore[i] = new VariableSlot(newDefs, false); modified = true; } } } if (modified) { agenda.Push(handler); } } }
/// <summary> /// Update the state of the given branch targets (of the given bytecode). /// Add modified branch targets to the given agenda. /// </summary> private static void UpdateBranchTarget(ByteCode byteCode, ByteCode branchTarget, bool canShareNewState, StackSlot[] newStack, VariableSlot[] newVariableState, Stack<ByteCode> agenda) { if ((branchTarget.StackBefore == null) && (branchTarget.VariablesBefore == null)) { // Branch target has not been processed at all if (canShareNewState) { branchTarget.StackBefore = newStack; branchTarget.VariablesBefore = newVariableState; } else { // Do not share data for several bytecodes branchTarget.StackBefore = StackSlot.ModifyStack(newStack, 0, 0, null); branchTarget.VariablesBefore = VariableSlot.CloneVariableState(newVariableState); } agenda.Push(branchTarget); return; } // If we get here, the branch target has been processed before. // See the the stack size is the same and merge the state where needed. if ((branchTarget.StackBefore == null) || (branchTarget.StackBefore.Length != newStack.Length)) { throw new Exception("Inconsistent stack size at " + byteCode.Name); } // Be careful not to change our new data - it might be reused for several branch targets. // In general, be careful that two bytecodes never share data structures. var modified = false; // Merge stacks - modify the target for (var i = 0; i < newStack.Length; i++) { var oldDefs = branchTarget.StackBefore[i].Definitions; var newDefs = oldDefs.Union(newStack[i].Definitions); if (newDefs.Length > oldDefs.Length) { branchTarget.StackBefore[i] = new StackSlot(newDefs, null); modified = true; } } // Merge variables - modify the target for (var i = 0; i < newVariableState.Length; i++) { var oldSlot = branchTarget.VariablesBefore[i]; var newSlot = newVariableState[i]; if (oldSlot.UnknownDefinition) continue; if (newSlot.UnknownDefinition) { branchTarget.VariablesBefore[i] = newSlot; modified = true; } else { var oldDefs = oldSlot.Definitions; var newDefs = oldDefs.Union(newSlot.Definitions); if (newDefs.Length > oldDefs.Length) { branchTarget.VariablesBefore[i] = new VariableSlot(newDefs, false); modified = true; } } } if (modified) { agenda.Push(branchTarget); } }
public void CompileJumpByteCode(ByteCode b, short jump) { this.CompileByte((byte)b); this.CompileByte((byte)(jump >> 8)); this.CompileByte((byte)(jump & 0xff)); }
internal void Read(ushort pc, BigEndianBinaryReader br) { this.pc = pc; ByteCode bc = (ByteCode) br.ReadByte(); switch (ByteCodeMetaData.GetMode(bc)) { case ByteCodeMode.Simple: break; case ByteCodeMode.Constant_1: case ByteCodeMode.Local_1: arg1 = br.ReadByte(); break; case ByteCodeMode.Constant_2: arg1 = br.ReadUInt16(); break; case ByteCodeMode.Branch_2: arg1 = br.ReadInt16(); break; case ByteCodeMode.Branch_4: arg1 = br.ReadInt32(); break; case ByteCodeMode.Constant_2_1_1: arg1 = br.ReadUInt16(); arg2 = br.ReadByte(); if (br.ReadByte() != 0) { throw new ClassFormatError("invokeinterface filler must be zero"); } break; case ByteCodeMode.Immediate_1: arg1 = br.ReadSByte(); break; case ByteCodeMode.Immediate_2: arg1 = br.ReadInt16(); break; case ByteCodeMode.Local_1_Immediate_1: arg1 = br.ReadByte(); arg2 = br.ReadSByte(); break; case ByteCodeMode.Constant_2_Immediate_1: arg1 = br.ReadUInt16(); arg2 = br.ReadSByte(); break; case ByteCodeMode.Tableswitch: { // skip the padding uint p = pc + 1u; uint align = ((p + 3) & 0x7ffffffc) - p; br.Skip(align); int default_offset = br.ReadInt32(); this.arg1 = default_offset; int low = br.ReadInt32(); int high = br.ReadInt32(); if (low > high || high > 16384L + low) { throw new ClassFormatError("Incorrect tableswitch"); } SwitchEntry[] entries = new SwitchEntry[high - low + 1]; for (int i = low; i <= high; i++) { entries[i - low].value = i; entries[i - low].target_offset = br.ReadInt32(); } this.switch_entries = entries; break; } case ByteCodeMode.Lookupswitch: { // skip the padding uint p = pc + 1u; uint align = ((p + 3) & 0x7ffffffc) - p; br.Skip(align); int default_offset = br.ReadInt32(); this.arg1 = default_offset; int count = br.ReadInt32(); if (count < 0 || count > 16384) { throw new ClassFormatError("Incorrect lookupswitch"); } SwitchEntry[] entries = new SwitchEntry[count]; for (int i = 0; i < count; i++) { entries[i].value = br.ReadInt32(); entries[i].target_offset = br.ReadInt32(); } this.switch_entries = entries; break; } case ByteCodeMode.WidePrefix: bc = (ByteCode) br.ReadByte(); // NOTE the PC of a wide instruction is actually the PC of the // wide prefix, not the following instruction (vmspec 4.9.2) switch (ByteCodeMetaData.GetWideMode(bc)) { case ByteCodeModeWide.Local_2: arg1 = br.ReadUInt16(); break; case ByteCodeModeWide.Local_2_Immediate_2: arg1 = br.ReadUInt16(); arg2 = br.ReadInt16(); break; default: throw new ClassFormatError("Invalid wide prefix on opcode: {0}", bc); } break; default: throw new ClassFormatError("Invalid opcode: {0}", bc); } this.opcode = bc; this.normopcode = ByteCodeMetaData.GetNormalizedByteCode(bc); arg1 = ByteCodeMetaData.GetArg(opcode, arg1); }
public void CompileJumpByteCodeAt(ByteCode b, short jump, int position) { this.bytecodes[position] = (byte)b; this.bytecodes[position + 1] = (byte)(jump >> 8); this.bytecodes[position + 2] = (byte)(jump & 0xff); }
/// <summary> /// Create a single bytecode. /// </summary> private ByteCode CreateByteCode(Instruction inst, AstCode code, object operand, int popCount, int pushCount, Category category, XTypeReference type = null) { var next = codeAttr.GetNext(inst); var byteCode = new ByteCode { Category = category, Offset = inst.Offset, EndOffset = (next != null) ? next.Offset : codeAttr.Code.Length, Code = code, Operand = operand, PopCount = popCount, PushCount = pushCount, SourceLocation = new SourceLocation(codeAttr, inst), Type = type }; return byteCode; }
static bool IsDeterministicLdloca(ByteCode b) { var v = b.Operand; b = b.Next; if (b.Code == AstCode.Initobj) return true; // instance method calls on value types use the variable ref deterministically int stack = 1; while (true) { if (b.PopCount == null) return false; stack -= b.PopCount.GetValueOrDefault(); if (stack == 0) break; if (stack < 0) return false; if (b.Code.IsConditionalControlFlow() || b.Code.IsUnconditionalControlFlow()) return false; switch (b.Code) { case AstCode.Ldloc: case AstCode.Ldloca: case AstCode.Stloc: if (b.Operand == v) return false; break; } stack += b.PushCount; b = b.Next; if (b == null) return false; } if (b.Code == AstCode.Ldfld || b.Code == AstCode.Stfld) return true; return (b.Code == AstCode.Call || b.Code == AstCode.Callvirt) && ((MethodReference)b.Operand).HasThis; }
void Emit(Node node, ByteCode code, object operandA = null, object operandB = null) { var instruction = new Instruction(); instruction.operation = code; instruction.operandA = operandA; instruction.operandB = operandB; node.instructions.Add (instruction); if (code == ByteCode.Label) { // Add this label to the label table node.labels.Add ((string)instruction.operandA, node.instructions.Count - 1); } }
public VariableSlot(ByteCode[] definitions, bool unknownDefinition) { Definitions = definitions; UnknownDefinition = unknownDefinition; }
internal static bool CanThrowException(ByteCode bc) { return (data[(int) bc].flags & ByteCodeFlags.CannotThrow) == 0; }
private ByteCodeMetaData(ByteCode bc, NormalizedByteCode normbc, int arg, ByteCodeMode reg, ByteCodeModeWide wide, bool cannotThrow) { this.reg = reg; this.wide = wide; this.normbc = normbc; this.arg = arg; this.flags = ByteCodeFlags.FixedArg; if(cannotThrow) { this.flags |= ByteCodeFlags.CannotThrow; } data[(int)bc] = this; }
public void CompileByteCode(ByteCode b) { this.CompileByte((byte)b); }
internal static ByteCodeMode GetMode(ByteCode bc) { return data[(int)bc].reg; }
public void CompileByteCode(ByteCode b, byte arg1, byte arg2) { this.CompileByteCode(b); this.CompileByte(arg1); this.CompileByte(arg2); }
internal static ByteCodeModeWide GetWideMode(ByteCode bc) { return data[(int)bc].wide; }
public void CompileByteCodeAt(ByteCode b, int position) { this.bytecodes[position] = (byte)b; }
public StackSlot(ByteCode pushedBy) { this.PushedBy = new List<ByteCode>(1); this.PushedBy.Add(pushedBy); }
/// <summary> /// Analyse the instructions in the method code and convert them to a ByteCode list. /// </summary> private List<ByteCode> StackAnalysis() { // Map from instruction to bytecode. var instrToByteCode = new Dictionary<Instruction, ByteCode>(); // Create temporary structure for the stack analysis var body = new List<ByteCode>(codeAttr.Code.Length); foreach (var inst in codeAttr.Instructions) { var first = true; foreach (var byteCode in Create(inst, module)) { if (first) { instrToByteCode[inst] = byteCode; first = false; } body.Add(byteCode); } } // Connect bytecodes to the next for (var i = 0; i < body.Count - 1; i++) { body[i].Next = body[i + 1]; } var agenda = new Stack<ByteCode>(); var localVarsCount = codeAttr.MaxLocals; // methodDef.GetParametersLocalVariableSlots(); // All bytecodes that are the start of an exception handler. var exceptionHandlerStarts = new HashSet<ByteCode>(validExceptionHandlers.Select(eh => instrToByteCode[eh.Handler])); var exceptionTryStarts = new DefaultDictionary<ByteCode, List<ExceptionHandler>>(x => new List<ExceptionHandler>()); foreach (var eh in validExceptionHandlers) { exceptionTryStarts[instrToByteCode[eh.Start]].Add(eh); } // Add known states var ldExceptionByHandlerPc = new Dictionary<int, ByteCode>(); foreach (var ex in validExceptionHandlers) { ByteCode ldexception; if (ldExceptionByHandlerPc.TryGetValue(ex.HandlerPc, out ldexception)) { // Re-use ldexception (that we've created for the same handler PC for another exception handler before) } else { // No handler at handlerPc processed before, do that now var handlerStart = instrToByteCode[ex.Handler]; handlerStart.StackBefore = new StackSlot[0]; handlerStart.VariablesBefore = VariableSlot.MakeUnknownState(localVarsCount); { // Catch handlers start with the exeption on the stack ldexception = new ByteCode { Code = AstCode.Ldexception, Operand = ex.CatchType, PopCount = 0, PushCount = 1, Offset = handlerStart.Offset, Next = handlerStart, StackBefore = new StackSlot[0], VariablesBefore = handlerStart.VariablesBefore }; handlerStart.StackBefore = new[] { new StackSlot(new[] { ldexception }, null) }; } ldExceptionByHandlerPc[ex.HandlerPc] = ldexception; agenda.Push(handlerStart); } // Store ldexception by exception handler ldexceptions[ex] = ldexception; } // At the start of the method the stack is empty and all local variables have unknown state body[0].StackBefore = new StackSlot[0]; body[0].VariablesBefore = VariableSlot.MakeUnknownState(localVarsCount); agenda.Push(body[0]); // Process agenda while (agenda.Count > 0) { var byteCode = agenda.Pop(); // Calculate new stack var newStack = byteCode.CreateNewStack(); // Calculate new variable state var newVariableState = VariableSlot.CloneVariableState(byteCode.VariablesBefore); if (byteCode.IsVariableDefinition) { newVariableState[((LocalVariableReference)byteCode.Operand).Index] = new VariableSlot(new[] { byteCode }, false); } // After the leave, finally block might have touched the variables if (byteCode.Code == AstCode.Leave) { newVariableState = VariableSlot.MakeUnknownState(localVarsCount); } // Find all successors var branchTargets = FindBranchTargets(byteCode, instrToByteCode, exceptionHandlerStarts); // Apply the state to successors foreach (var branchTarget in branchTargets) { UpdateBranchTarget(byteCode, branchTarget, (branchTargets.Count == 1), newStack, newVariableState, agenda); } // Apply state to handlers when a branch target is the start of an exception handler foreach (var branchTarget in branchTargets.Where(exceptionTryStarts.ContainsKey)) { // The branch target is the start of a try block. UpdateTryStartBranchTarget(branchTarget, exceptionTryStarts[branchTarget], instrToByteCode, newVariableState, agenda); } } // Occasionally the compilers or obfuscators generate unreachable code (which might be intentonally invalid) // I believe it is safe to just remove it body.RemoveAll(b => b.StackBefore == null); // Generate temporary variables to replace stack foreach (var byteCode in body) { var argIdx = 0; var popCount = byteCode.PopCount ?? byteCode.StackBefore.Length; for (var i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) { var tmpVar = new AstGeneratedVariable(string.Format("arg_{0:X2}_{1}", byteCode.Offset, argIdx), null); byteCode.StackBefore[i] = new StackSlot(byteCode.StackBefore[i].Definitions, tmpVar); foreach (var pushedBy in byteCode.StackBefore[i].Definitions) { if (pushedBy.StoreTo == null) { pushedBy.StoreTo = new List<AstVariable>(1); } pushedBy.StoreTo.Add(tmpVar); } argIdx++; } } // Try to use single temporary variable insted of several if possible (especially useful for dup) // This has to be done after all temporary variables are assigned so we know about all loads foreach (var byteCode in body) { if ((byteCode.StoreTo == null) || (byteCode.StoreTo.Count <= 1)) continue; var locVars = byteCode.StoreTo; // For each of the variables, find the location where it is loaded - there should be preciesly one var loadedBy = locVars.Select(locVar => body.SelectMany(bc => bc.StackBefore).Single(s => s.LoadFrom == locVar)).ToList(); // We now know that all the variables have a single load, // Let's make sure that they have also a single store - us if (loadedBy.All(slot => (slot.Definitions.Length == 1) && (slot.Definitions[0] == byteCode))) { // Great - we can reduce everything into single variable var tmpVar = new AstGeneratedVariable(string.Format("expr_{0:X2}", byteCode.Offset), locVars.Select(x => x.OriginalName).FirstOrDefault()); byteCode.StoreTo = new List<AstVariable> { tmpVar }; foreach (var bc in body) { for (var i = 0; i < bc.StackBefore.Length; i++) { // Is it one of the variable to be merged? if (locVars.Contains(bc.StackBefore[i].LoadFrom)) { // Replace with the new temp variable bc.StackBefore[i] = new StackSlot(bc.StackBefore[i].Definitions, tmpVar); } } } } } // Split and convert the normal local variables ConvertLocalVariables(body); // Convert branch targets to labels foreach (var byteCode in body) { if (byteCode.Operand is Instruction[]) { byteCode.Operand = (from target in (Instruction[])byteCode.Operand select instrToByteCode[target].Label(true)).ToArray(); } else if (byteCode.Operand is Instruction) { byteCode.Operand = instrToByteCode[(Instruction)byteCode.Operand].Label(true); } else if (byteCode.Operand is LookupSwitchData) { var data = (LookupSwitchData) byteCode.Operand; byteCode.Operand = data.Pairs.Select(x => new AstLabelKeyPair(instrToByteCode[x.Target].Label(true), x.Match)).ToArray(); } } // Convert parameters to ILVariables ConvertParameters(body); // Replace temporary opcodes foreach (var byteCode in body) { switch (byteCode.Code) { case AstCode.Dup_x1: case AstCode.Dup_x2: case AstCode.Dup2: case AstCode.Dup2_x1: case AstCode.Dup2_x2: case AstCode.Swap: byteCode.Code = AstCode.Dup; break; case AstCode.Pop2: byteCode.Code = AstCode.Pop; break; } } return body; }