/// <summary> /// Process an unplugged IL chunk. /// </summary> /// <param name="aChunk">The chunk to process.</param> /// <returns>The resulting ASM chunk or null if processing failed.</returns> private ASMChunk ProcessUnpluggedILChunk(ILChunk aChunk) { ASMChunk result = new ASMChunk(); string methodSignature = Utils.GetMethodSignature(aChunk.Method); string MethodID = TheScannerState.GetMethodID(aChunk.Method); //Add the method to the debug database DB_Method dbMethod = null; if (TheSettings.DebugBuild) { dbMethod = new DB_Method(); dbMethod.Id = MethodID; dbMethod.MethodSignature = methodSignature; dbMethod.Plugged = false; dbMethod.ASMStartPos = -1; dbMethod.ASMEndPos = -1; result.DBMethod = dbMethod; DebugDatabase.AddMethod(dbMethod); } result.ASM.AppendLine("; IL Scanned Method"); //DEBUG INFO result.ASM.AppendLine("; " + methodSignature); //DEBUG INFO //Outputs the label that is the start of this method result.ASM.AppendLine(MethodID + ":"); //Construct the stack frame state for the start of this method StackFrame currFrame = new StackFrame(); TheScannerState.CurrentStackFrame = currFrame; TheScannerState.CurrentILChunk = aChunk; //Insert the method start op //See comments on method start op for what it does etc. int addASMLineNum = 0; { //TODO - Add DBILOpInfo for MethodStart op result.ASM.AppendLine("; MethodStart"); // DEBUG INFO //Get the ASM of the op string asm = MethodStartOp.Convert(null, TheScannerState); //Split the ASM into lines string[] asmLines = asm.Replace("\r", "").Split('\n'); //This code inserts the ASM line by line, outputting labels for // each line of ASM. //Start at any existing offset for the current op // - prevents duplication of labels int asmLineNum = addASMLineNum; //For each line of ASM: foreach (string asmLine in asmLines) { //If the line isn't already a label: if (!asmLine.Split(';')[0].Trim().EndsWith(":")) { //Output the ASM label result.ASM.AppendLine(string.Format("{0}.IL_{1}_{2}:", MethodID, 0, asmLineNum)); } //Append the ASM result.ASM.AppendLine(asmLine); //Increment the ASM line num asmLineNum++; } //Set the overall ASM line offset for current op to final // offset addASMLineNum = asmLineNum; } #region GC //If Garbage Collection code should be added to this method: if (aChunk.ApplyGC) { //Inc ref count of all args passed to the method // - see ILReader for GC cleanup / dec ref count (use search: "Dec ref count" exc. quotes) List<Type> allParams = aChunk.Method.GetParameters() .Select(x => x.ParameterType) .ToList(); //Non-static methods have first arg as instance reference if (!aChunk.Method.IsStatic) { allParams.Insert(0, aChunk.Method.DeclaringType); } //If the number of args for this method > 0 if (allParams.Count > 0) { //Store the new ASM to append afterwards string asm = ""; //For each arg: for (int i = 0; i < allParams.Count; i++) { Type aVarType = allParams[i]; //If the arg is of a type managed by the GC if (Utils.IsGCManaged(aVarType)) { //Load the arg asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Ldarg] .Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Ldarg, ValueBytes = BitConverter.GetBytes(i) }, TheScannerState); //Call increment ref count asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call] .Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.IncrementRefCountMethod }, TheScannerState); } } //Append the new ASM - see MethodStart for explanation above string[] asmLines = asm.Replace("\r", "").Split('\n'); int asmLineNum = addASMLineNum; foreach (string asmLine in asmLines) { if (!asmLine.Split(';')[0].Trim().EndsWith(":")) { result.ASM.AppendLine(string.Format("{0}.IL_{1}_{2}:", MethodID, 0, asmLineNum)); } result.ASM.AppendLine(asmLine); asmLineNum++; } addASMLineNum = asmLineNum; } } #endregion //Stores the label of the last IL op that was a debug NOP // - See documentation for use of Debug NOPs / INT3s string debugNopLabel = null; //Stores the start position of the current IL op in the ASM int ASMStartPos = 0; //Get a list of all the IL ops List<ILOpInfo> TheILOpInfos = aChunk.ILOpInfos; //For each IL op: foreach (ILOpInfo anILOpInfo in TheILOpInfos) { //We surround all this with a try-catch block // so that if processing the current IL op causes an exception, we don't abort processing // of the entire method. We will end up with invalid ASM code in the output, but at least // the developer will receive all the errors with their code not just one per build. try { #region Debug //Create the debug DB_ILOpInfo dbILOpInfo = null; if (TheSettings.DebugBuild) { dbILOpInfo = new DB_ILOpInfo(); dbILOpInfo.Id = Guid.NewGuid(); dbILOpInfo.MethodID = MethodID; dbILOpInfo.OpCode = anILOpInfo.opCode.Value; dbILOpInfo.CustomOpCode = 0; dbILOpInfo.NextPosition = anILOpInfo.NextPosition; dbILOpInfo.Position = anILOpInfo.Position; if (anILOpInfo.ValueBytes != null) { if (anILOpInfo.ValueBytes.Length < 8000) { dbILOpInfo.ValueBytes = new System.Data.Linq.Binary(anILOpInfo.ValueBytes); } else { OutputWarning(new Exception("ValueBytes not set because data too large. Op: " + anILOpInfo.opCode.Name + ", Op offset: " + anILOpInfo.Position.ToString("X2") + "\r\n" + methodSignature)); anILOpInfo.ValueBytes = null; } } dbILOpInfo.ASMInsertLabel = true; anILOpInfo.DBILOpInfo = dbILOpInfo; dbILOpInfo.ASMStartPos = anILOpInfo.ASMStartPos = ASMStartPos; } #endregion //Stores all the new ASM for this IL op // - This ASM gets appended to the result at the end of the try-section // thus it only gets appended if there are no processing errors. string asm = ""; #region Exception Handling //We needs to check if we are in try, catch or finally blocks (a.k.a critical sections): ExceptionHandledBlock exBlock = aChunk.GetExceptionHandledBlock(anILOpInfo.Position); //If we are in a critical section: if (exBlock != null) { //If this IL op is the first op of a try-section: if (exBlock.Offset == anILOpInfo.Position) { //Insert the start of try-block //Consists of adding a new ExceptionHandlerInfos // built from the info in exBlock so we: // - Add infos for all finally blocks first // - Then add infos for all catch blocks // Since finally code is always run after catch code in C#, // by adding catch handlers after finally handlers, they // appear as the inner-most exception handlers and so get // run before finally handlers. //To add a new ExceptionHandlerInfo we must set up args for // calling Exceptions.AddExceptionHandlerInfo: // 1. We load a pointer to the handler // - This is calculated from an offset from the start of the function to the handler // 2. We load a pointer to the filter // - This is calculated from an offset from the start of the function to the filter // Note: Filter has not been implemented as an actual filter. // At the moment, 0x00000000 indicates a finally handler, // 0xFFFFFFFF indicates no filter block // (i.e. an unfiltered catch handler) // 0xXXXXXXXX has undetermined behaviour! result.ASM.AppendLine("; Try-block start"); // DEBUG INFO //For each finally block: foreach (FinallyBlock finBlock in exBlock.FinallyBlocks) { // 1. Load the pointer to the handler code: asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Ldftn].Convert(new ILOpInfo() { //Load function pointer op opCode = System.Reflection.Emit.OpCodes.Ldftn, //Load a pointer to the current method MethodToCall = aChunk.Method, //At this offset: The first IL op of the finally block LoadAtILOffset = finBlock.Offset }, TheScannerState); // 2. Load the pointer to the filter code: asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Ldc_I4].Convert(new ILOpInfo() { //Load a constant value opCode = System.Reflection.Emit.OpCodes.Ldc_I4, //The value is 0x00000000 - since this is a finally handler ValueBytes = BitConverter.GetBytes(0x00000000) }, TheScannerState); // Call Exceptions.AddExceptionHandlerInfo asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.AddExceptionHandlerInfoMethod }, TheScannerState); } foreach (CatchBlock catchBlock in exBlock.CatchBlocks) { // 1. Load the pointer to the handler code: asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Ldftn].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Ldftn, MethodToCall = aChunk.Method, LoadAtILOffset = catchBlock.Offset }, TheScannerState); //TODO - We need to sort out a way of doing filter functions // 2. Load the pointer to the filter code: asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Ldc_I4].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Ldc_I4, //The value is 0xFFFFFFFF - since this is a catch handler (and filters aren't implemented yet!) ValueBytes = BitConverter.GetBytes(0xFFFFFFFF) }, TheScannerState); // Call Exceptions.AddExceptionHandlerInfo asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.AddExceptionHandlerInfoMethod }, TheScannerState); } } //We need to check if the current IL op is the first op // of a catch block. This is because C# catch blocks are // compiled with a "pop" instruction as the first op if // the catch block has no filter. Presumably, this is because // C# would be expecting the current exception to be on the // stack. //Get any catch blocks we are currently in. List<CatchBlock> potCatchBlocks = (from catchBlocks in exBlock.CatchBlocks where (catchBlocks.Offset <= anILOpInfo.Position && catchBlocks.Offset + catchBlocks.Length >= anILOpInfo.Position) select catchBlocks).ToList(); //If we are in a catch block: if (potCatchBlocks.Count > 0) { CatchBlock catchBlock = potCatchBlocks.First(); //If this is the first op of the catch block: if (catchBlock.Offset == anILOpInfo.Position) { result.ASM.AppendLine("; Catch-block start"); // DEBUG INFO //Ignore the first pop-op of the catch block if ((int)anILOpInfo.opCode.Value == (int)ILOps.ILOp.OpCodes.Pop) { //Do an immediate append rather than using the "asm" variable as we will be calling // "continue" - see below. //For debug, we must insert this op's label in to the ASM. string label = string.Format("{0}.IL_{1}_{2}", MethodID, anILOpInfo.Position, 0); result.ASM.AppendLine(label + ":"); result.ASM.AppendLine("; Skipped first pop of catch handler"); // DEBUG INFO //End processing of this op by skipping to the next! continue; } } } //We want to be able to output some debug info if we are starting a finally block // just so that our ASM is more intellgible / debuggable. List<FinallyBlock> potFinallyBlocks = (from finallyBlocks in exBlock.FinallyBlocks where (finallyBlocks.Offset <= anILOpInfo.Position && finallyBlocks.Offset + finallyBlocks.Length >= anILOpInfo.Position) select finallyBlocks).ToList(); if (potFinallyBlocks.Count > 0) { FinallyBlock finallyBlock = potFinallyBlocks.First(); if (finallyBlock.Offset == anILOpInfo.Position) { result.ASM.AppendLine("; Finally-block start"); // DEBUG INFO } } } #endregion #region Debug if (TheSettings.DebugBuild) { //If this chunk hasn't been marked as no-debug ops: if (!aChunk.NoDebugOps) { // Insert a debug nop just before the op // - This allows us to step an entire IL op at a time rather than just one // line of ASM at a time. result.ASM.AppendLine("; Debug Nop"); // DEBUG INFO // Insert the nop asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Nop].Convert(anILOpInfo, TheScannerState); //See above for how this append code works string[] asmLines = asm.Replace("\r", "").Split("\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); int asmLineNum = addASMLineNum; //Clear the current debug nop label so we can get the first label // of the new ASM and use it as the debug nop label for this IL op. debugNopLabel = null; foreach (string asmLine in asmLines) { if (!asmLine.Split(';')[0].Trim().EndsWith(":")) { string label = string.Format("{0}.IL_{1}_{2}", MethodID, anILOpInfo.Position, asmLineNum); //If we do not currently have a debug nop label for this IL op: if (debugNopLabel == null) { //Set the current debug nop label. debugNopLabel = label; } result.ASM.AppendLine(label + ":"); } result.ASM.AppendLine(asmLine); asmLineNum++; } addASMLineNum = asmLineNum; //We just added all the ASM for this op generated so far, so clean the "asm" variable asm = ""; } //Set the debug nop label for this IL op as the last inserted debug nop label dbILOpInfo.DebugOpMeta = "DebugNopLabel=" + debugNopLabel + ";"; } #endregion //Insert some method end code just before the ret op. if ((int)anILOpInfo.opCode.Value == (int)ILOps.ILOp.OpCodes.Ret) { //Method End op inserts code such as storing the return value // in the return argument and restoring the stack base pointer result.ASM.AppendLine("; MethodEnd"); // DEBUG INFO asm += "\r\n" + MethodEndOp.Convert(null, TheScannerState); } if ((int)anILOpInfo.opCode.Value == (int)ILOps.ILOp.OpCodes.Leave || (int)anILOpInfo.opCode.Value == (int)ILOps.ILOp.OpCodes.Leave_S) #region Exception Handling { //Leave is for leaving a critical section //We handle it by a higher-level implementation rather than // leaving it to each architecture to implement. //Leave is handled by inserting a call to the Exceptions.HandleLeave method //This value is an offset from the next IL op to the line to continue execution at // if there isno current exception and no finally handler. int ILOffset = 0; if ((int)anILOpInfo.opCode.Value == (int)ILOps.ILOp.OpCodes.Leave) { ILOffset = BitConverter.ToInt32(anILOpInfo.ValueBytes, 0); } else { ILOffset = (int)anILOpInfo.ValueBytes[0]; } //Get the IL number of the next op int startILNum = anILOpInfo.NextPosition; //Add the offset to get the IL op num to jump to int ILNumToGoTo = startILNum + ILOffset; // Load the address of the op to continue execution at if there is no exception and // no finally handler. asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Ldftn].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Ldftn, MethodToCall = aChunk.Method, LoadAtILOffset = ILNumToGoTo }, TheScannerState); // Call Exceptions.HandleLeave asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.ExceptionsHandleLeaveMethod }, TheScannerState); } else if ((int)anILOpInfo.opCode.Value == (int)ILOps.ILOp.OpCodes.Endfinally) { //Endfinally is for leaving a (critical) finally section //We handle it by a higher-level implementation rather than // leaving it to each architecture to implement. //Endfinally is handled by inserting a call to the Exceptions.HandleEndFinally method asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.ExceptionsHandleEndFinallyMethod }, TheScannerState); } #endregion else if ((int)anILOpInfo.opCode.Value == (int)ILOps.ILOp.OpCodes.Castclass) { //This IL op is ignored for now. We assume the the Microsoft compiler (csc.exe) //makes the correct casting checks etc //And even if it doesn't, at the kernel level it is useful to be able to play // tricks with converting objects to/from MS types and custom kernel types // e.g. System.String to Kernel.FOS_System.String } else { //Indicates whether the IL op should actually be processed or not. // - At this stage, there are a few cases when the Il op should not be // processed but the new ASM should still be appended. bool processOp = true; #region Special-case op handling //If the op is a call: if ((int)anILOpInfo.opCode.Value == (int)ILOps.ILOp.OpCodes.Call) { //If the method to call is actually in mscorlib: if (anILOpInfo.MethodToCall != null && anILOpInfo.MethodToCall.DeclaringType.AssemblyQualifiedName. Contains("mscorlib")) { //We do not want to process ops which attempt to call methods in mscorlib! processOp = false; //We do not allow calls to methods declared in MSCorLib. //Some of these calls can just be ignored (e.g. GetTypeFromHandle is // called by typeof operator). //Ones which can't be ignored, will result in an error...by virtue of // the fact that they were ignored when they were required. //But just to make sure we save ourselves a headache later when // runtime debugging, output a message saying we ignored the call. result.ASM.AppendLine("; Call to method defined in MSCorLib ignored:"); // DEBUG INFO result.ASM.AppendLine("; " + anILOpInfo.MethodToCall.DeclaringType.FullName + "." + anILOpInfo.MethodToCall.Name); // DEBUG INFO //If the method is a call to a constructor in MsCorLib: if (anILOpInfo.MethodToCall is ConstructorInfo) { //Then we can presume it was a call to a base-class constructor (e.g. the Object constructor) // and so we just need to remove any args that were loaded onto the stack. result.ASM.AppendLine("; Method to call was constructor so removing params"); // DEBUG INFO //Remove args from stack //If the constructor was non-static, then the first arg is the instance reference. if (!anILOpInfo.MethodToCall.IsStatic) { asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Pop].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Pop }, TheScannerState); } foreach (ParameterInfo anInfo in anILOpInfo.MethodToCall.GetParameters()) { asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Pop].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Pop }, TheScannerState); } } } //If the method to call wasn't to a method in MsCorLib, we may need to set the method to call: else if(anILOpInfo.SetToGCDecRefCountMethod) { anILOpInfo.MethodToCall = TheScannerState.DecrementRefCountMethod; } } #endregion //If the op should be processed: if(processOp) { #region GC //GC requires us to decrement ref count of any field/local/arg // that is about to be overwritten //NewILOps - Unimplemented and new IL Ops need checking and below // adding if necessary if (aChunk.ApplyGC) { bool IncRefCount = false; switch ((ILOps.ILOp.OpCodes)anILOpInfo.opCode.Value) { case ILOps.ILOp.OpCodes.Stsfld: { int metadataToken = Utils.ReadInt32(anILOpInfo.ValueBytes, 0); FieldInfo theField = TheScannerState.CurrentILChunk.Method.Module.ResolveField(metadataToken); if (Utils.IsGCManaged(theField.FieldType)) { asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Ldsfld].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Ldsfld, ValueBytes = anILOpInfo.ValueBytes }, TheScannerState); asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.DecrementRefCountMethod }, TheScannerState); IncRefCount = true; } } break; case ILOps.ILOp.OpCodes.Stloc: { UInt16 localIndex = (UInt16)Utils.ReadInt16(anILOpInfo.ValueBytes, 0); if (Utils.IsGCManaged(aChunk.LocalVariables[localIndex].TheType)) { asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Ldloc].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Ldloc, ValueBytes = anILOpInfo.ValueBytes }, TheScannerState); asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.DecrementRefCountMethod }, TheScannerState); IncRefCount = true; } } break; case ILOps.ILOp.OpCodes.Stloc_0: { if (Utils.IsGCManaged(aChunk.LocalVariables[0].TheType)) { asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Ldloc_0].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Ldloc_0 }, TheScannerState); asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.DecrementRefCountMethod }, TheScannerState); IncRefCount = true; } } break; case ILOps.ILOp.OpCodes.Stloc_1: { if (Utils.IsGCManaged(aChunk.LocalVariables[1].TheType)) { asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Ldloc_1].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Ldloc_1 }, TheScannerState); asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.DecrementRefCountMethod }, TheScannerState); IncRefCount = true; } } break; case ILOps.ILOp.OpCodes.Stloc_2: { if (Utils.IsGCManaged(aChunk.LocalVariables[2].TheType)) { asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Ldloc_2].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Ldloc_2 }, TheScannerState); asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.DecrementRefCountMethod }, TheScannerState); IncRefCount = true; } } break; case ILOps.ILOp.OpCodes.Stloc_3: { if (Utils.IsGCManaged(aChunk.LocalVariables[3].TheType)) { asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Ldloc_3].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Ldloc_3 }, TheScannerState); asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.DecrementRefCountMethod }, TheScannerState); IncRefCount = true; } } break; case ILOps.ILOp.OpCodes.Stloc_S: { UInt16 localIndex = (UInt16)anILOpInfo.ValueBytes[0]; if (Utils.IsGCManaged(aChunk.LocalVariables[localIndex].TheType)) { asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Ldloc_S].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Ldloc_S, ValueBytes = anILOpInfo.ValueBytes }, TheScannerState); asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.DecrementRefCountMethod }, TheScannerState); IncRefCount = true; } } break; case ILOps.ILOp.OpCodes.Stfld: { int metadataToken = Utils.ReadInt32(anILOpInfo.ValueBytes, 0); FieldInfo theField = aChunk.Method.Module.ResolveField(metadataToken); if (Utils.IsGCManaged(theField.FieldType)) { // Items on stack: // - Object reference // - (New) Value to store // // We want to load the current value of the field // for which we must duplicate the object ref // But first, we must remove the (new) value to store // off the stack, while also storing it to put back // on the stack after so the store can continue // // So: // 1. Switch value to store and object ref on stack // 3. Duplicate the object ref // 4. Load the field value // 5. Call dec ref count // 6. Switch value to store and object ref back again //USE A SWITCH STACK ITEMS OP!! asm += "\r\n" + StackSwitchOp.Convert(new ILOpInfo() { }, TheScannerState); StackItem switchItem1 = TheScannerState.CurrentStackFrame.Stack.Pop(); StackItem switchItem2 = TheScannerState.CurrentStackFrame.Stack.Pop(); TheScannerState.CurrentStackFrame.Stack.Push(switchItem1); TheScannerState.CurrentStackFrame.Stack.Push(switchItem2); asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Dup].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Dup }, TheScannerState); asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Ldfld].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Ldfld, ValueBytes = anILOpInfo.ValueBytes }, TheScannerState); asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.DecrementRefCountMethod }, TheScannerState); asm += "\r\n" + StackSwitchOp.Convert(new ILOpInfo() { }, TheScannerState); switchItem1 = TheScannerState.CurrentStackFrame.Stack.Pop(); switchItem2 = TheScannerState.CurrentStackFrame.Stack.Pop(); TheScannerState.CurrentStackFrame.Stack.Push(switchItem1); TheScannerState.CurrentStackFrame.Stack.Push(switchItem2); IncRefCount = true; } } break; case ILOps.ILOp.OpCodes.Stelem: case ILOps.ILOp.OpCodes.Stelem_Ref: { bool doDecrement = false; bool isRefOp = false; if ((ILOps.ILOp.OpCodes)anILOpInfo.opCode.Value == ILOps.ILOp.OpCodes.Stelem_Ref) { doDecrement = TheScannerState.CurrentStackFrame.Stack.Peek().isGCManaged; isRefOp = true; } else { int metadataToken = Utils.ReadInt32(anILOpInfo.ValueBytes, 0); Type elementType = aChunk.Method.Module.ResolveType(metadataToken); doDecrement = Utils.IsGCManaged(elementType); } if (doDecrement) { // Items on stack: // - Array reference // - Index // - (New) Value to store // // We want to load the current value of the element at Index in the array // for which we must duplicate the array ref and index // But first, we must remove the (new) value to store // off the stack, while also storing it to put back // on the stack after so the store can continue // // So: // 1. Switch (rotate) 1 times the top 3 values so that index is on top // 2. Duplicate the index // 3. Switch (rotate) 2 times the top 4 values so that array ref is on top // 4. Duplicate the array ref // 5. Switch (rotate) 4 times the top 5 values so that duplicate array ref and index are on top // 6. Do LdElem op to load existing element value // 7. Call GC.DecrementRefCount // 8. Switch (rotate) 1 times the top 3 values so that the stack is in its original state // (9. Continue to increment ref count as normal) // // The following is a diagram of the stack manipulation occurring here: // Key: A=Array ref, I=Index, V=Value to store, E=Loaded element // Top-most stack item appears last // // 1) Rotate x 1 2) Duplicate 3) Rot x 2 4) Dup // A,I,V ---------> V,A,I ---------> V,A,I,I ---------> I,I,V,A ---------> I,I,V,A,A // // // 5) Rot x 4 6) Ldelem 7) Call GC (Dec) // I,I,V,A,A ---------> I,V,A,A,I ---------> I,V,A,E ---------> I,V,A // // // 8) Rot x 1 9) Dup 10) Call GC (Inc) // I,V,A ---------> A,I,V ---------> A,I,V,V ---------> A,I,V #region 1. asm += "\r\n" + StackSwitchOp.Convert(new ILOpInfo() { ValueBytes = BitConverter.GetBytes(3) }, TheScannerState); rotateStackItems(3, 1); #endregion #region 2. asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Dup].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Dup }, TheScannerState); #endregion #region 3. asm += "\r\n" + StackSwitchOp.Convert(new ILOpInfo() { ValueBytes = BitConverter.GetBytes(4) }, TheScannerState); asm += "\r\n" + StackSwitchOp.Convert(new ILOpInfo() { ValueBytes = BitConverter.GetBytes(4) }, TheScannerState); rotateStackItems(4, 2); #endregion #region 4. asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Dup].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Dup }, TheScannerState); #endregion #region 5. asm += "\r\n" + StackSwitchOp.Convert(new ILOpInfo() { ValueBytes = BitConverter.GetBytes(5) }, TheScannerState); asm += "\r\n" + StackSwitchOp.Convert(new ILOpInfo() { ValueBytes = BitConverter.GetBytes(5) }, TheScannerState); asm += "\r\n" + StackSwitchOp.Convert(new ILOpInfo() { ValueBytes = BitConverter.GetBytes(5) }, TheScannerState); asm += "\r\n" + StackSwitchOp.Convert(new ILOpInfo() { ValueBytes = BitConverter.GetBytes(5) }, TheScannerState); rotateStackItems(5, 4); #endregion #region 6. asm += "\r\n" + TargetILOps[isRefOp ? ILOps.ILOp.OpCodes.Ldelem_Ref : ILOps.ILOp.OpCodes.Ldelem].Convert(new ILOpInfo() { opCode = isRefOp ? System.Reflection.Emit.OpCodes.Ldelem_Ref : System.Reflection.Emit.OpCodes.Ldelem, ValueBytes = anILOpInfo.ValueBytes, Position = anILOpInfo.Position }, TheScannerState); #endregion #region 7. asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.DecrementRefCountMethod }, TheScannerState); #endregion #region 8. asm += "\r\n" + StackSwitchOp.Convert(new ILOpInfo() { ValueBytes = BitConverter.GetBytes(3) }, TheScannerState); rotateStackItems(3, 1); #endregion IncRefCount = true; } } break; case ILOps.ILOp.OpCodes.Starg: { Int16 index = Utils.ReadInt16(anILOpInfo.ValueBytes, 0); index -= (Int16)(!aChunk.Method.IsStatic ? 1 : 0); if (Utils.IsGCManaged(aChunk.Method.GetParameters()[index].ParameterType)) { asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Ldarg].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Ldarg, ValueBytes = anILOpInfo.ValueBytes }, TheScannerState); asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.DecrementRefCountMethod }, TheScannerState); IncRefCount = true; } } break; case ILOps.ILOp.OpCodes.Starg_S: { Int16 index = (Int16)anILOpInfo.ValueBytes[0]; index -= (Int16)(!aChunk.Method.IsStatic ? 1 : 0); if (Utils.IsGCManaged(aChunk.Method.GetParameters()[index].ParameterType)) { asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Ldarg_S].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Ldarg_S, ValueBytes = anILOpInfo.ValueBytes }, TheScannerState); asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.DecrementRefCountMethod }, TheScannerState); IncRefCount = true; } } break; } if(IncRefCount && !TheScannerState.CurrentStackFrame.Stack.Peek().isNewGCObject) { TheScannerState.CurrentStackFrame.Stack.Peek().isNewGCObject = false; asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Dup].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Dup }, TheScannerState); asm += "\r\n" + TargetILOps[ILOps.ILOp.OpCodes.Call].Convert(new ILOpInfo() { opCode = System.Reflection.Emit.OpCodes.Call, MethodToCall = TheScannerState.IncrementRefCountMethod }, TheScannerState); } } #endregion result.ASM.AppendLine("; " + anILOpInfo.opCode.Name); //DEBUG INFO ILOps.ILOp TheIlOp = TargetILOps[(ILOps.ILOp.OpCodes)anILOpInfo.opCode.Value]; #region Debug if (TheSettings.DebugBuild) { if (anILOpInfo.opCode.Name == "nop" && !aChunk.NoDebugOps) { anILOpInfo.IsDebugOp = dbILOpInfo.IsDebugOp = true; dbILOpInfo.DebugOpMeta += "breakpoint;"; } else { dbILOpInfo.IsDebugOp = false; } } #endregion // Convert the IL op to ASM! asm += "\r\n" + TheIlOp.Convert(anILOpInfo, TheScannerState); } } { // Append the new ASm to the result ASM :) // See above (MethodStart op) for thow this code works. string[] asmLines = asm.Replace("\r", "").Split("\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); int asmLineNum = addASMLineNum; foreach (string asmLine in asmLines) { if (!asmLine.Split(';')[0].Trim().EndsWith(":")) { result.ASM.AppendLine(string.Format("{0}.IL_{1}_{2}:", MethodID, anILOpInfo.Position, asmLineNum)); } result.ASM.AppendLine(asmLine); asmLineNum++; } addASMLineNum = asmLineNum; } #region Debug if (TheSettings.DebugBuild) { dbILOpInfo.ASMEndPos = anILOpInfo.ASMEndPos = result.ASM.Length; DebugDatabase.AddILOpInfo(dbILOpInfo); } #endregion } catch(KeyNotFoundException) { result.ASM.AppendLine("; ERROR! Target architecture does not support this IL op."); //DEBUG INFO OutputError(new Exception("Target architecture does not support ILOp! Op type: " + anILOpInfo.opCode.Name + ", Op offset: " + anILOpInfo.Position.ToString("X2") + "\r\n" + methodSignature)); } catch (Exception ex) { result.ASM.AppendLine("; ERROR! ILScanner failed to process."); //DEBUG INFO OutputError(new Exception("Could not process an ILOp! Op type: " + anILOpInfo.opCode.Name + ", Op offset: " + anILOpInfo.Position.ToString("X2") + "\r\n" + methodSignature, ex)); } ASMStartPos = result.ASM.Length; addASMLineNum = 0; } #region Debug if (TheSettings.DebugBuild) { //Add debug info for local variables of this method int locIndex = 0; foreach (LocalVariable localItem in aChunk.LocalVariables) { DB_LocalVariable dbLocalVar = new DB_LocalVariable(); dbLocalVar.BytesSize = localItem.sizeOnStackInBytes; dbLocalVar.Id = Guid.NewGuid(); dbLocalVar.Index = locIndex; //We always call ProcessType just in case we missed a type // when loading assemblies dbLocalVar.TypeID = ProcessType(localItem.TheType).Id; dbLocalVar.MethodID = dbMethod.Id; DebugDatabase.AddLocalVariable(dbLocalVar); locIndex++; } } #endregion return result; }
/// <summary> /// Process a plugged IL chunk. /// </summary> /// <param name="aChunk">The chunk to process.</param> /// <returns>The resulting ASM chunk or null if processing failed.</returns> /// <exception cref="System.IO.FileNotFoundException"> /// Thrown if the plug file for the ILChunk could not be found. /// </exception> private ASMChunk ProcessPluggedILChunk(ILChunk aChunk) { string methodSignature = Utils.GetMethodSignature(aChunk.Method); ASMChunk result = new ASMChunk(); DB_Method dbMethod = null; if (TheSettings.DebugBuild) { //Add the method to the debug database // (method is marked as plugged) dbMethod = new DB_Method(); dbMethod.Id = TheScannerState.GetMethodID(aChunk.Method); dbMethod.MethodSignature = methodSignature; dbMethod.Plugged = true; dbMethod.ASMStartPos = -1; dbMethod.ASMEndPos = -1; result.DBMethod = dbMethod; DebugDatabase.AddMethod(dbMethod); } //We do not want to output this initial comment stuff in front of // the Multiboot signature! //The Multiboot signature has to be the first stuff in the final // ASM file if (aChunk.PlugASMFilePath == null || !aChunk.PlugASMFilePath.Contains("Multiboot")) { if (TheSettings.DebugBuild) { result.ASM.AppendLine("; Plugged Method"); //DEBUG INFO result.ASM.AppendLine("; Method Signature : " + methodSignature); //DEBUG INFO result.ASM.AppendLine("; Method ID : " + dbMethod.Id); //DEBUG INFO } if (aChunk.PlugASMFilePath == null) { result.ASM.AppendLine("; No plug file loaded as ASM path was null"); return result; } else { result.ASM.AppendLine("; " + aChunk.PlugASMFilePath); } } result.ASM.Append(PlugLoader.LoadPlugASM(aChunk.PlugASMFilePath, TheSettings)); if (result.ASM == null) { throw new System.IO.FileNotFoundException("Failed to load ASM plug file! Path: " + aChunk.PlugASMFilePath); } return result; }
/// <summary> /// Process any IL chunk (plugged or unplugged). /// </summary> /// <param name="aChunk">The IL chunk to process.</param> /// <returns>The resulting ASM chunk or null if processing failed.</returns> public ASMChunk ProcessILChunk(ILChunk aChunk) { ASMChunk result = null; //Process the chunk from IL to ASM if (aChunk.Plugged) { result = ProcessPluggedILChunk(aChunk); } else { result = ProcessUnpluggedILChunk(aChunk); } //Result could be null if processing failed if (result != null) { result.SequencePriority = aChunk.SequencePriority; //Add arguments info to debug database int argIndex = 0; if (TheSettings.DebugBuild) { if (!aChunk.Method.IsStatic) { DB_Argument dbArgVar = new DB_Argument(); dbArgVar.BytesSize = Utils.GetNumBytesForType(aChunk.Method.DeclaringType); dbArgVar.Id = Guid.NewGuid(); dbArgVar.Index = argIndex; dbArgVar.TypeID = ProcessType(aChunk.Method.DeclaringType).Id; dbArgVar.MethodID = TheScannerState.GetMethodID(aChunk.Method); DebugDatabase.AddArgument(dbArgVar); argIndex++; } ParameterInfo[] args = aChunk.Method.GetParameters(); foreach (ParameterInfo argItem in args) { DB_Argument dbArgVar = new DB_Argument(); dbArgVar.BytesSize = Utils.GetNumBytesForType(argItem.ParameterType); dbArgVar.Id = Guid.NewGuid(); dbArgVar.Index = argIndex; dbArgVar.TypeID = ProcessType(argItem.ParameterType).Id; dbArgVar.MethodID = TheScannerState.GetMethodID(aChunk.Method); DebugDatabase.AddArgument(dbArgVar); argIndex++; } } else { //Must still process types info for release builds if (!aChunk.Method.IsStatic) { ProcessType(aChunk.Method.DeclaringType); } ParameterInfo[] args = aChunk.Method.GetParameters(); foreach (ParameterInfo argItem in args) { ProcessType(argItem.ParameterType); } } //Must add the return arg if (TheSettings.DebugBuild) { ParameterInfo argItem = (aChunk.Method.IsConstructor || aChunk.Method is ConstructorInfo ? null : ((MethodInfo)aChunk.Method).ReturnParameter); if (argItem == null) { //If arg item is null, then return type is void //We still add info about the return value // so the debugger can make sense of what is happening // without unnecessary assumptions DB_Argument dbArgVar = new DB_Argument(); dbArgVar.BytesSize = Utils.GetNumBytesForType(typeof(void)); dbArgVar.Id = Guid.NewGuid(); dbArgVar.Index = argIndex; dbArgVar.TypeID = TheScannerState.GetTypeID(typeof(void)); dbArgVar.MethodID = TheScannerState.GetMethodID(aChunk.Method); dbArgVar.IsReturnArg = true; DebugDatabase.AddArgument(dbArgVar); } else { DB_Argument dbArgVar = new DB_Argument(); dbArgVar.BytesSize = Utils.GetNumBytesForType(argItem.ParameterType); dbArgVar.Id = Guid.NewGuid(); dbArgVar.Index = argIndex; dbArgVar.TypeID = ProcessType(argItem.ParameterType).Id; dbArgVar.MethodID = TheScannerState.GetMethodID(aChunk.Method); dbArgVar.IsReturnArg = true; DebugDatabase.AddArgument(dbArgVar); } } else { ParameterInfo argItem = (aChunk.Method.IsConstructor || aChunk.Method is ConstructorInfo ? null : ((MethodInfo)aChunk.Method).ReturnParameter); if (argItem != null) { ProcessType(argItem.ParameterType); } } } return result; }
/// <summary> /// Process an unplugged method. /// </summary> /// <param name="aMethod">The method to process.</param> /// <param name="staticConstructorDependencyRoot">Null if method scanning is not a static constructor. Otherwise, the root node that represents the static constructor being scanned.</param> /// <returns>A new ILChunk with ILOpInfos and common attribites loaded. Null if any errors occur.</returns> /// <exception cref="System.Exception"> /// Thrown when an unrecognised operand type is read. Can occur if MSBuild has been /// updated/extended from when the kernel compiler was last updated. /// </exception> public ILChunk ProcessUnpluggedMethod(MethodBase aMethod, StaticConstructorDependency staticConstructorDependencyRoot = null) { ILChunk result = new ILChunk() { Plugged = false, Method = aMethod }; //Pre-process common method attributes so we get information such as //whether to apply GC or not etc. ProcessCommonMethodAttributes(aMethod, result); //Get the method body which can then be used to get locals info and //IL bytes that are the IL code. MethodBody theMethodBody = aMethod.GetMethodBody(); //Method body for something like [DelegateType].Invoke() // is null if (theMethodBody == null) { //Just return empty method return result; } //For each local variable in this method foreach (LocalVariableInfo aLocal in theMethodBody.LocalVariables) { //Add it to our list of locals with some common information pre-worked out LocalVariable localItem = new LocalVariable() { sizeOnStackInBytes = Utils.GetNumBytesForType(aLocal.LocalType), isFloat = Utils.IsFloat(aLocal.LocalType), TheType = aLocal.LocalType, isGCManaged = Utils.IsGCManaged(aLocal.LocalType) }; result.LocalVariables.Add(localItem); } //Used later to store location and length of the cleanup try-finally block int CleanUpBlock_TryOffset = 0; int CleanUpBlock_TryLength = 0; int CleanUpBlock_FinallyOffset = 0; int CleanUpBlock_FinallyLength = 0; //The "cleanup" block is the finally handler created by the IL reader that //calls GC.DecrementRefCount of locals and arguments as-required so that //memory managed by the GC through objects gets freed correctly. //The try-section of the cleanup block surrounds all of the main code of the method //excluding the final "ret" instruction. In this way, even if an exception occurs, //locals and args still get "cleaned up". //The IL bytes are the IL code. byte[] ILBytes = theMethodBody.GetILAsByteArray(); //Note: IL offsets are usually calculated as the number of bytes offset from the // start of the method. //Note: IL line numbers are IL offsets. //The current position in the IL bytes. //This will change throughout the loop below so it always points past //all the bytes processed so far. int ILBytesPos = 0; //The previous position in the IL bytes. //This will only change in the loop below after a new IL op is created. In this way, //it actually points to the IL bytes position just before the new op is created. //That is to say, it points to the IL bytes pos of the start of the new op. int PrevILBytesPos = 0; //The previous IL op info that was created. //This is the latest one that was created as opposed the the one before that. //I.e. this is the last ILOpInfo added to the final list of IL op infos. ILOpInfo prevInfo = null; //Loop through all the IL bytes for this method... while (ILBytesPos < ILBytes.Length) { //The current System.Reflection.Emit.OpCode being processed OpCode currOpCode; //The unique number that identifies the op code. //This number is also deliberately equivalent to Kernel.Compiler.ILOps.IlOp.OpCodes! ushort currOpCodeID = 0; //MSIL is saved such that OpIds that only require 1 byte, only use 1 byte! //ILBytes encoded as big-endian(?) so high bytes of the op code value (ID) come first //So if high byte is set to 0xFE then we need to load the next byte as low byte if (ILBytes[ILBytesPos] == 0xFE) { currOpCodeID = (ushort)(0xFE00 + (short)ILBytes[ILBytesPos + 1]); ILBytesPos += 2; } else { currOpCodeID = (ushort)ILBytes[ILBytesPos]; ILBytesPos++; } //Load the op code from our pre-constructed list of all op codes currOpCode = AllOpCodes[currOpCodeID]; int operandSize = 0; //Operand type tells us the operand size //We must: // a) Skip over the operand bytes so that we read the next IL op correctly // b) Store the operand bytes in the ILOpInfo for later use switch(currOpCode.OperandType) { case OperandType.InlineBrTarget: operandSize = 4; break; case OperandType.InlineField: operandSize = 4; break; case OperandType.InlineI: operandSize = 4; break; case OperandType.InlineI8: operandSize = 8; break; case OperandType.InlineMethod: operandSize = 4; break; case OperandType.InlineNone: //No operands = no op size break; case OperandType.InlineR: operandSize = 8; break; case OperandType.InlineSig: operandSize = 4; break; case OperandType.InlineString: operandSize = 4; break; case OperandType.InlineSwitch: { int count = Utils.ReadInt32(ILBytes, ILBytesPos); ILBytesPos += 4; operandSize = count * 4; } break; case OperandType.InlineTok: operandSize = 4; break; case OperandType.InlineType: operandSize = 4; break; case OperandType.InlineVar: operandSize = 2; break; case OperandType.ShortInlineBrTarget: operandSize = 1; break; case OperandType.ShortInlineI: operandSize = 1; break; case OperandType.ShortInlineR: operandSize = 4; break; case OperandType.ShortInlineVar: operandSize = 1; break; default: throw new Exception("Unrecognised operand type!"); } //Update the previous op with next position now that we // know what that is... if (prevInfo != null) { prevInfo.NextPosition = PrevILBytesPos; } //The IL reader pre-loads any methods that should be called by, for example, a call op //This was added so that the MethodToCall could be set by the IL reader to inject call ops // - It was going to be a lot harder to try and get the "metadata token bytes" for the // method to call than to simply "pre-load" the method to call. MethodBase methodToCall = null; //Value bytes generally contain a constant value to be loaded or the bytes of a metadata token. //Metadata tokens can be used to retrieve information such as string literals or method infos //from the calling assembly. byte[] valueBytes = new byte[operandSize]; //Don't bother copying 0 bytes... if (operandSize > 0) { //Copy the bytes... Array.Copy(ILBytes, ILBytesPos, valueBytes, 0, operandSize); //If the op is one where the valueBytes are a metadata token pointing to a method: if ((ILOps.ILOp.OpCodes)currOpCode.Value == ILOps.ILOp.OpCodes.Call || (ILOps.ILOp.OpCodes)currOpCode.Value == ILOps.ILOp.OpCodes.Calli || (ILOps.ILOp.OpCodes)currOpCode.Value == ILOps.ILOp.OpCodes.Callvirt || (ILOps.ILOp.OpCodes)currOpCode.Value == ILOps.ILOp.OpCodes.Ldftn || (ILOps.ILOp.OpCodes)currOpCode.Value == ILOps.ILOp.OpCodes.Newobj) { //Pre-load the method for reasons described above. //The metadata token that identifies the method to call in the DLL //It is used to retrieve more information about the method from the DLL int MethodMetadataToken = Utils.ReadInt32(valueBytes, 0); //The method to call retrieved using the metasdata token methodToCall = aMethod.Module.ResolveMethod(MethodMetadataToken); } } //If the op being processed is a Return op and this method has GC applied: if ((ILOps.ILOp.OpCodes)currOpCode.Value == ILOps.ILOp.OpCodes.Ret && result.ApplyGC) { //We must insert the cleanup block code. //Insert try-finally block around the entire method but just before Ret //The finally block can then do clean-up of local variables and args //1. Insert IL ops for doing locals / args cleanup //2. Add the try/finally block (or just finally block if try block already exists) //We must also consider the fact that just before a "ret" op, the return value is loaded. //Since this cleanup block will wrap that load op, we must add code to store the return value //at the end of the try block and then re-load the return value after the finally block (but //before the ret op.) //Get a list of all the params to the current method List<Type> allParams = result.Method.GetParameters() .Select(x => x.ParameterType) .ToList(); //If it isn't a static method: if (!result.Method.IsStatic) { //The first param is the current instance reference. allParams.Insert(0, result.Method.DeclaringType); } //Only insert the cleanup block if there are some params or locals to clean up. //This is the first of two checks of this condition. if (result.LocalVariables.Count > 0 || allParams.Count > 0) { //As per above we need to check for return value LocalVariable returnValVariable = null; //Return type of constructor is void, so only check proper methods if(result.Method is MethodInfo) { Type returnType = ((MethodInfo)result.Method).ReturnType; //Void return type = no return value if(returnType != typeof(void)) { //Add a new local variable for storing the return value returnValVariable = new LocalVariable() { isFloat = Utils.IsFloat(returnType), sizeOnStackInBytes = Utils.GetNumBytesForType(returnType), TheType = returnType, isGCManaged = Utils.IsGCManaged(returnType) }; result.LocalVariables.Add(returnValVariable); //This will become the penultimate IL op of the try-block //It will immediately follow the op just before ret which // will have loaded the return value or, at the very least, // the top-most item on the stack is the return value //This op stores that return value in our new local variable // for reload after the finally block has completed. result.ILOpInfos.Add(prevInfo = new ILOpInfo() { opCode = OpCodes.Stloc, Position = PrevILBytesPos++, NextPosition = PrevILBytesPos, ValueBytes = BitConverter.GetBytes(result.LocalVariables.IndexOf(returnValVariable)) }); } } //This becomes the last op of the try-block (and is required to be // the last op of a try block) result.ILOpInfos.Add(prevInfo = new ILOpInfo() { opCode = OpCodes.Leave_S, Position = PrevILBytesPos++, NextPosition = PrevILBytesPos, ValueBytes = new byte[4] }); //Try block length is now the length in IL bytes from start // (i.e. offset) to start of the current IL op. See above for // why we use PrevIlBytesPos. CleanUpBlock_TryLength = PrevILBytesPos - CleanUpBlock_TryOffset; //Finally offset is offset to first op of finally block i.e. // current IL op position. CleanUpBlock_FinallyOffset = PrevILBytesPos; //Finally length is currently 0 - gets increased later. CleanUpBlock_FinallyLength = 0; //Add cleanup code for each local for (int i = 0; i < result.LocalVariables.Count; i++) { //Clean-up local variables //If the local variable is GC handled: //1. Load the the local //2. Call GC Dec Ref count LocalVariable aVar = result.LocalVariables[i]; //Only add cleanup code if the local is actually GC managed. if (Utils.IsGCManaged(aVar.TheType)) { if (prevInfo != null) { prevInfo.NextPosition = PrevILBytesPos; } //Load the local result.ILOpInfos.Add(prevInfo = new ILOpInfo() { opCode = OpCodes.Ldloc, Position = PrevILBytesPos++, NextPosition = -1, ValueBytes = BitConverter.GetBytes(i) }); prevInfo.NextPosition = PrevILBytesPos; //Decrement the ref count of the local result.ILOpInfos.Add(prevInfo = new ILOpInfo() { opCode = OpCodes.Call, Position = PrevILBytesPos++, NextPosition = -1, SetToGCDecRefCountMethod = true }); CleanUpBlock_FinallyLength += 2; } } //Add cleanup code for each arg //Dec ref count of all args passed to the method for (int i = 0; i < allParams.Count; i++) { Type aVarType = allParams[i]; //Only add cleanup code if the arg is actually GC managed. if (Utils.IsGCManaged(aVarType)) { if (prevInfo != null) { prevInfo.NextPosition = PrevILBytesPos; } result.ILOpInfos.Add(prevInfo = new ILOpInfo() { opCode = OpCodes.Ldarg, Position = PrevILBytesPos++, NextPosition = -1, ValueBytes = BitConverter.GetBytes(i) }); prevInfo.NextPosition = PrevILBytesPos; result.ILOpInfos.Add(prevInfo = new ILOpInfo() { opCode = OpCodes.Call, Position = PrevILBytesPos++, NextPosition = -1, SetToGCDecRefCountMethod = true }); CleanUpBlock_FinallyLength += 2; } } //Locals and args not necessarily of GC managed type // so we could potentially have cleaned up nothing //This is the second of the two checks to make sure we // only add cleanup code if there is something to cleanup if (CleanUpBlock_FinallyLength > 0) { //If there is cleanup code, add the end of the finally block and // reload the return value if necessary. prevInfo.NextPosition = PrevILBytesPos; result.ILOpInfos.Add(prevInfo = new ILOpInfo() { opCode = OpCodes.Endfinally, Position = PrevILBytesPos++, NextPosition = -1 }); CleanUpBlock_FinallyLength += 1; if (returnValVariable != null) { result.ILOpInfos.Add(prevInfo = new ILOpInfo() { opCode = OpCodes.Ldloc, Position = PrevILBytesPos++, NextPosition = PrevILBytesPos, ValueBytes = BitConverter.GetBytes(result.LocalVariables.IndexOf(returnValVariable)) }); } } else { //If there was nothing to cleanup, we need to remove // the ops and locals etc. that got added earlier. result.ILOpInfos.RemoveAt(result.ILOpInfos.Count - 1); PrevILBytesPos--; if(returnValVariable != null) { result.LocalVariables.Remove(returnValVariable); result.ILOpInfos.RemoveAt(result.ILOpInfos.Count - 1); PrevILBytesPos--; } } } } if (staticConstructorDependencyRoot != null) { //Create our static constructor dependency tree //Each of these ops could try to access a static method or field switch((ILOps.ILOp.OpCodes)currOpCode.Value) { case ILOps.ILOp.OpCodes.Call: //Check if the method to call is static and not a constructor itself //If so, we must add the declaring type's static constructors to the tree { int metadataToken = Utils.ReadInt32(valueBytes, 0); MethodBase methodBaseInf = aMethod.Module.ResolveMethod(metadataToken); if(!(methodBaseInf.IsConstructor || methodBaseInf is ConstructorInfo)) { MethodInfo methodInf = (MethodInfo)methodBaseInf; ConstructorInfo[] staticConstructors = methodInf.DeclaringType.GetConstructors(BindingFlags.Static | BindingFlags.Public) .Concat(methodInf.DeclaringType.GetConstructors(BindingFlags.Static | BindingFlags.NonPublic)) .ToArray(); if (staticConstructors.Length > 0) { ConstructorInfo TheConstructor = staticConstructors[0]; if (staticConstructorDependencyRoot[TheConstructor] == null) { staticConstructorDependencyRoot.Children.Add(new StaticConstructorDependency() { TheConstructor = TheConstructor }); } } } } break; case ILOps.ILOp.OpCodes.Ldsfld: case ILOps.ILOp.OpCodes.Ldsflda: case ILOps.ILOp.OpCodes.Stsfld: { int metadataToken = Utils.ReadInt32(valueBytes, 0); FieldInfo fieldInf = aMethod.Module.ResolveField(metadataToken); ConstructorInfo[] staticConstructors = fieldInf.DeclaringType.GetConstructors(BindingFlags.Static | BindingFlags.Public) .Concat(fieldInf.DeclaringType.GetConstructors(BindingFlags.Static | BindingFlags.NonPublic)) .ToArray(); if(staticConstructors.Length > 0) { ConstructorInfo TheConstructor = staticConstructors[0]; if (staticConstructorDependencyRoot[TheConstructor] == null) { staticConstructorDependencyRoot.Children.Add(new StaticConstructorDependency() { TheConstructor = TheConstructor }); } } } break; } } //Add the IL op result.ILOpInfos.Add(prevInfo = new ILOpInfo() { opCode = currOpCode, Position = PrevILBytesPos, // Next position set to -1 indicates no next op NextPosition = -1, ValueBytes = valueBytes, MethodToCall = methodToCall }); ILBytesPos += operandSize; PrevILBytesPos = ILBytesPos; } prevInfo.NextPosition = PrevILBytesPos; //Add the exception handlers (excluding Cleanup try-finally block - see below) foreach (ExceptionHandlingClause aClause in theMethodBody.ExceptionHandlingClauses) { ExceptionHandledBlock exBlock = result.GetExactExceptionHandledBlock(aClause.TryOffset); if (exBlock == null) { exBlock = new ExceptionHandledBlock(); exBlock.Offset = aClause.TryOffset; exBlock.Length = aClause.TryLength; result.ExceptionHandledBlocks.Add(exBlock); } switch (aClause.Flags) { case ExceptionHandlingClauseOptions.Fault: case ExceptionHandlingClauseOptions.Clause: { CatchBlock catchBlock = new CatchBlock() { Offset = aClause.HandlerOffset, Length = aClause.HandlerLength, //Though not used, we may as well set it anyway FilterType = aClause.CatchType }; exBlock.CatchBlocks.Add(catchBlock); } break; case ExceptionHandlingClauseOptions.Finally: { FinallyBlock finallyBlock = new FinallyBlock() { Offset = aClause.HandlerOffset, Length = aClause.HandlerLength }; exBlock.FinallyBlocks.Add(finallyBlock); } break; default: OutputError(new NotSupportedException("Exception handling clause not supported! Type: " + aClause.Flags.ToString())); break; } } //Add the cleanup try-finally block //Only add the block if try-section has non-zero length and // if the finally block has more than just the endfinally op if (CleanUpBlock_TryLength != 0 && CleanUpBlock_FinallyLength > 1) { ExceptionHandledBlock cleanUpTryBlock = new ExceptionHandledBlock() { Offset = CleanUpBlock_TryOffset, Length = CleanUpBlock_TryLength }; FinallyBlock cleanupFinallyBlock = new FinallyBlock() { Offset = CleanUpBlock_FinallyOffset, Length = CleanUpBlock_FinallyLength, }; cleanUpTryBlock.FinallyBlocks.Add(cleanupFinallyBlock); result.ExceptionHandledBlocks.Add(cleanUpTryBlock); } return result; }
/// <summary> /// Process a plugged method. /// </summary> /// <param name="aMethod">The method to process.</param> /// <returns>A new ILChunk marked as plugged with common attribites loaded. Null if any errors occur.</returns> public ILChunk ProcessPluggedMethod(MethodBase aMethod) { ILChunk result = null; PluggedMethodAttribute plugAttr = (PluggedMethodAttribute)aMethod.GetCustomAttribute(typeof(PluggedMethodAttribute)); //Resolve the ASMPlugPath to be relative to the assembly file that the method came from string ASMPlugPath = plugAttr.ASMFilePath; //Null path will result in no load attempt //Allows multiple methods to be plugged by the same ASM file if (ASMPlugPath == null) { result = new ILChunk() { Plugged = true, PlugASMFilePath = null, Method = aMethod }; ProcessCommonMethodAttributes(aMethod, result); return result; } string assemblyPath = aMethod.DeclaringType.Assembly.Location; assemblyPath = Path.GetDirectoryName(assemblyPath); ASMPlugPath = Path.Combine(assemblyPath, ASMPlugPath); if (ASMPlugPath.EndsWith("\\")) { ASMPlugPath = ASMPlugPath.Substring(0, ASMPlugPath.Length - 1); } result = new ILChunk() { Plugged = true, PlugASMFilePath = ASMPlugPath, Method = aMethod }; ProcessCommonMethodAttributes(aMethod, result); return result; }
/// <summary> /// Processes attributes that are common to both plugged and unplugged methods. /// </summary> /// <param name="aMethod">The method to process attributes of.</param> /// <param name="aChunk">The ILChunk to load attributes' info into.</param> private void ProcessCommonMethodAttributes(MethodBase aMethod, ILChunk aChunk) { SequencePriorityAttribute seqPriorityAttr = (SequencePriorityAttribute)aMethod.GetCustomAttribute(typeof(SequencePriorityAttribute)); if (seqPriorityAttr != null) { aChunk.SequencePriority = seqPriorityAttr.Priority; } else { aChunk.SequencePriority = 0; } NoGCAttribute noGCAttr = (NoGCAttribute)aMethod.GetCustomAttribute(typeof(NoGCAttribute)); if (noGCAttr != null) { aChunk.ApplyGC = false; } NoDebugAttribute noDebugAttr = (NoDebugAttribute)aMethod.GetCustomAttribute(typeof(NoDebugAttribute)); if (noDebugAttr != null) { aChunk.NoDebugOps = true; } KernelMainMethodAttribute kernelMainMethodAttr = (KernelMainMethodAttribute)aMethod.GetCustomAttribute(typeof(KernelMainMethodAttribute)); if (kernelMainMethodAttr != null) { aChunk.IsMainMethod = true; } CallStaticConstructorsMethodAttribute callStaticConstructorsMethodAttr = (CallStaticConstructorsMethodAttribute)aMethod.GetCustomAttribute(typeof(CallStaticConstructorsMethodAttribute)); if (callStaticConstructorsMethodAttr != null) { aChunk.IsCallStaticConstructorsMethod = true; } AddExceptionHandlerInfoMethodAttribute addExceptionHandlerInfoMethodAttr = (AddExceptionHandlerInfoMethodAttribute)aMethod.GetCustomAttribute(typeof(AddExceptionHandlerInfoMethodAttribute)); if (addExceptionHandlerInfoMethodAttr != null) { aChunk.IsAddExceptionHandlerInfoMethod = true; } ExceptionsHandleLeaveMethodAttribute exceptionsHandleLeaveMethodAttr = (ExceptionsHandleLeaveMethodAttribute)aMethod.GetCustomAttribute(typeof(ExceptionsHandleLeaveMethodAttribute)); if (exceptionsHandleLeaveMethodAttr != null) { aChunk.IsExceptionsHandleLeaveMethod = true; } ExceptionsHandleEndFinallyMethodAttribute exceptionsHandleEndFinallyMethodAttr = (ExceptionsHandleEndFinallyMethodAttribute)aMethod.GetCustomAttribute(typeof(ExceptionsHandleEndFinallyMethodAttribute)); if (exceptionsHandleEndFinallyMethodAttr != null) { aChunk.IsExceptionsHandleEndFinallyMethod = true; } ThrowExceptionMethodAttribute throwExceptionMethodAttr = (ThrowExceptionMethodAttribute)aMethod.GetCustomAttribute(typeof(ThrowExceptionMethodAttribute)); if (throwExceptionMethodAttr != null) { aChunk.IsExceptionsThrowMethod = true; } ThrowNullReferenceExceptionMethodAttribute throwNullReferenceExceptionMethodAttr = (ThrowNullReferenceExceptionMethodAttribute)aMethod.GetCustomAttribute(typeof(ThrowNullReferenceExceptionMethodAttribute)); if (throwNullReferenceExceptionMethodAttr != null) { aChunk.IsExceptionsThrowNullReferenceMethod = true; } ThrowArrayTypeMismatchExceptionMethodAttribute throwArrayTypeMismatchExceptionMethodAttr = (ThrowArrayTypeMismatchExceptionMethodAttribute)aMethod.GetCustomAttribute(typeof(ThrowArrayTypeMismatchExceptionMethodAttribute)); if (throwArrayTypeMismatchExceptionMethodAttr != null) { aChunk.IsExceptionsThrowArrayTypeMismatchMethod = true; } ThrowIndexOutOfRangeExceptionMethodAttribute throwIndexOutOfRangeExceptionMethodAttr = (ThrowIndexOutOfRangeExceptionMethodAttribute)aMethod.GetCustomAttribute(typeof(ThrowIndexOutOfRangeExceptionMethodAttribute)); if (throwIndexOutOfRangeExceptionMethodAttr != null) { aChunk.IsExceptionsThrowIndexOutOfRangeMethod = true; } HandleExceptionMethodAttribute handleExceptionMethodAttr = (HandleExceptionMethodAttribute)aMethod.GetCustomAttribute(typeof(HandleExceptionMethodAttribute)); if (handleExceptionMethodAttr != null) { aChunk.IsExceptionsHandleExceptionMethod = true; } NewObjMethodAttribute newObjMethodAttr = (NewObjMethodAttribute)aMethod.GetCustomAttribute(typeof(NewObjMethodAttribute)); if (newObjMethodAttr != null) { aChunk.IsNewObjMethod = true; } NewArrMethodAttribute newArrMethodAttr = (NewArrMethodAttribute)aMethod.GetCustomAttribute(typeof(NewArrMethodAttribute)); if (newArrMethodAttr != null) { aChunk.IsNewArrMethod = true; } IncrementRefCountMethodAttribute incrementRefCountMethodAttr = (IncrementRefCountMethodAttribute)aMethod.GetCustomAttribute(typeof(IncrementRefCountMethodAttribute)); if (incrementRefCountMethodAttr != null) { aChunk.IsIncrementRefCountMethod = true; } DecrementRefCountMethodAttribute decrementRefCountMethodAttr = (DecrementRefCountMethodAttribute)aMethod.GetCustomAttribute(typeof(DecrementRefCountMethodAttribute)); if (decrementRefCountMethodAttr != null) { aChunk.IsDecrementRefCountMethod = true; } HaltMethodAttribute haltMethodAttr = (HaltMethodAttribute)aMethod.GetCustomAttribute(typeof(HaltMethodAttribute)); if (haltMethodAttr != null) { aChunk.IsHaltMethod = true; } ArrayConstructorMethodAttribute arrayConstructorMethodAttr = (ArrayConstructorMethodAttribute)aMethod.GetCustomAttribute(typeof(ArrayConstructorMethodAttribute)); if (arrayConstructorMethodAttr != null) { aChunk.IsArrayConstructorMethod = true; } }