/// <summary> /// Adds a new string literal of specified value to the string literals data block. /// </summary> /// <param name="value">The value of the string to add.</param> /// <param name="ilOpInfo">The ILOpInfo that is adding the string literal.</param> /// <returns>The ID (label) of the string.</returns> public string AddStringLiteral(string value, ILOpInfo ilOpInfo) { string stringID = Utils.GetMD5Hash( Encoding.UTF8.GetBytes(value)); string label = Utils.FilterIdentifierForInvalidChars("StringLiteral_" + stringID); if (!StringLiteralsDataBlock.ASM.ToString().Contains(stringID)) { Encoding xEncoding = Encoding.ASCII; var NumBytes = xEncoding.GetByteCount(value); var stringData = new byte[4 + NumBytes]; Array.Copy(BitConverter.GetBytes(value.Length), 0, stringData, 0, 4); Array.Copy(xEncoding.GetBytes(value), 0, stringData, 4, NumBytes); //This is UTF-16 (Unicode)/ASCII text StringLiteralsDataBlock.ASM.AppendLine(string.Format("{0}:", label)); //Put in type info as FOS_System.String type StringLiteralsDataBlock.ASM.AppendLine("dd STRING_TYPE_ID"); //Put in string length bytes StringLiteralsDataBlock.ASM.Append("db "); for (int i = 0; i < 3; i++) { StringLiteralsDataBlock.ASM.Append(stringData[i]); StringLiteralsDataBlock.ASM.Append(", "); } StringLiteralsDataBlock.ASM.Append(stringData[3]); //Put in string characters (as words) StringLiteralsDataBlock.ASM.Append("\ndw "); for (int i = 4; i < (stringData.Length - 1); i++) { StringLiteralsDataBlock.ASM.Append(stringData[i]); StringLiteralsDataBlock.ASM.Append(", "); } StringLiteralsDataBlock.ASM.Append(stringData.Last()); StringLiteralsDataBlock.ASM.AppendLine(); if (DebugBuild) { DB_StringLiteral dbStringLiteral = new DB_StringLiteral(); dbStringLiteral.Id = stringID; if (ilOpInfo != null) { dbStringLiteral.ILOpInfoID = ilOpInfo.DBILOpInfo.Id; } dbStringLiteral.Value = value; DebugDatabase.AddStringLiteral(dbStringLiteral); } } return label; }
/// <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; }