Example #1
0
        /// <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;
        }
Example #2
0
        /// <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;
        }