using System; using System.Collections.Generic; using System.Diagnostics.SymbolStore; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Xml.Serialization; using Cosmos.Assembler; using Cosmos.Assembler.x86; using Cosmos.Assembler.x86._486AndUp; using Cosmos.Build.Common; using Cosmos.Common; using Cosmos.Debug.Common; using Cosmos.IL2CPU.Plugs; using Mono.Cecil; using SysReflection = System.Reflection; namespace Cosmos.IL2CPU { public class AppAssembler : IDisposable { public const string EndOfMethodLabelNameNormal = ".END__OF__METHOD_NORMAL"; public const string EndOfMethodLabelNameException = ".END__OF__METHOD_EXCEPTION"; protected const string InitStringIDsLabel = "___INIT__STRINGS_TYPE_ID_S___"; protected List mLocals_Arguments_Infos = new List(); protected ILOp[] mILOpsLo = new ILOp[256]; protected ILOp[] mILOpsHi = new ILOp[256]; public bool ShouldOptimize = false; public DebugInfo DebugInfo { get; set; } protected TextWriter mLog; protected Dictionary mLoadedModules = new Dictionary(); protected DebugInfo.SequencePoint[] mSequences = new DebugInfo.SequencePoint[0]; public TraceAssemblies TraceAssemblies; public bool DebugEnabled = false; public bool StackCorruptionDetection = false; public DebugMode DebugMode; public bool IgnoreDebugStubAttribute; protected static HashSet mDebugLines = new HashSet(); protected List mSymbols = new List(); protected List mINT3Labels = new List(); public readonly Cosmos.Assembler.Assembler Assembler; // protected string mCurrentMethodLabel; protected long mCurrentMethodLabelEndGuid; protected long mCurrentMethodGuid; public AppAssembler(int aComPort, string assemblerLogFile) { Assembler = CreateAssembler(aComPort); mLog = new StreamWriter(assemblerLogFile, false); InitILOps(); } protected virtual Assembler.Assembler CreateAssembler(int aComPort) { return new Cosmos.Assembler.Assembler(aComPort); } public void Dispose() { if (mLog != null) { mLog.Dispose(); mLog = null; } GC.SuppressFinalize(this); } protected void MethodBegin(MethodInfo aMethod) { new Comment("---------------------------------------------------------"); new Comment("Assembly: " + aMethod.MethodBase.DeclaringType.Assembly.FullName); new Comment("Type: " + aMethod.MethodBase.DeclaringType.ToString()); new Comment("Name: " + aMethod.MethodBase.Name); new Comment("Plugged: " + (aMethod.PlugMethod == null ? "No" : "Yes")); // for now: var shouldIncludeArgAndLocalsComment = true; if (shouldIncludeArgAndLocalsComment) { if (aMethod.MethodAssembler == null && aMethod.PlugMethod == null && !aMethod.IsInlineAssembler) { // the body of aMethod is getting emitted var xBody = aMethod.MethodBase.GetMethodBody(); if (xBody != null) { foreach (var localVariable in xBody.LocalVariables) { new Comment(String.Format("Local {0} at EBP-{1}", localVariable.LocalIndex, ILOp.GetEBPOffsetForLocal(aMethod, localVariable.LocalIndex))); } } var xIdxOffset = 0u; if (!aMethod.MethodBase.IsStatic) { new Comment(String.Format("Argument $this at EBP+{0}, size = {1}", X86.IL.Ldarg.GetArgumentDisplacement(aMethod, 0), ILOp.Align(ILOp.SizeOfType(aMethod.MethodBase.DeclaringType), 4))); xIdxOffset++; } var xParams = aMethod.MethodBase.GetParameters(); var xParamCount = (ushort) xParams.Length; for (ushort i = 0; i < xParamCount; i++) { var xOffset = X86.IL.Ldarg.GetArgumentDisplacement(aMethod, (ushort) (i + xIdxOffset)); var xSize = X86.IL.Ldarg.SizeOfType(xParams[i].ParameterType); // if last argument is 8 byte long, we need to add 4, so that debugger could read all 8 bytes from this variable in positiv direction new Comment(String.Format("Argument {0} at EBP+{1}, size = {2}", xParams[i].Name, xOffset, xSize)); } } } // Issue label that is used for calls etc. string xMethodLabel; if (aMethod.PluggedMethod != null) { xMethodLabel = "PLUG_FOR___" + LabelName.Get(aMethod.PluggedMethod.MethodBase); } else { xMethodLabel = LabelName.Get(aMethod.MethodBase); } new Cosmos.Assembler.Label(xMethodLabel); //Assembler.WriteDebugVideo("Method " + aMethod.UID); // We could use same GUID as MethodLabelStart, but its better to keep GUIDs unique globaly for items // so during debugging they can never be confused as to what they point to. mCurrentMethodGuid = DebugInfo.CreateId(); // We issue a second label for GUID. This is increases label count, but for now we need a master label first. // We issue a GUID label to reduce amount of work and time needed to construct debugging DB. var xLabelGuid = DebugInfo.CreateId(); new Cosmos.Assembler.Label("GUID_" + xLabelGuid.ToString()); mCurrentMethodLabel = "METHOD_" + xLabelGuid.ToString(); Cosmos.Assembler.Label.LastFullLabel = mCurrentMethodLabel; mCurrentMethodLabelEndGuid = DebugInfo.CreateId(); if (aMethod.MethodBase.IsStatic && aMethod.MethodBase is ConstructorInfo) { new Comment("Static constructor. See if it has been called already, return if so."); var xName = DataMember.FilterStringForIncorrectChars("CCTOR_CALLED__" + LabelName.GetFullName(aMethod.MethodBase.DeclaringType)); var xAsmMember = new DataMember(xName, (byte)0); Assembler.DataMembers.Add(xAsmMember); new Compare { DestinationRef = Cosmos.Assembler.ElementReference.New(xName), DestinationIsIndirect = true, Size = 8, SourceValue = 1 }; new ConditionalJump { Condition = ConditionalTestEnum.Equal, DestinationLabel = ".BeforeQuickReturn" }; new Mov { DestinationRef = Cosmos.Assembler.ElementReference.New(xName), DestinationIsIndirect = true, Size = 8, SourceValue = 1 }; new Jump { DestinationLabel = ".AfterCCTorAlreadyCalledCheck" }; new Cosmos.Assembler.Label(".BeforeQuickReturn"); new Mov { DestinationReg = RegistersEnum.ECX, SourceValue = 0 }; new Return { }; new Cosmos.Assembler.Label(".AfterCCTorAlreadyCalledCheck"); } new Push { DestinationReg = Registers.EBP }; new Mov { DestinationReg = Registers.EBP, SourceReg = Registers.ESP }; if (DebugMode == DebugMode.Source) { // Would be nice to use xMethodSymbols.GetSourceStartEnd but we cant // because its not implemented by the unmanaged code underneath. // // This doesnt seem right to store as a field, but old code had it that way so we // continue using a field for now. mSequences = DebugInfo.GetSequencePoints(aMethod.MethodBase, true); if (mSequences.Length > 0) { DebugInfo.AddDocument(mSequences[0].Document); var xMethod = new Method() { ID = mCurrentMethodGuid, TypeToken = aMethod.MethodBase.DeclaringType.MetadataToken, MethodToken = aMethod.MethodBase.MetadataToken, LabelStartID = xLabelGuid, LabelEndID = mCurrentMethodLabelEndGuid, LabelCall = xMethodLabel, AssemblyFileID = DebugInfo.AssemblyGUIDs[aMethod.MethodBase.DeclaringType.Assembly], DocumentID = DebugInfo.DocumentGUIDs[mSequences[0].Document.ToLower()], // Storing Line + Col as one item makes comparisons MUCH easier, otherwise we have to // check for things like col < start col but line > start line. // // () around << are VERY important.. + has precedence over << LineColStart = ((Int64)mSequences[0].LineStart << 32) + mSequences[0].ColStart, LineColEnd = ((Int64)(mSequences[mSequences.Length - 1].LineEnd) << 32) + mSequences[mSequences.Length - 1].ColEnd }; DebugInfo.AddMethod(xMethod); } } if (aMethod.MethodAssembler == null && aMethod.PlugMethod == null && !aMethod.IsInlineAssembler) { // the body of aMethod is getting emitted var xBody = aMethod.MethodBase.GetMethodBody(); if (xBody != null) { var xLocalsOffset = mLocals_Arguments_Infos.Count; aMethod.LocalVariablesSize = 0; foreach (var xLocal in xBody.LocalVariables) { var xInfo = new LOCAL_ARGUMENT_INFO { METHODLABELNAME = xMethodLabel, IsArgument = false, INDEXINMETHOD = xLocal.LocalIndex, NAME = "Local" + xLocal.LocalIndex, OFFSET = 0 - (int)ILOp.GetEBPOffsetForLocalForDebugger(aMethod, xLocal.LocalIndex), TYPENAME = xLocal.LocalType.AssemblyQualifiedName }; mLocals_Arguments_Infos.Add(xInfo); var xSize = ILOp.Align(ILOp.SizeOfType(xLocal.LocalType), 4); new Comment(String.Format("Local {0}, Size {1}", xLocal.LocalIndex, xSize)); for (int i = 0; i < xSize / 4; i++) { new Push { DestinationValue = 0 }; } aMethod.LocalVariablesSize += xSize; //new Sub { DestinationReg = Registers.ESP, SourceValue = ILOp.Align(ILOp.SizeOfType(xLocal.LocalType), 4) }; } var xCecilMethod = GetCecilMethodDefinitionForSymbolReading(aMethod.MethodBase); if (xCecilMethod != null && xCecilMethod.Body != null) { // mLocals_Arguments_Infos is one huge list, so ourlatest additions are at the end for (int i = 0; i < xCecilMethod.Body.Variables.Count; i++) { mLocals_Arguments_Infos[xLocalsOffset + i].NAME = xCecilMethod.Body.Variables[i].Name; } for (int i = xLocalsOffset + xCecilMethod.Body.Variables.Count - 1; i >= xLocalsOffset; i--) { if (mLocals_Arguments_Infos[i].NAME.Contains('$')) { mLocals_Arguments_Infos.RemoveAt(i); } } } } // debug info: var xIdxOffset = 0u; if (!aMethod.MethodBase.IsStatic) { mLocals_Arguments_Infos.Add(new LOCAL_ARGUMENT_INFO { METHODLABELNAME = xMethodLabel, IsArgument = true, NAME = "this:" + X86.IL.Ldarg.GetArgumentDisplacement(aMethod, 0), INDEXINMETHOD = 0, OFFSET = X86.IL.Ldarg.GetArgumentDisplacement(aMethod, 0), TYPENAME = aMethod.MethodBase.DeclaringType.AssemblyQualifiedName }); xIdxOffset++; } var xParams = aMethod.MethodBase.GetParameters(); var xParamCount = (ushort)xParams.Length; for (ushort i = 0; i < xParamCount; i++) { var xOffset = X86.IL.Ldarg.GetArgumentDisplacement(aMethod, (ushort)(i + xIdxOffset)); // if last argument is 8 byte long, we need to add 4, so that debugger could read all 8 bytes from this variable in positiv direction xOffset -= (int)Cosmos.IL2CPU.ILOp.Align(ILOp.SizeOfType(xParams[i].ParameterType), 4) - 4; mLocals_Arguments_Infos.Add(new LOCAL_ARGUMENT_INFO { METHODLABELNAME = xMethodLabel, IsArgument = true, INDEXINMETHOD = (int)(i + xIdxOffset), NAME = xParams[i].Name, OFFSET = xOffset, TYPENAME = xParams[i].ParameterType.AssemblyQualifiedName }); } } } protected void MethodEnd(MethodInfo aMethod) { new Comment("End Method: " + aMethod.MethodBase.Name); uint xReturnSize = 0; var xMethInfo = aMethod.MethodBase as SysReflection.MethodInfo; if (xMethInfo != null) { xReturnSize = ILOp.Align(ILOp.SizeOfType(xMethInfo.ReturnType), 4); } var xMethodLabel = ILOp.GetMethodLabel(aMethod); //if (aMethod.PlugMethod == null // && !aMethod.IsInlineAssembler) { new Cosmos.Assembler.Label(xMethodLabel + EndOfMethodLabelNameNormal); new Comment("Following code is for debugging. Adjust accordingly!"); new Mov { DestinationRef = ElementReference.New("static_field__Cosmos_Core_INTs_mLastKnownAddress"), DestinationIsIndirect = true, SourceRef = ElementReference.New(xMethodLabel + EndOfMethodLabelNameNormal) }; } new Mov { DestinationReg = Registers.ECX, SourceValue = 0 }; var xTotalArgsSize = (from item in aMethod.MethodBase.GetParameters() select (int)ILOp.Align(ILOp.SizeOfType(item.ParameterType), 4)).Sum(); if (!aMethod.MethodBase.IsStatic) { if (aMethod.MethodBase.DeclaringType.IsValueType) { xTotalArgsSize += 4; // only a reference is passed } else { xTotalArgsSize += (int)ILOp.Align(ILOp.SizeOfType(aMethod.MethodBase.DeclaringType), 4); } } if (aMethod.PluggedMethod != null) { xReturnSize = 0; xMethInfo = aMethod.PluggedMethod.MethodBase as SysReflection.MethodInfo; if (xMethInfo != null) { xReturnSize = ILOp.Align(ILOp.SizeOfType(xMethInfo.ReturnType), 4); } xTotalArgsSize = (from item in aMethod.PluggedMethod.MethodBase.GetParameters() select (int)ILOp.Align(ILOp.SizeOfType(item.ParameterType), 4)).Sum(); if (!aMethod.PluggedMethod.MethodBase.IsStatic) { if (aMethod.PluggedMethod.MethodBase.DeclaringType.IsValueType) { xTotalArgsSize += 4; // only a reference is passed } else { xTotalArgsSize += (int)ILOp.Align(ILOp.SizeOfType(aMethod.PluggedMethod.MethodBase.DeclaringType), 4); } } } if (xReturnSize > 0) { var xOffset = GetResultCodeOffset(xReturnSize, (uint)xTotalArgsSize); for (int i = 0; i < xReturnSize / 4; i++) { new Pop { DestinationReg = Registers.EAX }; new Mov { DestinationReg = Registers.EBP, DestinationIsIndirect = true, DestinationDisplacement = (int)(xOffset + ((i + 0) * 4)), SourceReg = Registers.EAX }; } // extra stack space is the space reserved for example when a "public static int TestMethod();" method is called, 4 bytes is pushed, to make room for result; } var xLabelExc = xMethodLabel + EndOfMethodLabelNameException; new Cosmos.Assembler.Label(xLabelExc); if (aMethod.MethodAssembler == null && aMethod.PlugMethod == null && !aMethod.IsInlineAssembler) { var xBody = aMethod.MethodBase.GetMethodBody(); if (xBody != null) { uint xLocalsSize = 0; for (int j = xBody.LocalVariables.Count - 1; j >= 0; j--) { xLocalsSize += ILOp.Align(ILOp.SizeOfType(xBody.LocalVariables[j].LocalType), 4); if (xLocalsSize >= 256) { new Add { DestinationReg = Registers.ESP, SourceValue = 255 }; xLocalsSize -= 255; } } if (xLocalsSize > 0) { new Add { DestinationReg = Registers.ESP, SourceValue = xLocalsSize }; } } } if (DebugEnabled && StackCorruptionDetection) { // if debugstub is active, emit a stack corruption detection. at this point EBP and ESP should have the same value. // if not, we should somehow break here. new Mov {DestinationReg = Registers.EAX, SourceReg = RegistersEnum.ESP}; new Mov { DestinationReg = Registers.EBX, SourceReg = RegistersEnum.EBP }; new Compare {SourceReg = RegistersEnum.EAX, DestinationReg = RegistersEnum.EBX}; new ConditionalJump { Condition = ConditionalTestEnum.Equal, DestinationLabel = xLabelExc + "__2" }; new ClrInterruptFlag(); // don't remove the call. It seems pointless, but we need it to retrieve the EIP value new Call { DestinationLabel = xLabelExc + ".MethodFooterStackCorruptionCheck_Break_on_location" }; new Assembler.Label(xLabelExc + ".MethodFooterStackCorruptionCheck_Break_on_location"); new Pop {DestinationReg = RegistersEnum.EAX}; new Mov {DestinationRef = ElementReference.New("DebugStub_CallerEIP"), DestinationIsIndirect = true, SourceReg = RegistersEnum.EAX}; new Call { DestinationLabel = "DebugStub_SendStackCorruptionOccurred" }; new Halt(); } new Cosmos.Assembler.Label(xLabelExc + "__2"); new Pop { DestinationReg = Registers.EBP }; var xRetSize = ((int)xTotalArgsSize) - ((int)xReturnSize); if (xRetSize < 0) { xRetSize = 0; } WriteDebug(aMethod.MethodBase, (uint)xRetSize, X86.IL.Call.GetStackSizeToReservate(aMethod.MethodBase)); new Return { DestinationValue = (uint)xRetSize }; // Final, after all code. Points to op AFTER method. new Cosmos.Assembler.Label("GUID_" + mCurrentMethodLabelEndGuid.ToString()); } public void FinalizeDebugInfo() { DebugInfo.AddDocument(null, true); DebugInfo.AddAssemblies(null, true); DebugInfo.AddMethod(null, true); DebugInfo.WriteAllLocalsArgumentsInfos(mLocals_Arguments_Infos); DebugInfo.AddSymbols(mSymbols, true); DebugInfo.AddINT3Labels(mINT3Labels, true); } public static uint GetResultCodeOffset(uint aResultSize, uint aTotalArgumentSize) { uint xOffset = 8; if ((aTotalArgumentSize > 0) && (aTotalArgumentSize >= aResultSize)) { xOffset += aTotalArgumentSize; xOffset -= aResultSize; } return xOffset; } public void ProcessMethod(MethodInfo aMethod, List aOpCodes) { try { // We check this here and not scanner as when scanner makes these // plugs may still have not yet been scanned that it will depend on. // But by the time we make it here, they have to be resolved. if (aMethod.Type == MethodInfo.TypeEnum.NeedsPlug && aMethod.PlugMethod == null) { throw new Exception("Method needs plug, but no plug was assigned."); } // todo: MtW: how to do this? we need some extra space. // see ConstructLabel for extra info if (aMethod.UID > 0x00FFFFFF) { throw new Exception("Too many methods."); } MethodBegin(aMethod); mLog.WriteLine("Method '{0}', ID = '{1}'", aMethod.MethodBase.GetFullName(), aMethod.UID); mLog.Flush(); if (aMethod.MethodAssembler != null) { var xAssembler = (AssemblerMethod)Activator.CreateInstance(aMethod.MethodAssembler); xAssembler.AssembleNew(Assembler, aMethod.PluggedMethod); } else if (aMethod.IsInlineAssembler) { aMethod.MethodBase.Invoke("", new object[aMethod.MethodBase.GetParameters().Length]); } else { // now emit the actual assembler code for this method. //Conditions under which we should emit an INT3 instead of a plceholder NOP: /* - First instruction in a Method / Loop / If / Else etc. * -- In essence, whenever there is a opening { * -- C# Debug builds automatically insert NOPs at these locations (otherwise NOP is not used) * -- So only insert an INT3 when we are about to insert a NOP that came from IL code */ /* We group opcodes together by logical statement. Each statement will have its logical stack cleared. * Also, this lets us do optimizations later on. */ bool emitINT3 = true; DebugInfo.SequencePoint xPreviousSequencePoint = null; var xCurrentGroup = new List(); ILOpCode.ILInterpretationDebugLine(() => String.Format("Method: {0}", aMethod.MethodBase.GetFullName())); foreach (var xRawOpcode in aOpCodes) { var xSP = mSequences.FirstOrDefault(q => q.Offset == xRawOpcode.Position && q.LineStart != 0xFEEFEE); // detect if we're at a new statement. if (xPreviousSequencePoint == null && xSP != null) { } if (xSP != null && xCurrentGroup.Count > 0) { EmitInstructions(aMethod, xCurrentGroup, ref emitINT3); xCurrentGroup.Clear(); xPreviousSequencePoint = xSP; } xCurrentGroup.Add(xRawOpcode); } if (xCurrentGroup.Count > 0) { EmitInstructions(aMethod, xCurrentGroup, ref emitINT3); } } MethodEnd(aMethod); } catch (Exception E) { throw new Exception("Error compiling method '" + aMethod.MethodBase.GetFullName() + "': " + E.ToString(), E); } } private void BeforeEmitInstructions(MethodInfo aMethod, List aCurrentGroup) { // do optimizations } private void AfterEmitInstructions(MethodInfo aMethod, List aCurrentGroup) { // do optimizations //if (Assembler.Stack.Count > 0) //{ // if (mDebugStackErrors) // { // Console.WriteLine("StackCorruption in Analytical stack:"); // Console.WriteLine("- Method: {0}", aMethod.MethodBase.GetFullName()); // Console.WriteLine("- Last ILOpCode offset: {0}", aCurrentGroup.Last().Position.ToString("X")); // } //} } //private static bool mDebugStackErrors = true; private void EmitInstructions(MethodInfo aMethod, List aCurrentGroup, ref bool emitINT3) { ILOpCode.ILInterpretationDebugLine(() => "---- Group"); InterpretInstructionsToDetermineStackTypes(aCurrentGroup); BeforeEmitInstructions(aMethod, aCurrentGroup); var xFirstInstruction = true; foreach (var xOpCode in aCurrentGroup) { ushort xOpCodeVal = (ushort) xOpCode.OpCode; ILOp xILOp; if (xOpCodeVal <= 0xFF) { xILOp = mILOpsLo[xOpCodeVal]; } else { xILOp = mILOpsHi[xOpCodeVal & 0xFF]; } mLog.Flush(); //Only emit INT3 as per conditions above... bool INT3Emitted = false; BeforeOp(aMethod, xOpCode, emitINT3, out INT3Emitted, xFirstInstruction); xFirstInstruction = false; //Emit INT3 on the first non-NOP instruction immediately after a NOP // - This is because TracePoints for NOP are automatically ignored in code called below this emitINT3 = (emitINT3 && !INT3Emitted) || xILOp is Cosmos.IL2CPU.X86.IL.Nop; new Comment(xILOp.ToString()); var xNextPosition = xOpCode.Position + 1; #region Exception handling support code ExceptionHandlingClause xCurrentHandler = null; var xBody = aMethod.MethodBase.GetMethodBody(); // todo: add support for nested handlers using a stack or so.. foreach (ExceptionHandlingClause xHandler in xBody.ExceptionHandlingClauses) { if (xHandler.TryOffset > 0) { if (xHandler.TryOffset <= xNextPosition && (xHandler.TryLength + xHandler.TryOffset) > xNextPosition) { if (xCurrentHandler == null) { xCurrentHandler = xHandler; continue; } else if (xHandler.TryOffset > xCurrentHandler.TryOffset && (xHandler.TryLength + xHandler.TryOffset) < (xCurrentHandler.TryLength + xCurrentHandler.TryOffset)) { // only replace if the current found handler is narrower xCurrentHandler = xHandler; continue; } } } if (xHandler.HandlerOffset > 0) { if (xHandler.HandlerOffset <= xNextPosition && (xHandler.HandlerOffset + xHandler.HandlerLength) > xNextPosition) { if (xCurrentHandler == null) { xCurrentHandler = xHandler; continue; } else if (xHandler.HandlerOffset > xCurrentHandler.HandlerOffset && (xHandler.HandlerOffset + xHandler.HandlerLength) < (xCurrentHandler.HandlerOffset + xCurrentHandler.HandlerLength)) { // only replace if the current found handler is narrower xCurrentHandler = xHandler; continue; } } } if ((xHandler.Flags & ExceptionHandlingClauseOptions.Filter) > 0) { if (xHandler.FilterOffset > 0) { if (xHandler.FilterOffset <= xNextPosition) { if (xCurrentHandler == null) { xCurrentHandler = xHandler; continue; } else if (xHandler.FilterOffset > xCurrentHandler.FilterOffset) { // only replace if the current found handler is narrower xCurrentHandler = xHandler; continue; } } } } } #endregion var xNeedsExceptionPush = (xCurrentHandler != null) && (((xCurrentHandler.HandlerOffset > 0 && xCurrentHandler.HandlerOffset == xOpCode.Position) || ((xCurrentHandler.Flags & ExceptionHandlingClauseOptions.Filter) > 0 && xCurrentHandler.FilterOffset > 0 && xCurrentHandler.FilterOffset == xOpCode.Position)) && (xCurrentHandler.Flags == ExceptionHandlingClauseOptions.Clause)); if (xNeedsExceptionPush) { Push(DataMember.GetStaticFieldName(ExceptionHelperRefs.CurrentExceptionRef), true); } xILOp.DebugEnabled = DebugEnabled; xILOp.Execute(aMethod, xOpCode); AfterOp(aMethod, xOpCode); //mLog.WriteLine( " end: " + Stack.Count.ToString() ); } AfterEmitInstructions(aMethod, aCurrentGroup); } /// /// This method takes care of "interpreting" the instructions per group (statement). This is necessary to /// reliably able to tell what sizes are involved in certain actions. /// /// private static void InterpretInstructionsToDetermineStackTypes(List aCurrentGroup) { var xNeedsInterpreting = true; // see if we need to interpret the instructions at all. foreach (var xOp in aCurrentGroup) { foreach (var xStackEntry in xOp.StackPopTypes.Concat(xOp.StackPushTypes)) { if (xStackEntry == null) { xNeedsInterpreting = true; break; } } if (xNeedsInterpreting) { break; } } var xIteration = 0; var xGroupILByILOffset = aCurrentGroup.ToDictionary(i => i.Position); while (xNeedsInterpreting) { ILOpCode.ILInterpretationDebugLine(() => String.Format("--------- New Interpretation iteration (xIteration = {0})", xIteration)); xIteration ++; if (xIteration > 20) { // Situation not resolved. Now give error with first offset needing types: foreach (var xOp in aCurrentGroup) { foreach (var xStackEntry in xOp.StackPopTypes.Concat(xOp.StackPushTypes)) { if (xStackEntry == null) { throw new Exception(string.Format("Safety exception. Handled {0} iterations. Instruction needing info: {1}", xIteration, xOp)); } } } } aCurrentGroup.ForEach(i => i.Processed = false); var xMaxInterpreterRecursionDepth = 25000; var xCurStack = new Stack(); var xSituationChanged = false; aCurrentGroup.First().InterpretStackTypes(xGroupILByILOffset, xCurStack, ref xSituationChanged, xMaxInterpreterRecursionDepth); if (!xSituationChanged) { // nothing changed, now give error with first offset needing types: foreach (var xOp in aCurrentGroup) { foreach (var xStackEntry in xOp.StackPopTypes.Concat(xOp.StackPushTypes)) { if (xStackEntry == null) { throw new Exception("After interpreting stack types, nothing changed! (First instruction needing types = " + xOp + ")"); } } } } xNeedsInterpreting = false; foreach (var xOp in aCurrentGroup) { foreach (var xStackEntry in xOp.StackPopTypes.Concat(xOp.StackPushTypes)) { if (xStackEntry == null) { xNeedsInterpreting = true; break; } } if (xNeedsInterpreting) { break; } } } foreach (var xOp in aCurrentGroup) { foreach (var xStackEntry in xOp.StackPopTypes.Concat(xOp.StackPushTypes)) { if (xStackEntry == null) { throw new Exception(String.Format("Instruction '{0}' has not been fully analysed yet!", xOp)); } } } } protected void InitILOps() { InitILOps(typeof(ILOp)); } protected virtual void InitILOps(Type aAssemblerBaseOp) { foreach (var xType in aAssemblerBaseOp.Assembly.GetExportedTypes()) { if (xType.IsSubclassOf(aAssemblerBaseOp)) { var xAttribs = (OpCodeAttribute[])xType.GetCustomAttributes(typeof(OpCodeAttribute), false); foreach (var xAttrib in xAttribs) { var xOpCode = (ushort)xAttrib.OpCode; var xCtor = xType.GetConstructor(new Type[] { typeof(Cosmos.Assembler.Assembler) }); var xILOp = (ILOp)xCtor.Invoke(new Object[] { Assembler }); if (xOpCode <= 0xFF) { mILOpsLo[xOpCode] = xILOp; } else { mILOpsHi[xOpCode & 0xFF] = xILOp; } } } } } protected void Move(string aDestLabelName, int aValue) { new Mov { DestinationRef = ElementReference.New(aDestLabelName), DestinationIsIndirect = true, SourceValue = (uint)aValue }; } protected void Push(uint aValue) { new Push { DestinationValue = aValue }; } protected void Push(string aLabelName, bool isIndirect = false) { new Push { DestinationRef = ElementReference.New(aLabelName), DestinationIsIndirect = isIndirect }; } protected void Call(MethodBase aMethod) { new Cosmos.Assembler.x86.Call { DestinationLabel = LabelName.Get(aMethod) }; } protected void Jump(string aLabelName) { new Cosmos.Assembler.x86.Jump { DestinationLabel = aLabelName }; } protected X86.IL.FieldInfo ResolveField(MethodInfo method, string fieldId, bool aOnlyInstance) { return X86.IL.Ldflda.ResolveField(method.MethodBase.DeclaringType, fieldId, aOnlyInstance); } protected void Ldarg(MethodInfo aMethod, int aIndex) { X86.IL.Ldarg.DoExecute(Assembler, aMethod, (ushort)aIndex); } protected void Call(MethodInfo aMethod, MethodInfo aTargetMethod, string aNextLabel) { var xSize = X86.IL.Call.GetStackSizeToReservate(aTargetMethod.MethodBase); if (xSize > 0) { new Sub { DestinationReg = Registers.ESP, SourceValue = xSize }; } new Call { DestinationLabel = ILOp.GetMethodLabel(aTargetMethod) }; var xMethodInfo = aMethod.MethodBase as SysReflection.MethodInfo; uint xReturnsize = 0; if (xMethodInfo != null) { xReturnsize = ILOp.SizeOfType(((SysReflection.MethodInfo)aMethod.MethodBase).ReturnType); } ILOp.EmitExceptionLogic(Assembler, aMethod, null, true, delegate() { var xResultSize = xReturnsize; if (xResultSize % 4 != 0) { xResultSize += 4 - (xResultSize % 4); } for (int i = 0; i < xResultSize / 4; i++) { new Add { DestinationReg = Registers.ESP, SourceValue = 4 }; } }, aNextLabel); } protected void Ldflda(MethodInfo aMethod, string aFieldId) { X86.IL.Ldflda.DoExecute(Assembler, aMethod, aMethod.MethodBase.DeclaringType, aFieldId, false, false); } protected void Ldflda(MethodInfo aMethod, X86.IL.FieldInfo aFieldInfo) { X86.IL.Ldflda.DoExecute(Assembler, aMethod, aMethod.MethodBase.DeclaringType, aFieldInfo, false, false); } protected void Ldsflda(MethodInfo aMethod, X86.IL.FieldInfo aFieldInfo) { X86.IL.Ldsflda.DoExecute(Assembler, aMethod, DataMember.GetStaticFieldName(aFieldInfo.Field), aMethod.MethodBase.DeclaringType, null); } protected int GetVTableEntrySize() { return 16; // todo: retrieve from actual type info } public const string InitVMTCodeLabel = "___INIT__VMT__CODE____"; public virtual void GenerateVMTCode(HashSet aTypesSet, HashSet aMethodsSet, Func aGetTypeID, Func aGetMethodUID) { new Comment("---------------------------------------------------------"); new Cosmos.Assembler.Label(InitVMTCodeLabel); new Push { DestinationReg = Registers.EBP }; new Mov { DestinationReg = Registers.EBP, SourceReg = Registers.ESP }; mSequences = new DebugInfo.SequencePoint[0]; var xSetTypeInfoRef = VTablesImplRefs.SetTypeInfoRef; var xSetMethodInfoRef = VTablesImplRefs.SetMethodInfoRef; var xLoadTypeTableRef = VTablesImplRefs.LoadTypeTableRef; var xTypesFieldRef = VTablesImplRefs.VTablesImplDef.GetField("mTypes", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance); string xTheName = DataMember.GetStaticFieldName(xTypesFieldRef); DataMember xDataMember = (from item in Cosmos.Assembler.Assembler.CurrentInstance.DataMembers where item.Name == xTheName select item).FirstOrDefault(); if (xDataMember != null) { Cosmos.Assembler.Assembler.CurrentInstance.DataMembers.Remove((from item in Cosmos.Assembler.Assembler.CurrentInstance.DataMembers where item == xDataMember select item).First()); } var xData = new byte[16 + (aTypesSet.Count * GetVTableEntrySize())]; var xTemp = BitConverter.GetBytes(aGetTypeID(typeof(Array))); xTemp = BitConverter.GetBytes(0x80000002); Array.Copy(xTemp, 0, xData, 4, 4); xTemp = BitConverter.GetBytes(aTypesSet.Count); Array.Copy(xTemp, 0, xData, 8, 4); xTemp = BitConverter.GetBytes(GetVTableEntrySize()); Array.Copy(xTemp, 0, xData, 12, 4); Cosmos.Assembler.Assembler.CurrentInstance.DataMembers.Add(new DataMember(xTheName + "__Contents", xData)); Cosmos.Assembler.Assembler.CurrentInstance.DataMembers.Add(new DataMember(xTheName, Cosmos.Assembler.ElementReference.New(xTheName + "__Contents"))); #if VMT_DEBUG using (var xVmtDebugOutput = XmlWriter.Create(@"e:\vmt_debug.xml")) { xVmtDebugOutput.WriteStartDocument(); xVmtDebugOutput.WriteStartElement("VMT"); #endif //Push((uint)aTypesSet.Count); //Call(xLoadTypeTableRef); foreach (var xType in aTypesSet) { #if VMT_DEBUG xVmtDebugOutput.WriteStartElement("Type"); xVmtDebugOutput.WriteAttributeString("TypeId", aGetTypeID(xType).ToString()); if (xType.BaseType != null) { xVmtDebugOutput.WriteAttributeString("BaseTypeId", aGetTypeID(xType.BaseType).ToString()); } xVmtDebugOutput.WriteAttributeString("Name", xType.FullName); #endif // value contains true if the method is an interface method definition SortedList xEmittedMethods = new SortedList(new MethodBaseComparer()); foreach (MethodBase xMethod in xType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { if (aMethodsSet.Contains(xMethod)) { //) && !xMethod.IsAbstract) { xEmittedMethods.Add(xMethod, false); } } foreach (MethodBase xCtor in xType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { if (aMethodsSet.Contains(xCtor)) { // && !xCtor.IsAbstract) { xEmittedMethods.Add(xCtor, false); } } foreach (var xIntf in xType.GetInterfaces()) { foreach (var xMethodIntf in xIntf.GetMethods()) { var xActualMethod = xType.GetMethod(xIntf.FullName + "." + xMethodIntf.Name, (from xParam in xMethodIntf.GetParameters() select xParam.ParameterType).ToArray()); if (xActualMethod == null) { // get private implemenation xActualMethod = xType.GetMethod(xMethodIntf.Name, (from xParam in xMethodIntf.GetParameters() select xParam.ParameterType).ToArray()); } if (xActualMethod == null) { try { if (!xIntf.IsGenericType) { var xMap = xType.GetInterfaceMap(xIntf); for (int k = 0; k < xMap.InterfaceMethods.Length; k++) { if (xMap.InterfaceMethods[k] == xMethodIntf) { xActualMethod = xMap.TargetMethods[k]; break; } } } } catch { } } if (aMethodsSet.Contains(xMethodIntf)) { if (!xEmittedMethods.ContainsKey(xMethodIntf)) { xEmittedMethods.Add(xMethodIntf, true); } } } } int? xBaseIndex = null; if (xType.BaseType == null) { xBaseIndex = (int)aGetTypeID(xType); } else { for (int t = 0; t < aTypesSet.Count; t++) { // todo: optimize check var xItem = aTypesSet.Skip(t).First(); if (xItem.ToString() == xType.BaseType.ToString()) { xBaseIndex = (int)aGetTypeID(xItem); break; } } } if (xBaseIndex == null) { throw new Exception("Base type not found!"); } for (int x = xEmittedMethods.Count - 1; x >= 0; x--) { if (!aMethodsSet.Contains(xEmittedMethods.Keys[x])) { xEmittedMethods.RemoveAt(x); } } if (!xType.IsInterface) { if (!xType.IsInterface) { Push(aGetTypeID(xType)); } Move("VMT__TYPE_ID_HOLDER__" + DataMember.FilterStringForIncorrectChars(LabelName.GetFullName(xType) + " ASM_IS__" + xType.Assembly.GetName().Name), (int)aGetTypeID(xType)); Cosmos.Assembler.Assembler.CurrentInstance.DataMembers.Add( new DataMember("VMT__TYPE_ID_HOLDER__" + DataMember.FilterStringForIncorrectChars(LabelName.GetFullName(xType) + " ASM_IS__" + xType.Assembly.GetName().Name), new int[] { (int)aGetTypeID(xType) })); Push((uint)xBaseIndex.Value); xData = new byte[16 + (xEmittedMethods.Count * 4)]; xTemp = BitConverter.GetBytes(aGetTypeID(typeof(Array))); Array.Copy(xTemp, 0, xData, 0, 4); xTemp = BitConverter.GetBytes(0x80000002); // embedded array Array.Copy(xTemp, 0, xData, 4, 4); xTemp = BitConverter.GetBytes(xEmittedMethods.Count); // embedded array Array.Copy(xTemp, 0, xData, 8, 4); xTemp = BitConverter.GetBytes(4); // embedded array Array.Copy(xTemp, 0, xData, 12, 4); string xDataName = "____SYSTEM____TYPE___" + DataMember.FilterStringForIncorrectChars(LabelName.GetFullName(xType) + " ASM_IS__" + xType.Assembly.GetName().Name) + "__MethodIndexesArray"; Cosmos.Assembler.Assembler.CurrentInstance.DataMembers.Add(new DataMember(xDataName, xData)); Push(xDataName); xDataName = "____SYSTEM____TYPE___" + DataMember.FilterStringForIncorrectChars(LabelName.GetFullName(xType) + " ASM_IS__" + xType.Assembly.GetName().Name) + "__MethodAddressesArray"; Cosmos.Assembler.Assembler.CurrentInstance.DataMembers.Add(new DataMember(xDataName, xData)); Push(xDataName); xData = new byte[16 + Encoding.Unicode.GetByteCount(xType.FullName + ", " + xType.Module.Assembly.GetName().FullName)]; xTemp = BitConverter.GetBytes(aGetTypeID(typeof(Array))); Array.Copy(xTemp, 0, xData, 0, 4); xTemp = BitConverter.GetBytes(0x80000002); // embedded array Array.Copy(xTemp, 0, xData, 4, 4); xTemp = BitConverter.GetBytes((xType.FullName + ", " + xType.Module.Assembly.GetName().FullName).Length); Array.Copy(xTemp, 0, xData, 8, 4); xTemp = BitConverter.GetBytes(2); // embedded array Array.Copy(xTemp, 0, xData, 12, 4); xDataName = "____SYSTEM____TYPE___" + DataMember.FilterStringForIncorrectChars(LabelName.GetFullName(xType) + " ASM_IS__" + xType.Assembly.GetName().Name); Cosmos.Assembler.Assembler.CurrentInstance.DataMembers.Add(new DataMember(xDataName, xData)); Push("0" + xEmittedMethods.Count.ToString("X") + "h"); Call(xSetTypeInfoRef); } for (int j = 0; j < xEmittedMethods.Count; j++) { MethodBase xMethod = xEmittedMethods.Keys[j]; #if VMT_DEBUG xVmtDebugOutput.WriteStartElement("Method"); xVmtDebugOutput.WriteAttributeString("Id", aGetMethodUID(xMethod).ToString()); xVmtDebugOutput.WriteAttributeString("Name", xMethod.GetFullName()); xVmtDebugOutput.WriteEndElement(); #endif var xMethodId = aGetMethodUID(xMethod); if (!xType.IsInterface) { if (xEmittedMethods.Values[j]) { var xNewMethod = xType.GetMethod(xMethod.DeclaringType.FullName + "." + xMethod.Name, (from xParam in xMethod.GetParameters() select xParam.ParameterType).ToArray()); if (xNewMethod == null) { // get private implementation xNewMethod = xType.GetMethod(xMethod.Name, (from xParam in xMethod.GetParameters() select xParam.ParameterType).ToArray()); } if (xNewMethod == null) { try { var xMap = xType.GetInterfaceMap(xMethod.DeclaringType); for (int k = 0; k < xMap.InterfaceMethods.Length; k++) { if (xMap.InterfaceMethods[k] == xMethod) { xNewMethod = xMap.TargetMethods[k]; break; } } } catch { } } xMethod = xNewMethod; } Push((uint)aGetTypeID(xType)); Push((uint)j); Push((uint)xMethodId); if (xMethod.IsAbstract) { // abstract methods dont have bodies, oiw, are not emitted Push(0); } else { Push(ILOp.GetMethodLabel(xMethod)); } Push(0); Call(VTablesImplRefs.SetMethodInfoRef); } } #if VMT_DEBUG xVmtDebugOutput.WriteEndElement(); // type #endif } #if VMT_DEBUG xVmtDebugOutput.WriteEndElement(); // types xVmtDebugOutput.WriteEndDocument(); } #endif new Cosmos.Assembler.Label("_END_OF_" + InitVMTCodeLabel); new Pop { DestinationReg = Registers.EBP }; new Return(); } public void ProcessField(FieldInfo aField) { string xFieldName = LabelName.GetFullName(aField); xFieldName = DataMember.GetStaticFieldName(aField); if (Cosmos.Assembler.Assembler.CurrentInstance.DataMembers.Count(x => x.Name == xFieldName) == 0) { var xItemList = (from item in aField.GetCustomAttributes(false) where item.GetType().FullName == "ManifestResourceStreamAttribute" select item).ToList(); object xItem = null; if (xItemList.Count > 0) xItem = xItemList[0]; string xManifestResourceName = null; if (xItem != null) { var xItemType = xItem.GetType(); xManifestResourceName = (string)xItemType.GetField("ResourceName").GetValue(xItem); } if (xManifestResourceName != null) { // todo: add support for manifest streams again //RegisterType(xCurrentField.FieldType); //string xFileName = Path.Combine(mOutputDir, // (xCurrentField.DeclaringType.Assembly.FullName + "__" + xManifestResourceName).Replace(",", // "_") + ".res"); //using (var xStream = xCurrentField.DeclaringType.Assembly.GetManifestResourceStream(xManifestResourceName)) { // if (xStream == null) { // throw new Exception("Resource '" + xManifestResourceName + "' not found!"); // } // using (var xTarget = File.Create(xFileName)) { // // todo: abstract this array code out. // xTarget.Write(BitConverter.GetBytes(Engine.RegisterType(Engine.GetType("mscorlib", // "System.Array"))), // 0, // 4); // xTarget.Write(BitConverter.GetBytes((uint)InstanceTypeEnum.StaticEmbeddedArray), // 0, // 4); // xTarget.Write(BitConverter.GetBytes((int)xStream.Length), 0, 4); // xTarget.Write(BitConverter.GetBytes((int)1), 0, 4); // var xBuff = new byte[128]; // while (xStream.Position < xStream.Length) { // int xBytesRead = xStream.Read(xBuff, 0, 128); // xTarget.Write(xBuff, 0, xBytesRead); // } // } //} //Assembler.DataMembers.Add(new DataMember("___" + xFieldName + "___Contents", // "incbin", // "\"" + xFileName + "\"")); //Assembler.DataMembers.Add(new DataMember(xFieldName, // "dd", // "___" + xFieldName + "___Contents")); throw new NotImplementedException(); } else { uint xTheSize; //string theType = "db"; Type xFieldTypeDef = aField.FieldType; if (!xFieldTypeDef.IsClass || xFieldTypeDef.IsValueType) { xTheSize = GetSizeOfType(aField.FieldType); } else { xTheSize = 4; } byte[] xData = new byte[xTheSize]; try { object xValue = aField.GetValue(null); if (xValue != null) { try { Type xTyp = xValue.GetType(); if(xTyp.IsEnum) { xValue = Convert.ChangeType(xValue, Enum.GetUnderlyingType(xTyp)); } if (xTyp.IsValueType) { for (int x = 0; x < xTheSize; x++) { xData[x] = Marshal.ReadByte(xValue, x); } } } catch { } } } catch { } Cosmos.Assembler.Assembler.CurrentInstance.DataMembers.Add(new DataMember(xFieldName, xData)); } } } public uint GetSizeOfType(Type aType) { return ILOp.SizeOfType(aType); } /// /// Generates a forwarding stub, which transforms from the actual method to the plug. /// /// The method to forward to the plug /// The plug internal void GenerateMethodForward(MethodInfo aFrom, MethodInfo aTo) { // todo: completely get rid of this kind of trampoline code MethodBegin(aFrom); { var xParams = aTo.MethodBase.GetParameters().ToArray(); if (aTo.MethodAssembler != null) { xParams = aFrom.MethodBase.GetParameters(); } int xCurParamIdx = 0; if (!aFrom.MethodBase.IsStatic) { Ldarg(aFrom, 0); xCurParamIdx++; if (aTo.MethodAssembler == null) { xParams = xParams.Skip(1).ToArray(); } } foreach (var xParam in xParams) { FieldAccessAttribute xFieldAccessAttrib = null; foreach (var xAttrib in xParam.GetCustomAttributes(typeof(FieldAccessAttribute), true)) { xFieldAccessAttrib = xAttrib as FieldAccessAttribute; } if (xFieldAccessAttrib != null) { // field access new Comment("Loading address of field '" + xFieldAccessAttrib.Name + "'"); var xFieldInfo = ResolveField(aFrom, xFieldAccessAttrib.Name, false); if (xFieldInfo.IsStatic) { Ldsflda(aFrom, xFieldInfo); } else { Ldarg(aFrom, 0); Ldflda(aFrom, xFieldInfo); } } else { // normal field access new Comment("Loading parameter " + xCurParamIdx); Ldarg(aFrom, xCurParamIdx); xCurParamIdx++; } } var xMethodLabel = ILOp.GetMethodLabel(aFrom); var xEndOfMethodLabel = xMethodLabel + EndOfMethodLabelNameNormal; Call(aFrom, aTo, xEndOfMethodLabel); } MethodEnd(aFrom); } protected static void WriteDebug(MethodBase aMethod, uint aSize, uint aSize2) { var xLine = String.Format("{0}\t{1}\t{2}", LabelName.GenerateFullName(aMethod), aSize, aSize2); } // These are all temp functions until we move to the new assembler. // They are used to clean up the old assembler slightly while retaining compatibiltiy for now public static string TmpPosLabel(MethodInfo aMethod, int aOffset) { return ILOp.GetLabel(aMethod, aOffset); } public static string TmpPosLabel(MethodInfo aMethod, ILOpCode aOpCode) { return TmpPosLabel(aMethod, aOpCode.Position); } public static string TmpBranchLabel(MethodInfo aMethod, ILOpCode aOpCode) { return TmpPosLabel(aMethod, ((ILOpCodes.OpBranch)aOpCode).Value); } public void EmitEntrypoint(MethodBase aEntrypoint) { // at the time the datamembers for literal strings are created, the type id for string is not yet determined. // for now, we fix this at runtime. new Cosmos.Assembler.Label(InitStringIDsLabel); new Push { DestinationReg = Registers.EBP }; new Mov { DestinationReg = Registers.EBP, SourceReg = Registers.ESP }; new Mov { DestinationReg = Registers.EAX, SourceRef = Cosmos.Assembler.ElementReference.New(ILOp.GetTypeIDLabel(typeof(String))), SourceIsIndirect = true }; new Mov { DestinationRef = ElementReference.New("static_field__System_String_Empty"), DestinationIsIndirect = true, SourceRef = ElementReference.New(X86.IL.LdStr.GetContentsArrayName("")) }; var xMemberId = 0; foreach (var xDataMember in Assembler.DataMembers) { if (!xDataMember.Name.StartsWith("StringLiteral")) { continue; } if (xDataMember.Name.EndsWith("__Contents")) { continue; } if (xMemberId % 100 == 0) { Assembler.WriteDebugVideo("."); } xMemberId ++; new Mov { DestinationRef = Cosmos.Assembler.ElementReference.New(xDataMember.Name), DestinationIsIndirect = true, SourceReg = Registers.EAX }; } Assembler.WriteDebugVideo("Done"); new Pop { DestinationReg = Registers.EBP }; new Return(); new Cosmos.Assembler.Label(Cosmos.Assembler.Assembler.EntryPointName); new Push { DestinationReg = Registers.EBP }; new Mov { DestinationReg = Registers.EBP, SourceReg = Registers.ESP }; new Call { DestinationLabel = InitVMTCodeLabel }; Assembler.WriteDebugVideo("Initializing string IDs."); new Call { DestinationLabel = InitStringIDsLabel }; Assembler.WriteDebugVideo("Done initializing string IDs"); // we now need to do "newobj" on the entry point, and after that, call .Start on it var xCurLabel = Cosmos.Assembler.Assembler.EntryPointName + ".CreateEntrypoint"; new Cosmos.Assembler.Label(xCurLabel); Assembler.WriteDebugVideo("Now create kernel class"); X86.IL.Newobj.Assemble(Cosmos.Assembler.Assembler.CurrentInstance, null, null, xCurLabel, aEntrypoint.DeclaringType, aEntrypoint); xCurLabel = Cosmos.Assembler.Assembler.EntryPointName + ".CallStart"; new Cosmos.Assembler.Label(xCurLabel); X86.IL.Call.DoExecute(Assembler, null, aEntrypoint.DeclaringType.BaseType.GetMethod("Start"), null, xCurLabel, Cosmos.Assembler.Assembler.EntryPointName + ".AfterStart", DebugEnabled); new Cosmos.Assembler.Label(Cosmos.Assembler.Assembler.EntryPointName + ".AfterStart"); new Pop { DestinationReg = Registers.EBP }; new Return(); if (ShouldOptimize) { Orvid.Optimizer.Optimize(Assembler); } } protected void AfterOp(MethodInfo aMethod, ILOpCode aOpCode) { } protected void BeforeOp(MethodInfo aMethod, ILOpCode aOpCode, bool emitInt3NotNop, out bool INT3Emitted, bool hasSourcePoint) { string xLabel = TmpPosLabel(aMethod, aOpCode); Assembler.CurrentIlLabel = xLabel; new Cosmos.Assembler.Label(xLabel); if (aMethod.MethodBase.DeclaringType != typeof(VTablesImpl)) { Assembler.EmitAsmLabels = false; try { //Assembler.WriteDebugVideo(String.Format("Method {0}:{1}.", aMethod.UID, aOpCode.Position.ToString("X"))); //Assembler.WriteDebugVideo(xLabel); } finally { Assembler.EmitAsmLabels = true; } } uint? xStackDifference = null; if (mSymbols != null) { var xMLSymbol = new MethodIlOp(); xMLSymbol.LabelName = xLabel; var xStackSize = aOpCode.StackOffsetBeforeExecution.Value; xMLSymbol.StackDiff = -1; if (aMethod.MethodBase != null) { var xBody = aMethod.MethodBase.GetMethodBody(); if (xBody != null) { var xLocalsSize = (from item in xBody.LocalVariables select ILOp.Align(ILOp.SizeOfType(item.LocalType), 4)).Sum(); xMLSymbol.StackDiff = checked((int)(xLocalsSize + xStackSize)); xStackDifference = (uint?)xMLSymbol.StackDiff; } } xMLSymbol.IlOffset = aOpCode.Position; xMLSymbol.MethodID = mCurrentMethodGuid; mSymbols.Add(xMLSymbol); DebugInfo.AddSymbols(mSymbols); } DebugInfo.AddSymbols(mSymbols, false); bool INT3PlaceholderEmitted = false; EmitTracer(aMethod, aOpCode, aMethod.MethodBase.DeclaringType.Namespace, emitInt3NotNop, out INT3Emitted, out INT3PlaceholderEmitted, hasSourcePoint); if (INT3Emitted || INT3PlaceholderEmitted) { var xINT3Label = new INT3Label(); xINT3Label.LabelName = xLabel; xINT3Label.MethodID = mCurrentMethodGuid; xINT3Label.LeaveAsINT3 = INT3Emitted; mINT3Labels.Add(xINT3Label); DebugInfo.AddINT3Labels(mINT3Labels); } if (DebugEnabled && StackCorruptionDetection) { // if debugstub is active, emit a stack corruption detection. at this point, the difference between EBP and ESP // should be equal to the local variables sizes and the IL stack. // if not, we should break here. // first, calculate the expected difference if (xStackDifference == null) { xStackDifference = aMethod.LocalVariablesSize; xStackDifference += aOpCode.StackOffsetBeforeExecution; } new Comment("Stack difference = " + xStackDifference); // if debugstub is active, emit a stack corruption detection. at this point EBP and ESP should have the same value. // if not, we should somehow break here. new Mov { DestinationReg = Registers.EAX, SourceReg = RegistersEnum.ESP }; new Mov { DestinationReg = Registers.EBX, SourceReg = RegistersEnum.EBP }; new Add { DestinationReg = Registers.EAX, SourceValue = xStackDifference }; new Compare { SourceReg = RegistersEnum.EAX, DestinationReg = RegistersEnum.EBX }; new ConditionalJump { Condition = ConditionalTestEnum.Equal, DestinationLabel = xLabel + ".StackCorruptionCheck_End" }; new ClrInterruptFlag(); // don't remove the call. It seems pointless, but we need it to retrieve the EIP value new Call { DestinationLabel = xLabel + ".StackCorruptionCheck_GetAddress" }; new Assembler.Label(xLabel + ".StackCorruptionCheck_GetAddress"); new Pop { DestinationReg = RegistersEnum.EAX }; new Mov { DestinationRef = ElementReference.New("DebugStub_CallerEIP"), DestinationIsIndirect = true, SourceReg = RegistersEnum.EAX }; new Call { DestinationLabel = "DebugStub_SendStackCorruptionOccurred" }; new Halt(); new Assembler.Label(xLabel + ".StackCorruptionCheck_End"); } if (xLabel == "SystemUInt32CosmosCorePlugsGCImplementionImplAllocNewObjectSystemUInt32.IL_0001") { // } } protected void EmitTracer(MethodInfo aMethod, ILOpCode aOp, string aNamespace, bool emitInt3NotNop, out bool INT3Emitted, out bool INT3PlaceholderEmitted, bool isNewSourcePoint) { // NOTE - These if statements can be optimized down - but clarity is // more important than the optimizations. Furthermore the optimizations available // would not offer much benefit // Determine if a new DebugStub should be emitted INT3Emitted = false; INT3PlaceholderEmitted = false; if (aOp.OpCode == ILOpCode.Code.Nop) { // Skip NOOP's so we dont have breakpoints on them //TODO: Each IL op should exist in IL, and descendants in IL.X86. // Because of this we have this hack return; } else if (DebugEnabled == false) { return; } else if (DebugMode == DebugMode.Source) { // If the current position equals one of the offsets, then we have // reached a new atomic C# statement if (!isNewSourcePoint) { return; } } // Check if the DebugStub has been disabled for this method if ((!IgnoreDebugStubAttribute) && (aMethod.DebugStubOff)) { return; } // This test fixes issue #15638 if (null != aNamespace) { // Check options for Debug Level // Set based on TracedAssemblies if (TraceAssemblies == TraceAssemblies.Cosmos || TraceAssemblies == TraceAssemblies.User) { if (aNamespace.StartsWith("System.", StringComparison.InvariantCultureIgnoreCase)) { return; } else if (aNamespace.ToLower() == "system") { return; } else if (aNamespace.StartsWith("Microsoft.", StringComparison.InvariantCultureIgnoreCase)) { return; } if (TraceAssemblies == TraceAssemblies.User) { //TODO: Maybe an attribute that could be used to turn tracing on and off //TODO: This doesnt match Cosmos.Kernel exact vs Cosmos.Kernel., so a user // could do Cosmos.KernelMine and it will fail. Need to fix this if (aNamespace.StartsWith("Cosmos.Kernel", StringComparison.InvariantCultureIgnoreCase)) { return; } else if (aNamespace.StartsWith("Cosmos.System", StringComparison.InvariantCultureIgnoreCase)) { return; } else if (aNamespace.StartsWith("Cosmos.HAL", StringComparison.InvariantCultureIgnoreCase)) { return; } else if (aNamespace.StartsWith("Cosmos.IL2CPU", StringComparison.InvariantCultureIgnoreCase)) { return; } } } } // If we made it this far without a return, emit the Tracer // We used to emit an INT3, but this meant the DS would brwak after every C# line // Breaking that frequently is of course, pointless and slow. // So now we emit mostly NOPs and only put an INT3 when told to. // We should only be told to put an INT3 at the start of method but this may change so search for more comments on this. if (emitInt3NotNop) { INT3Emitted = true; new INT3(); } else { INT3PlaceholderEmitted = true; new DebugNoop(); } } protected MethodDefinition GetCecilMethodDefinitionForSymbolReading(MethodBase methodBase) { var xMethodBase = methodBase; if (xMethodBase.IsGenericMethod) { var xMethodInfo = (SysReflection.MethodInfo)xMethodBase; xMethodBase = xMethodInfo.GetGenericMethodDefinition(); if (xMethodBase.IsGenericMethod && !xMethodBase.IsGenericMethod) { // apparently, a generic method can be derived from a generic method.. throw new Exception("Make recursive"); } } var xLocation = xMethodBase.DeclaringType.Assembly.Location; ModuleDefinition xModule = null; if (!mLoadedModules.TryGetValue(xLocation, out xModule)) { // if not in cache, try loading. if (xMethodBase.DeclaringType.Assembly.GlobalAssemblyCache || !File.Exists(xLocation)) { // file doesn't exist, so assume no symbols mLoadedModules.Add(xLocation, null); return null; } else { try { xModule = ModuleDefinition.ReadModule(xLocation, new ReaderParameters { ReadSymbols = true, SymbolReaderProvider = new Mono.Cecil.Pdb.PdbReaderProvider() }); } catch (InvalidOperationException) { throw new Exception("Please check that dll and pdb file is matching on location: " + xLocation); } if (xModule.HasSymbols) { mLoadedModules.Add(xLocation, xModule); } else { mLoadedModules.Add(xLocation, null); return null; } } } if (xModule == null) { return null; } // todo: cache MethodDefinition ? return xModule.LookupToken(xMethodBase.MetadataToken) as MethodDefinition; } } }