From dfdc58ee53fb080d0c2c4fc315311903b07ec55b Mon Sep 17 00:00:00 2001 From: Matthijs ter Woord Date: Fri, 19 Dec 2014 20:02:59 +0100 Subject: [PATCH] Improved cpu exception tracing. --- Demos/Guess/Guess.Cosmos | 568 ++--- source/Cosmos.Assembler/Assembler.cs | 1026 ++++----- source/Cosmos.Core/INTs.cs | 1254 +++++----- source/Cosmos.IL2CPU/.editorconfig | 27 +- source/Cosmos.IL2CPU/AppAssembler.cs | 3165 +++++++++++++------------- 5 files changed, 3027 insertions(+), 3013 deletions(-) diff --git a/Demos/Guess/Guess.Cosmos b/Demos/Guess/Guess.Cosmos index 939ae9ad4..a7b7edc80 100644 --- a/Demos/Guess/Guess.Cosmos +++ b/Demos/Guess/Guess.Cosmos @@ -1,285 +1,285 @@ - - - - Debug - 2.0 - {5b2c0369-7a84-4113-892c-f71b50e1da12} - false - Guess - elf - Guess - SAK - SAK - SAK - SAK - v4.5 - - - VMware - true - bin\Debug\ - MicrosoftNET - False - False - Source - User - PXE - Player - False - Player - ISO - False - ISO - VMware - True - User - PXE - None - False - Source - - Player - bin\Debug\ - MicrosoftNET - False - - False - false - USB - None - False - Source - False - Player - bin\Debug\ - MicrosoftNET - False - All - False - false - ISO - VMware - True - Source - False - Player - bin\Debug\ - MicrosoftNET - False - User - False - False - ISO - None - False - Source - False - Player - bin\Debug\ - MicrosoftNET - False - All - False - false - Guess - Use VMware Player or Workstation to deploy and debug. - User 001 - Creates a bootable ISO image which can be burned to a DVD. After running the selected project, an explorer window will open containing the ISO file. The ISO file can then be burned to a CD or DVD and used to boot a physical or virtual system. - Guess - Makes a USB device such as a flash drive or external hard disk bootable. - PXE with Slave (Kudzu) - Creates a PXE setup and hosts a DCHP and TFTP server to deploy directly to physical hardware. Allows debugging with a serial cable. - Guess - Use VMware Player or Workstation to deploy and debug. - PXE with Slave (Kudzu) - Creates a PXE setup and hosts a DCHP and TFTP server to deploy directly to physical hardware. Allows debugging with a serial cable. - PXE - Slave - True - Source - - Player - bin\Debug\ - MicrosoftNET - False - - False - false - 192.168.43.1 - Serial: COM3 - 192.168.43.1 - Serial: COM3 - 192.168.43.1 - Serial: COM3 - 192.168.43.1 - Serial: COM3 - 192.168.43.1 - Serial: COM3 - Pipe: Cosmos\Serial - Pipe: Cosmos\Serial - 192.168.43.1 - Serial: COM3 - Pipe: Cosmos\Serial - Serial: COM1 - False - False - False - False - False - Pipe: Cosmos\Serial - False - Pipe: Cosmos\Serial - - - VMware - true - bin\Debug\ - MicrosoftNET - False - False - Source - User - PXE - Player - False - Player - ISO - False - ISO - VMware - True - User - PXE - None - False - Source - - Player - bin\Debug\ - MicrosoftNET - False - - False - false - USB - None - False - Source - False - Player - bin\Debug\ - MicrosoftNET - False - All - False - false - ISO - VMware - True - Source - False - Player - bin\Debug\ - MicrosoftNET - False - User - False - False - ISO - None - False - Source - False - Player - bin\Debug\ - MicrosoftNET - False - All - False - false - Guess - Use VMware Player or Workstation to deploy and debug. - User 001 - Creates a bootable ISO image which can be burned to a DVD. After running the selected project, an explorer window will open containing the ISO file. The ISO file can then be burned to a CD or DVD and used to boot a physical or virtual system. - Guess - Makes a USB device such as a flash drive or external hard disk bootable. - PXE with Slave (Kudzu) - Creates a PXE setup and hosts a DCHP and TFTP server to deploy directly to physical hardware. Allows debugging with a serial cable. - Guess - Use VMware Player or Workstation to deploy and debug. - PXE with Slave (Kudzu) - Creates a PXE setup and hosts a DCHP and TFTP server to deploy directly to physical hardware. Allows debugging with a serial cable. - PXE - Slave - True - Source - - Player - bin\Debug\ - MicrosoftNET - False - - False - false - Serial: COM1 - Serial: COM1 - Serial: COM1 - Pipe: Cosmos\Serial - - Pipe: Cosmos\Serial - - Pipe: Cosmos\Serial - - Pipe: Cosmos\Serial - - Pipe: Cosmos\Serial - 192.168.43.1 - Serial: COM3 - 192.168.43.1 - Serial: COM3 - 192.168.43.1 - Serial: COM3 - 192.168.43.1 - Serial: COM3 - 192.168.43.1 - Serial: COM3 - 192.168.43.1 - Serial: COM3 - False - False - False - False - False - False - - - - Content - - - - - Cosmos.Core.Plugs - {d9a87aad-fcc9-4517-b31d-e904dad00784} - True - - - Cosmos.Debug.Kernel.Plugs - {b97a2956-c363-47f2-a6aa-b4fccff4d315} - True - - - Cosmos.System.Plugs - {7c64b97f-516d-4a6d-b9e1-3fe48f561409} - True - - - GuessKernel - {73cf15c1-0ab6-4b3a-8142-d326ada72970} - True - - - - - + + + + Debug + 2.0 + {5b2c0369-7a84-4113-892c-f71b50e1da12} + false + Guess + elf + Guess + SAK + SAK + SAK + SAK + v4.5 + + + VMware + true + bin\Debug\ + MicrosoftNET + False + False + Source + User + PXE + Player + False + Player + ISO + False + ISO + VMware + True + User + PXE + None + False + Source + + Player + bin\Debug\ + MicrosoftNET + False + + False + false + USB + None + False + Source + False + Player + bin\Debug\ + MicrosoftNET + False + All + False + false + ISO + VMware + True + Source + False + Player + bin\Debug\ + MicrosoftNET + False + User + False + False + ISO + None + False + Source + False + Player + bin\Debug\ + MicrosoftNET + False + All + False + false + Guess + Use VMware Player or Workstation to deploy and debug. + User 001 + Creates a bootable ISO image which can be burned to a DVD. After running the selected project, an explorer window will open containing the ISO file. The ISO file can then be burned to a CD or DVD and used to boot a physical or virtual system. + Guess + Makes a USB device such as a flash drive or external hard disk bootable. + PXE with Slave (Kudzu) + Creates a PXE setup and hosts a DCHP and TFTP server to deploy directly to physical hardware. Allows debugging with a serial cable. + Guess + Use VMware Player or Workstation to deploy and debug. + PXE with Slave (Kudzu) + Creates a PXE setup and hosts a DCHP and TFTP server to deploy directly to physical hardware. Allows debugging with a serial cable. + PXE + Slave + True + Source + + Player + bin\Debug\ + MicrosoftNET + False + + False + false + 192.168.43.1 + Serial: COM3 + 192.168.43.1 + Serial: COM3 + 192.168.43.1 + Serial: COM3 + 192.168.43.1 + Serial: COM3 + 192.168.43.1 + Serial: COM3 + Pipe: Cosmos\Serial + Pipe: Cosmos\Serial + 192.168.43.1 + Serial: COM3 + Pipe: Cosmos\Serial + Serial: COM1 + False + False + False + False + False + Pipe: Cosmos\Serial + False + Pipe: Cosmos\Serial + + + VMware + true + bin\Debug\ + MicrosoftNET + False + False + Source + User + PXE + Player + False + Player + ISO + False + ISO + VMware + True + User + PXE + None + False + Source + + Player + bin\Debug\ + MicrosoftNET + False + + False + false + USB + None + False + Source + False + Player + bin\Debug\ + MicrosoftNET + False + All + False + false + ISO + VMware + True + Source + False + Player + bin\Debug\ + MicrosoftNET + False + User + False + False + ISO + None + False + Source + False + Player + bin\Debug\ + MicrosoftNET + False + All + False + false + Guess + Use VMware Player or Workstation to deploy and debug. + User 001 + Creates a bootable ISO image which can be burned to a DVD. After running the selected project, an explorer window will open containing the ISO file. The ISO file can then be burned to a CD or DVD and used to boot a physical or virtual system. + Guess + Makes a USB device such as a flash drive or external hard disk bootable. + PXE with Slave (Kudzu) + Creates a PXE setup and hosts a DCHP and TFTP server to deploy directly to physical hardware. Allows debugging with a serial cable. + Guess + Use VMware Player or Workstation to deploy and debug. + PXE with Slave (Kudzu) + Creates a PXE setup and hosts a DCHP and TFTP server to deploy directly to physical hardware. Allows debugging with a serial cable. + PXE + Slave + True + Source + + Player + bin\Debug\ + MicrosoftNET + False + + False + false + Serial: COM1 + Serial: COM1 + Serial: COM1 + Pipe: Cosmos\Serial + + Pipe: Cosmos\Serial + + Pipe: Cosmos\Serial + + Pipe: Cosmos\Serial + + Pipe: Cosmos\Serial + 192.168.43.1 + Serial: COM3 + 192.168.43.1 + Serial: COM3 + 192.168.43.1 + Serial: COM3 + 192.168.43.1 + Serial: COM3 + 192.168.43.1 + Serial: COM3 + 192.168.43.1 + Serial: COM3 + False + False + False + False + False + False + + + + Content + + + + + Cosmos.Core.Plugs + {d9a87aad-fcc9-4517-b31d-e904dad00784} + True + + + Cosmos.Debug.Kernel.Plugs + {b97a2956-c363-47f2-a6aa-b4fccff4d315} + True + + + Cosmos.System.Plugs + {7c64b97f-516d-4a6d-b9e1-3fe48f561409} + True + + + GuessKernel + {73cf15c1-0ab6-4b3a-8142-d326ada72970} + True + + + + + \ No newline at end of file diff --git a/source/Cosmos.Assembler/Assembler.cs b/source/Cosmos.Assembler/Assembler.cs index ec5c714c6..42f85784a 100644 --- a/source/Cosmos.Assembler/Assembler.cs +++ b/source/Cosmos.Assembler/Assembler.cs @@ -1,514 +1,514 @@ -// please leave the next directive (and related code) in, just disable the directive -//#define VMT_DEBUG -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection.Emit; -using System.Text; -using System.Threading; -using System.IO; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Xml; -using Cosmos.Assembler.x86; - -namespace Cosmos.Assembler { - public class Assembler { - public Assembler(int aComPort) { - mCurrentInstance = this; - mComPort = aComPort; - } - - public bool EmitAsmLabels { get; set; } - //TODO: COM Port info - should be in assembler? Assembler should not know about comports... - protected int mComPort = 0; - protected UInt16 mGdCode; - protected UInt16 mGdData; - - // Contains info on the current stack structure. What type are on the stack, etc - //public readonly StackContents Stack = new StackContents(); - - // This is a hack, hope to fix it in the future - // as it will also cause problems when we thread the compiler - private static Assembler mCurrentInstance; - - private static string GetValidGroupName(string aGroup) { - return aGroup.Replace('-', '_').Replace('.', '_'); - } - public const string EntryPointName = "__ENGINE_ENTRYPOINT__"; - - protected byte[] GdtDescriptor(UInt32 aBase, UInt32 aSize, bool aCode) { - // Limit is a confusing word. Is it the max physical address or size? - // In fact it is the size, and 286 docs actually refer to it as size - // rather than limit. - // It is also size - 1, else there would be no way to specify - // all of RAM, and a limit of 0 is invalid. - - var xResult = new byte[8]; - - // Check the limit to make sure that it can be encoded - if ((aSize > 65536) && (aSize & 0x0FFF) != 0x0FFF) { - // If larger than 16 bit, must be an even page (4kb) size - throw new Exception("Invalid size in GDT descriptor."); - } - // Flags nibble - // 7: Granularity - // 0 = bytes - // 1 = 4kb pages - // 6: 1 = 32 bit mode - // 5: 0 - Reserved - // 4: 0 - Reserved - xResult[6] = 0x40; - if (aSize > 65536) { - // Set page sizing instead of byte sizing - aSize = aSize >> 12; - xResult[6] = (byte)(xResult[6] | 0x80); - } - - xResult[0] = (byte)(aSize & 0xFF); - xResult[1] = (byte)((aSize >> 8) & 0xFF); - xResult[6] = (byte)(xResult[6] | ((aSize >> 16) & 0x0F)); - - xResult[2] = (byte)(aBase & 0xFF); - xResult[3] = (byte)((aBase >> 8) & 0xFF); - xResult[4] = (byte)((aBase >> 16) & 0xFF); - xResult[7] = (byte)((aBase >> 24) & 0xFF); - - xResult[5] = (byte)( - // Bit 7: Present, must be 1 - 0x80 | - // Bit 6-5: Privilege, 0=kernel, 3=user - 0x00 | - // Reserved, must be 1 - 0x10 | - // Bit 3: 1=Code, 0=Data - (aCode ? 0x08 : 0x00) | - // Bit 2: Direction/Conforming - 0x00 | - // Bit 1: R/W Data (1=Writeable, 0=Read only) Code (1=Readable, 0=Not readable) - 0x02 | - // Bit 0: Accessed - Set to 0. Updated by CPU later. - 0x00 - ); - - return xResult; - } - - protected string mCurrentIlLabel; - public string CurrentIlLabel { - get { return mCurrentIlLabel; } - set { - mCurrentIlLabel = value; - mAsmIlIdx = 0; - } - } - - protected int mAsmIlIdx; - public int AsmIlIdx { - get { return mAsmIlIdx; } - } - - protected List mDataMembers = new List(); - public List DataMembers { - get { return mDataMembers; } - set { mDataMembers = value; } - } - - protected internal List mInstructions = new List(); - public List Instructions { - get { return mInstructions; } - set { mInstructions = value; } - } - - public static Assembler CurrentInstance { - get { return mCurrentInstance; } - } - - internal int AllAssemblerElementCount { - get { return mInstructions.Count + mDataMembers.Count; } - } - - public BaseAssemblerElement GetAssemblerElement(int aIndex) { - if (aIndex >= mInstructions.Count) { - return mDataMembers[aIndex - mInstructions.Count]; - } - return mInstructions[aIndex]; - } - - public BaseAssemblerElement TryResolveReference(Cosmos.Assembler.ElementReference aReference) { - foreach (var xInstruction in mInstructions) { - var xLabel = xInstruction as Label; - if (xLabel != null) { - if (xLabel.QualifiedName.Equals(aReference.Name, StringComparison.InvariantCultureIgnoreCase)) { - return xLabel; - } - } - } - foreach (var xDataMember in mDataMembers) { - if (xDataMember.Name.Equals(aReference.Name, StringComparison.InvariantCultureIgnoreCase)) { - return xDataMember; - } - } - return null; - } - - public void Add(Instruction aReader) { - if (aReader is Label || aReader is Comment) - { - } - else if (EmitAsmLabels) - { - // Only issue label if its executable code. - new Label("." + AsmIlIdx.ToString("X2"), "Asm"); - mAsmIlIdx++; - } - mInstructions.Add(aReader); - } - - public void Add(params Instruction[] aReaders) { - mInstructions.Capacity += aReaders.Length; - foreach (Instruction xInstruction in aReaders) { - mInstructions.Add(xInstruction); - } - } - - // Allows to emit footers to the code and datamember sections - protected void OnBeforeFlush() { - DataMembers.AddRange(new DataMember[] { new DataMember("_end_data", new byte[0]) }); - new Label("_end_code"); - } - - private uint mDataMemberCounter = 0; - public string GetIdentifier(string aPrefix) { - mDataMemberCounter++; - return aPrefix + mDataMemberCounter.ToString("X4"); - } - private bool mFlushInitializationDone = false; - protected void BeforeFlush() { - if (mFlushInitializationDone) { - return; - } - mFlushInitializationDone = true; - OnBeforeFlush(); - //MergeAllElements(); - } - - public virtual void FlushBinary(Stream aOutput, ulong aBaseAddress) { - BeforeFlush(); - var xMax = AllAssemblerElementCount; - var xCurrentAddresss = aBaseAddress; - for (int i = 0; i < xMax; i++) { - GetAssemblerElement(i).UpdateAddress(this, ref xCurrentAddresss); - } - aOutput.SetLength(aOutput.Length + (long)(xCurrentAddresss - aBaseAddress)); - for (int i = 0; i < xMax; i++) { - var xItem = GetAssemblerElement(i); - if (!xItem.IsComplete(this)) { - throw new Exception("Incomplete element encountered."); - } - //var xBuff = xItem.GetData(this); - //aOutput.Write(xBuff, 0, xBuff.Length); - xItem.WriteData(this, aOutput); - } - } - - public virtual void FlushText(TextWriter aOutput) { - BeforeFlush(); - - // Write out data declarations - aOutput.WriteLine(); - foreach (DataMember xMember in mDataMembers) { - aOutput.Write("\t"); - if (xMember.IsComment) { - aOutput.Write(xMember.Name); - } else { - xMember.WriteText(this, aOutput); - } - aOutput.WriteLine(); - } - aOutput.WriteLine(); - - // Write out code - for (int i = 0; i < mInstructions.Count; i++) { - var xOp = mInstructions[i]; - string prefix = "\t\t\t"; - if (xOp is Label) { - var xLabel = (Label)xOp; - aOutput.WriteLine(); - prefix = "\t\t"; - aOutput.Write(prefix); - xLabel.WriteText(this, aOutput); - aOutput.WriteLine(); - } else { - aOutput.Write(prefix); - xOp.WriteText(this, aOutput); - aOutput.WriteLine(); - } - } - - aOutput.WriteLine("%ifndef ELF_COMPILATION"); - aOutput.WriteLine("use32"); - aOutput.WriteLine("org 0x200000"); - aOutput.WriteLine("[map all main.map]"); - aOutput.WriteLine("%endif"); - aOutput.WriteLine("global Kernel_Start"); - } - - static public void WriteDebugVideo(string aText) { - // This method emits a lot of ASM, but thats what we want becuase - // at this point we need ASM as simple as possible and completely transparent. - // No stack changes, no register mods, etc. - - // TODO: Add an option on the debug project properties to turn this off. - // Also see TokenPatterns.cs Checkpoint in X# - var xPreBootLogging = true; - if (xPreBootLogging) { - UInt32 xVideo = 0xB8000; - for (UInt32 i = xVideo; i < xVideo + 80 * 2; i = i + 2) { - new LiteralAssemblerCode("mov byte [0x" + i.ToString("X") + "], 0"); - new LiteralAssemblerCode("mov byte [0x" + (i + 1).ToString("X") + "], 0x02"); - } - - foreach (var xChar in aText) { - new LiteralAssemblerCode("mov byte [0x" + xVideo.ToString("X") + "], " + (byte)xChar); - xVideo = xVideo + 2; - } - } - } - - public void CreateGDT() { - new Comment(this, "BEGIN - Create GDT"); - var xGDT = new List(); - // Null Segment - Selector 0x00 - // Not used, but required by many emulators. - xGDT.AddRange(new byte[8] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); - // Code Segment - mGdCode = (byte)xGDT.Count; - xGDT.AddRange(GdtDescriptor(0x00000000, 0xFFFFFFFF, true)); - // Data Segment - Selector - mGdData = (byte)xGDT.Count; - xGDT.AddRange(GdtDescriptor(0x00000000, 0xFFFFFFFF, false)); - DataMembers.Add(new DataMember("_NATIVE_GDT_Contents", xGDT.ToArray())); - - new Comment("Tell CPU about GDT"); - var xGdtPtr = new UInt16[3]; - // Size of GDT Table - 1 - xGdtPtr[0] = (UInt16)(xGDT.Count - 1); - DataMembers.Add(new DataMember("_NATIVE_GDT_Pointer", xGdtPtr)); - new Mov { - DestinationRef = Cosmos.Assembler.ElementReference.New("_NATIVE_GDT_Pointer"), - DestinationIsIndirect = true, - DestinationDisplacement = 2, - SourceRef = Cosmos.Assembler.ElementReference.New("_NATIVE_GDT_Contents") - }; - new Mov { DestinationReg = Registers.EAX, SourceRef = Cosmos.Assembler.ElementReference.New("_NATIVE_GDT_Pointer") }; - new Lgdt { DestinationReg = Registers.EAX, DestinationIsIndirect = true }; - - new Comment("Set data segments"); - new Mov { DestinationReg = Registers.EAX, SourceValue = mGdData }; - new Mov { DestinationReg = Registers.DS, SourceReg = Registers.EAX }; - new Mov { DestinationReg = Registers.ES, SourceReg = Registers.EAX }; - new Mov { DestinationReg = Registers.FS, SourceReg = Registers.EAX }; - new Mov { DestinationReg = Registers.GS, SourceReg = Registers.EAX }; - new Mov { DestinationReg = Registers.SS, SourceReg = Registers.EAX }; - - new Comment("Force reload of code segment"); - new JumpToSegment { Segment = mGdCode, DestinationLabel = "Boot_FlushCsGDT" }; - new Label("Boot_FlushCsGDT"); - new Cosmos.Assembler.Comment(this, "END - Create GDT"); - } - - protected void SetIdtDescriptor(int aNo, string aLabel, bool aDisableInts) { - int xOffset = aNo * 8; - new Mov { DestinationReg = Registers.EAX, SourceRef = Cosmos.Assembler.ElementReference.New(aLabel) }; - var xIDT = Cosmos.Assembler.ElementReference.New("_NATIVE_IDT_Contents"); - new Mov { DestinationRef = xIDT, DestinationIsIndirect = true, DestinationDisplacement = xOffset, SourceReg = Registers.AL }; - new Mov { DestinationRef = xIDT, DestinationIsIndirect = true, DestinationDisplacement = xOffset + 1, SourceReg = Registers.AH }; - new ShiftRight { DestinationReg = Registers.EAX, SourceValue = 16 }; - new Mov { DestinationRef = xIDT, DestinationIsIndirect = true, DestinationDisplacement = xOffset + 6, SourceReg = Registers.AL }; - new Mov { DestinationRef = xIDT, DestinationIsIndirect = true, DestinationDisplacement = xOffset + 7, SourceReg = Registers.AH }; - - // Code Segment - new Mov { DestinationRef = xIDT, DestinationIsIndirect = true, DestinationDisplacement = xOffset + 2, SourceValue = mGdCode, Size = 16 }; - - // Reserved - new Mov { DestinationRef = xIDT, DestinationIsIndirect = true, DestinationDisplacement = xOffset + 4, SourceValue = 0x00, Size = 8 }; - - // Type - new Mov { DestinationRef = xIDT, DestinationIsIndirect = true, DestinationDisplacement = xOffset + 5, SourceValue = (byte)(aDisableInts ? 0x8E : 0x8F), Size = 8 }; - } - - public void CreateIDT() { - new Comment(this, "BEGIN - Create IDT"); - - // Create IDT - UInt16 xIdtSize = 8 * 256; - DataMembers.Add(new DataMember("_NATIVE_IDT_Contents", new byte[xIdtSize])); - // - if (mComPort > 0) { - SetIdtDescriptor(1, "DebugStub_TracerEntry", false); - SetIdtDescriptor(3, "DebugStub_TracerEntry", false); - } - //SetIdtDescriptor(1, "DebugStub_INT0"); - Change to GPF - - // Set IDT - DataMembers.Add(new DataMember("_NATIVE_IDT_Pointer", new UInt16[] { xIdtSize, 0, 0 })); - new Mov { - DestinationRef = Cosmos.Assembler.ElementReference.New("_NATIVE_IDT_Pointer"), - DestinationIsIndirect = true, - DestinationDisplacement = 2, - SourceRef = Cosmos.Assembler.ElementReference.New("_NATIVE_IDT_Contents") - }; - new Mov { DestinationReg = Registers.EAX, SourceRef = Cosmos.Assembler.ElementReference.New("_NATIVE_IDT_Pointer") }; - new Lidt { DestinationReg = Registers.EAX, DestinationIsIndirect = true }; - - new Comment(this, "END - Create IDT"); - } - - public void Initialize() { - uint xSig = 0x1BADB002; - - DataMembers.Add(new DataIfNotDefined("ELF_COMPILATION")); - DataMembers.Add(new DataMember("MultibootSignature", new uint[] { xSig })); - uint xFlags = 0x10003; - DataMembers.Add(new DataMember("MultibootFlags", xFlags)); - DataMembers.Add(new DataMember("MultibootChecksum", (int)(0 - (xFlags + xSig)))); - DataMembers.Add(new DataMember("MultibootHeaderAddr", Cosmos.Assembler.ElementReference.New("MultibootSignature"))); - DataMembers.Add(new DataMember("MultibootLoadAddr", Cosmos.Assembler.ElementReference.New("MultibootSignature"))); - DataMembers.Add(new DataMember("MultibootLoadEndAddr", Cosmos.Assembler.ElementReference.New("_end_code"))); - DataMembers.Add(new DataMember("MultibootBSSEndAddr", Cosmos.Assembler.ElementReference.New("_end_code"))); - DataMembers.Add(new DataMember("MultibootEntryAddr", Cosmos.Assembler.ElementReference.New("Kernel_Start"))); - DataMembers.Add(new DataEndIfDefined()); - - DataMembers.Add(new DataIfDefined("ELF_COMPILATION")); - xFlags = 0x00003; - DataMembers.Add(new DataMember("MultibootSignature", new uint[] { xSig })); - DataMembers.Add(new DataMember("MultibootFlags", xFlags)); - DataMembers.Add(new DataMember("MultibootChecksum", (int)(0 - (xFlags + xSig)))); - DataMembers.Add(new DataEndIfDefined()); - - // graphics info fields - DataMembers.Add(new DataMember("MultibootGraphicsRuntime_VbeModeInfoAddr", Int32.MaxValue)); - DataMembers.Add(new DataMember("MultibootGraphicsRuntime_VbeControlInfoAddr", Int32.MaxValue)); - DataMembers.Add(new DataMember("MultibootGraphicsRuntime_VbeMode", Int32.MaxValue)); - // memory - DataMembers.Add(new DataMember("MultiBootInfo_Memory_High", 0)); - DataMembers.Add(new DataMember("MultiBootInfo_Memory_Low", 0)); - DataMembers.Add(new DataMember("Before_Kernel_Stack", new byte[0x50000])); - DataMembers.Add(new DataMember("Kernel_Stack", new byte[0])); - DataMembers.Add(new DataMember("MultiBootInfo_Structure", new uint[1])); - - if (mComPort > 0) { - new Define("DEBUGSTUB"); - } - - // This is our first entry point. Multiboot uses this as Cosmos entry point. - new Label("Kernel_Start", isGlobal: true); - - // Displays "Cosmos" in top left. Used to make sure Cosmos is booted in case of hang. - // ie bootloader debugging. This must be the FIRST code, even before setup so we know - // we are being called properly by the bootloader and that if there are problems its - // somwhere in our code, not the bootloader. - WriteDebugVideo("Cosmos pre boot"); - - // For when using Bochs, causes a break ASAP on entry after initial Cosmos display. - new LiteralAssemblerCode("xchg bx, bx"); - - // CLI ASAP - WriteDebugVideo("Clearing interrupts."); - new ClrInterruptFlag(); - - WriteDebugVideo("Begin multiboot info."); - new Comment(this, "MultiBoot compliant loader provides info in registers: "); - new Comment(this, "EBX=multiboot_info "); - new Comment(this, "EAX=0x2BADB002 - check if it's really Multiboot loader "); - new Comment(this, " ;- copy mb info - some stuff for you "); - new Comment(this, "BEGIN - Multiboot Info"); - new Mov { DestinationRef = Cosmos.Assembler.ElementReference.New("MultiBootInfo_Structure"), DestinationIsIndirect = true, SourceReg = Registers.EBX }; - new Add { DestinationReg = Registers.EBX, SourceValue = 4 }; - new Mov { DestinationReg = Registers.EAX, SourceReg = Registers.EBX, SourceIsIndirect = true }; - new Mov { DestinationRef = Cosmos.Assembler.ElementReference.New("MultiBootInfo_Memory_Low"), DestinationIsIndirect = true, SourceReg = Registers.EAX }; - new Add { DestinationReg = Registers.EBX, SourceValue = 4 }; - new Mov { - DestinationReg = Registers.EAX, - SourceReg = Registers.EBX, - SourceIsIndirect = true - }; - new Mov { DestinationRef = Cosmos.Assembler.ElementReference.New("MultiBootInfo_Memory_High"), DestinationIsIndirect = true, SourceReg = Registers.EAX }; - new Mov { - DestinationReg = Registers.ESP, - SourceRef = Cosmos.Assembler.ElementReference.New("Kernel_Stack") - }; - new Comment(this, "END - Multiboot Info"); - - WriteDebugVideo("Creating GDT."); - CreateGDT(); - - WriteDebugVideo("Creating IDT."); - CreateIDT(); - -#if LFB_1024_8 - new Comment("Set graphics fields"); - new Move { DestinationReg = Registers.EBX, SourceRef = Cosmos.Assembler.ElementReference.New("MultiBootInfo_Structure"), SourceIsIndirect = true }; - new Move { DestinationReg = Registers.EAX, SourceReg = Registers.EBX, SourceIsIndirect = true, SourceDisplacement = 72 }; - new Move { DestinationRef = Cosmos.Assembler.ElementReference.New("MultibootGraphicsRuntime_VbeControlInfoAddr"), DestinationIsIndirect = true, SourceReg = Registers.EAX }; - new Move { DestinationReg = Registers.EAX, SourceReg = Registers.EBX, SourceIsIndirect = true, SourceDisplacement = 76 }; - new Move { DestinationRef = Cosmos.Assembler.ElementReference.New("MultibootGraphicsRuntime_VbeModeInfoAddr"), DestinationIsIndirect = true, SourceReg = Registers.EAX }; - new Move { DestinationReg = Registers.EAX, SourceReg = Registers.EBX, SourceIsIndirect = true, SourceDisplacement = 80 }; - new Move { DestinationRef = Cosmos.Assembler.ElementReference.New("MultibootGraphicsRuntime_VbeMode"), DestinationIsIndirect = true, SourceReg = Registers.EAX }; -#endif - - WriteDebugVideo("Initializing SSE."); - new Comment(this, "BEGIN - SSE Init"); - // CR4[bit 9]=1, CR4[bit 10]=1, CR0[bit 2]=0, CR0[bit 1]=1 - new Mov { DestinationReg = Registers.EAX, SourceReg = Registers.CR4 }; - new Or { DestinationReg = Registers.EAX, SourceValue = 0x100 }; - new Mov { DestinationReg = Registers.CR4, SourceReg = Registers.EAX }; - new Mov { DestinationReg = Registers.EAX, SourceReg = Registers.CR4 }; - new Or { DestinationReg = Registers.EAX, SourceValue = 0x200 }; - new Mov { DestinationReg = Registers.CR4, SourceReg = Registers.EAX }; - new Mov { DestinationReg = Registers.EAX, SourceReg = Registers.CR0 }; - - new And { DestinationReg = Registers.EAX, SourceValue = 0xfffffffd }; - new Mov { DestinationReg = Registers.CR0, SourceReg = Registers.EAX }; - new Mov { DestinationReg = Registers.EAX, SourceReg = Registers.CR0 }; - - new And { DestinationReg = Registers.EAX, SourceValue = 1 }; - new Mov { DestinationReg = Registers.CR0, SourceReg = Registers.EAX }; - new Comment(this, "END - SSE Init"); - - if (mComPort > 0) { - WriteDebugVideo("Initializing DebugStub."); - new Call { DestinationLabel = "DebugStub_Init" }; - } - - // Jump to Kernel entry point - WriteDebugVideo("Jumping to kernel."); - new Call { DestinationLabel = EntryPointName }; - - new Comment(this, "Kernel done - loop till next IRQ"); - new Label(".loop"); - new ClrInterruptFlag(); - new Halt(); - new Jump { DestinationLabel = ".loop" }; - - if (mComPort > 0) { - var xGen = new XSharp.Compiler.AsmGenerator(); - foreach (var xFile in Directory.GetFiles(Cosmos.Build.Common.CosmosPaths.DebugStubSrc, "*.xs")) { - var xAsm = xGen.Generate(xFile); - foreach (var xData in xAsm.Data) { - Cosmos.Assembler.Assembler.CurrentInstance.DataMembers.Add(new DataMember() { RawAsm = xData }); - } - foreach (var xCode in xAsm.Code) { - new LiteralAssemblerCode(xCode); - } - } - } else { - new Label("DebugStub_Step"); - new Return(); - } - // Start emitting assembly labels - Cosmos.Assembler.Assembler.CurrentInstance.EmitAsmLabels = true; - } - } +// please leave the next directive (and related code) in, just disable the directive +//#define VMT_DEBUG +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; +using System.Text; +using System.Threading; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Xml; +using Cosmos.Assembler.x86; + +namespace Cosmos.Assembler { + public class Assembler { + public Assembler(int aComPort) { + mCurrentInstance = this; + mComPort = aComPort; + } + + public bool EmitAsmLabels { get; set; } + //TODO: COM Port info - should be in assembler? Assembler should not know about comports... + protected int mComPort = 0; + protected UInt16 mGdCode; + protected UInt16 mGdData; + + // Contains info on the current stack structure. What type are on the stack, etc + //public readonly StackContents Stack = new StackContents(); + + // This is a hack, hope to fix it in the future + // as it will also cause problems when we thread the compiler + private static Assembler mCurrentInstance; + + private static string GetValidGroupName(string aGroup) { + return aGroup.Replace('-', '_').Replace('.', '_'); + } + public const string EntryPointName = "__ENGINE_ENTRYPOINT__"; + + protected byte[] GdtDescriptor(UInt32 aBase, UInt32 aSize, bool aCode) { + // Limit is a confusing word. Is it the max physical address or size? + // In fact it is the size, and 286 docs actually refer to it as size + // rather than limit. + // It is also size - 1, else there would be no way to specify + // all of RAM, and a limit of 0 is invalid. + + var xResult = new byte[8]; + + // Check the limit to make sure that it can be encoded + if ((aSize > 65536) && (aSize & 0x0FFF) != 0x0FFF) { + // If larger than 16 bit, must be an even page (4kb) size + throw new Exception("Invalid size in GDT descriptor."); + } + // Flags nibble + // 7: Granularity + // 0 = bytes + // 1 = 4kb pages + // 6: 1 = 32 bit mode + // 5: 0 - Reserved + // 4: 0 - Reserved + xResult[6] = 0x40; + if (aSize > 65536) { + // Set page sizing instead of byte sizing + aSize = aSize >> 12; + xResult[6] = (byte)(xResult[6] | 0x80); + } + + xResult[0] = (byte)(aSize & 0xFF); + xResult[1] = (byte)((aSize >> 8) & 0xFF); + xResult[6] = (byte)(xResult[6] | ((aSize >> 16) & 0x0F)); + + xResult[2] = (byte)(aBase & 0xFF); + xResult[3] = (byte)((aBase >> 8) & 0xFF); + xResult[4] = (byte)((aBase >> 16) & 0xFF); + xResult[7] = (byte)((aBase >> 24) & 0xFF); + + xResult[5] = (byte)( + // Bit 7: Present, must be 1 + 0x80 | + // Bit 6-5: Privilege, 0=kernel, 3=user + 0x00 | + // Reserved, must be 1 + 0x10 | + // Bit 3: 1=Code, 0=Data + (aCode ? 0x08 : 0x00) | + // Bit 2: Direction/Conforming + 0x00 | + // Bit 1: R/W Data (1=Writeable, 0=Read only) Code (1=Readable, 0=Not readable) + 0x02 | + // Bit 0: Accessed - Set to 0. Updated by CPU later. + 0x00 + ); + + return xResult; + } + + protected string mCurrentIlLabel; + public string CurrentIlLabel { + get { return mCurrentIlLabel; } + set { + mCurrentIlLabel = value; + mAsmIlIdx = 0; + } + } + + protected int mAsmIlIdx; + public int AsmIlIdx { + get { return mAsmIlIdx; } + } + + protected List mDataMembers = new List(); + public List DataMembers { + get { return mDataMembers; } + set { mDataMembers = value; } + } + + protected internal List mInstructions = new List(); + public List Instructions { + get { return mInstructions; } + set { mInstructions = value; } + } + + public static Assembler CurrentInstance { + get { return mCurrentInstance; } + } + + internal int AllAssemblerElementCount { + get { return mInstructions.Count + mDataMembers.Count; } + } + + public BaseAssemblerElement GetAssemblerElement(int aIndex) { + if (aIndex >= mInstructions.Count) { + return mDataMembers[aIndex - mInstructions.Count]; + } + return mInstructions[aIndex]; + } + + public BaseAssemblerElement TryResolveReference(Cosmos.Assembler.ElementReference aReference) { + foreach (var xInstruction in mInstructions) { + var xLabel = xInstruction as Label; + if (xLabel != null) { + if (xLabel.QualifiedName.Equals(aReference.Name, StringComparison.InvariantCultureIgnoreCase)) { + return xLabel; + } + } + } + foreach (var xDataMember in mDataMembers) { + if (xDataMember.Name.Equals(aReference.Name, StringComparison.InvariantCultureIgnoreCase)) { + return xDataMember; + } + } + return null; + } + + public void Add(Instruction aReader) { + if (aReader is Label || aReader is Comment) + { + } + else if (EmitAsmLabels) + { + // Only issue label if its executable code. + new Label("." + AsmIlIdx.ToString("X2"), "Asm"); + mAsmIlIdx++; + } + mInstructions.Add(aReader); + } + + public void Add(params Instruction[] aReaders) { + mInstructions.Capacity += aReaders.Length; + foreach (Instruction xInstruction in aReaders) { + mInstructions.Add(xInstruction); + } + } + + // Allows to emit footers to the code and datamember sections + protected void OnBeforeFlush() { + DataMembers.AddRange(new DataMember[] { new DataMember("_end_data", new byte[0]) }); + new Label("_end_code"); + } + + private uint mDataMemberCounter = 0; + public string GetIdentifier(string aPrefix) { + mDataMemberCounter++; + return aPrefix + mDataMemberCounter.ToString("X4"); + } + private bool mFlushInitializationDone = false; + protected void BeforeFlush() { + if (mFlushInitializationDone) { + return; + } + mFlushInitializationDone = true; + OnBeforeFlush(); + //MergeAllElements(); + } + + public virtual void FlushBinary(Stream aOutput, ulong aBaseAddress) { + BeforeFlush(); + var xMax = AllAssemblerElementCount; + var xCurrentAddresss = aBaseAddress; + for (int i = 0; i < xMax; i++) { + GetAssemblerElement(i).UpdateAddress(this, ref xCurrentAddresss); + } + aOutput.SetLength(aOutput.Length + (long)(xCurrentAddresss - aBaseAddress)); + for (int i = 0; i < xMax; i++) { + var xItem = GetAssemblerElement(i); + if (!xItem.IsComplete(this)) { + throw new Exception("Incomplete element encountered."); + } + //var xBuff = xItem.GetData(this); + //aOutput.Write(xBuff, 0, xBuff.Length); + xItem.WriteData(this, aOutput); + } + } + + public virtual void FlushText(TextWriter aOutput) { + BeforeFlush(); + + // Write out data declarations + aOutput.WriteLine(); + foreach (DataMember xMember in mDataMembers) { + aOutput.Write("\t"); + if (xMember.IsComment) { + aOutput.Write(xMember.Name); + } else { + xMember.WriteText(this, aOutput); + } + aOutput.WriteLine(); + } + aOutput.WriteLine(); + + // Write out code + for (int i = 0; i < mInstructions.Count; i++) { + var xOp = mInstructions[i]; + string prefix = "\t\t\t"; + if (xOp is Label) { + var xLabel = (Label)xOp; + aOutput.WriteLine(); + prefix = "\t\t"; + aOutput.Write(prefix); + xLabel.WriteText(this, aOutput); + aOutput.WriteLine(); + } else { + aOutput.Write(prefix); + xOp.WriteText(this, aOutput); + aOutput.WriteLine(); + } + } + + aOutput.WriteLine("%ifndef ELF_COMPILATION"); + aOutput.WriteLine("use32"); + aOutput.WriteLine("org 0x200000"); + aOutput.WriteLine("[map all main.map]"); + aOutput.WriteLine("%endif"); + aOutput.WriteLine("global Kernel_Start"); + } + + static public void WriteDebugVideo(string aText) { + // This method emits a lot of ASM, but thats what we want becuase + // at this point we need ASM as simple as possible and completely transparent. + // No stack changes, no register mods, etc. + + // TODO: Add an option on the debug project properties to turn this off. + // Also see TokenPatterns.cs Checkpoint in X# + var xPreBootLogging = true; + if (xPreBootLogging) { + UInt32 xVideo = 0xB8000; + for (UInt32 i = xVideo; i < xVideo + 80 * 2; i = i + 2) { + new LiteralAssemblerCode("mov byte [0x" + i.ToString("X") + "], 0"); + new LiteralAssemblerCode("mov byte [0x" + (i + 1).ToString("X") + "], 0x02"); + } + + foreach (var xChar in aText) { + new LiteralAssemblerCode("mov byte [0x" + xVideo.ToString("X") + "], " + (byte)xChar); + xVideo = xVideo + 2; + } + } + } + + public void CreateGDT() { + new Comment(this, "BEGIN - Create GDT"); + var xGDT = new List(); + // Null Segment - Selector 0x00 + // Not used, but required by many emulators. + xGDT.AddRange(new byte[8] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + // Code Segment + mGdCode = (byte)xGDT.Count; + xGDT.AddRange(GdtDescriptor(0x00000000, 0xFFFFFFFF, true)); + // Data Segment - Selector + mGdData = (byte)xGDT.Count; + xGDT.AddRange(GdtDescriptor(0x00000000, 0xFFFFFFFF, false)); + DataMembers.Add(new DataMember("_NATIVE_GDT_Contents", xGDT.ToArray())); + + new Comment("Tell CPU about GDT"); + var xGdtPtr = new UInt16[3]; + // Size of GDT Table - 1 + xGdtPtr[0] = (UInt16)(xGDT.Count - 1); + DataMembers.Add(new DataMember("_NATIVE_GDT_Pointer", xGdtPtr)); + new Mov { + DestinationRef = Cosmos.Assembler.ElementReference.New("_NATIVE_GDT_Pointer"), + DestinationIsIndirect = true, + DestinationDisplacement = 2, + SourceRef = Cosmos.Assembler.ElementReference.New("_NATIVE_GDT_Contents") + }; + new Mov { DestinationReg = Registers.EAX, SourceRef = Cosmos.Assembler.ElementReference.New("_NATIVE_GDT_Pointer") }; + new Lgdt { DestinationReg = Registers.EAX, DestinationIsIndirect = true }; + + new Comment("Set data segments"); + new Mov { DestinationReg = Registers.EAX, SourceValue = mGdData }; + new Mov { DestinationReg = Registers.DS, SourceReg = Registers.EAX }; + new Mov { DestinationReg = Registers.ES, SourceReg = Registers.EAX }; + new Mov { DestinationReg = Registers.FS, SourceReg = Registers.EAX }; + new Mov { DestinationReg = Registers.GS, SourceReg = Registers.EAX }; + new Mov { DestinationReg = Registers.SS, SourceReg = Registers.EAX }; + + new Comment("Force reload of code segment"); + new JumpToSegment { Segment = mGdCode, DestinationLabel = "Boot_FlushCsGDT" }; + new Label("Boot_FlushCsGDT"); + new Cosmos.Assembler.Comment(this, "END - Create GDT"); + } + + protected void SetIdtDescriptor(int aNo, string aLabel, bool aDisableInts) { + int xOffset = aNo * 8; + new Mov { DestinationReg = Registers.EAX, SourceRef = Cosmos.Assembler.ElementReference.New(aLabel) }; + var xIDT = Cosmos.Assembler.ElementReference.New("_NATIVE_IDT_Contents"); + new Mov { DestinationRef = xIDT, DestinationIsIndirect = true, DestinationDisplacement = xOffset, SourceReg = Registers.AL }; + new Mov { DestinationRef = xIDT, DestinationIsIndirect = true, DestinationDisplacement = xOffset + 1, SourceReg = Registers.AH }; + new ShiftRight { DestinationReg = Registers.EAX, SourceValue = 16 }; + new Mov { DestinationRef = xIDT, DestinationIsIndirect = true, DestinationDisplacement = xOffset + 6, SourceReg = Registers.AL }; + new Mov { DestinationRef = xIDT, DestinationIsIndirect = true, DestinationDisplacement = xOffset + 7, SourceReg = Registers.AH }; + + // Code Segment + new Mov { DestinationRef = xIDT, DestinationIsIndirect = true, DestinationDisplacement = xOffset + 2, SourceValue = mGdCode, Size = 16 }; + + // Reserved + new Mov { DestinationRef = xIDT, DestinationIsIndirect = true, DestinationDisplacement = xOffset + 4, SourceValue = 0x00, Size = 8 }; + + // Type + new Mov { DestinationRef = xIDT, DestinationIsIndirect = true, DestinationDisplacement = xOffset + 5, SourceValue = (byte)(aDisableInts ? 0x8E : 0x8F), Size = 8 }; + } + + public void CreateIDT() { + new Comment(this, "BEGIN - Create IDT"); + + // Create IDT + UInt16 xIdtSize = 8 * 256; + DataMembers.Add(new DataMember("_NATIVE_IDT_Contents", new byte[xIdtSize])); + // + if (mComPort > 0) { + SetIdtDescriptor(1, "DebugStub_TracerEntry", false); + SetIdtDescriptor(3, "DebugStub_TracerEntry", false); + } + //SetIdtDescriptor(1, "DebugStub_INT0"); - Change to GPF + + // Set IDT + DataMembers.Add(new DataMember("_NATIVE_IDT_Pointer", new UInt16[] { xIdtSize, 0, 0 })); + new Mov { + DestinationRef = Cosmos.Assembler.ElementReference.New("_NATIVE_IDT_Pointer"), + DestinationIsIndirect = true, + DestinationDisplacement = 2, + SourceRef = Cosmos.Assembler.ElementReference.New("_NATIVE_IDT_Contents") + }; + new Mov { DestinationReg = Registers.EAX, SourceRef = Cosmos.Assembler.ElementReference.New("_NATIVE_IDT_Pointer") }; + new Lidt { DestinationReg = Registers.EAX, DestinationIsIndirect = true }; + + new Comment(this, "END - Create IDT"); + } + + public void Initialize() { + uint xSig = 0x1BADB002; + + DataMembers.Add(new DataIfNotDefined("ELF_COMPILATION")); + DataMembers.Add(new DataMember("MultibootSignature", new uint[] { xSig })); + uint xFlags = 0x10003; + DataMembers.Add(new DataMember("MultibootFlags", xFlags)); + DataMembers.Add(new DataMember("MultibootChecksum", (int)(0 - (xFlags + xSig)))); + DataMembers.Add(new DataMember("MultibootHeaderAddr", Cosmos.Assembler.ElementReference.New("MultibootSignature"))); + DataMembers.Add(new DataMember("MultibootLoadAddr", Cosmos.Assembler.ElementReference.New("MultibootSignature"))); + DataMembers.Add(new DataMember("MultibootLoadEndAddr", Cosmos.Assembler.ElementReference.New("_end_code"))); + DataMembers.Add(new DataMember("MultibootBSSEndAddr", Cosmos.Assembler.ElementReference.New("_end_code"))); + DataMembers.Add(new DataMember("MultibootEntryAddr", Cosmos.Assembler.ElementReference.New("Kernel_Start"))); + DataMembers.Add(new DataEndIfDefined()); + + DataMembers.Add(new DataIfDefined("ELF_COMPILATION")); + xFlags = 0x00003; + DataMembers.Add(new DataMember("MultibootSignature", new uint[] { xSig })); + DataMembers.Add(new DataMember("MultibootFlags", xFlags)); + DataMembers.Add(new DataMember("MultibootChecksum", (int)(0 - (xFlags + xSig)))); + DataMembers.Add(new DataEndIfDefined()); + + // graphics info fields + DataMembers.Add(new DataMember("MultibootGraphicsRuntime_VbeModeInfoAddr", Int32.MaxValue)); + DataMembers.Add(new DataMember("MultibootGraphicsRuntime_VbeControlInfoAddr", Int32.MaxValue)); + DataMembers.Add(new DataMember("MultibootGraphicsRuntime_VbeMode", Int32.MaxValue)); + // memory + DataMembers.Add(new DataMember("MultiBootInfo_Memory_High", 0)); + DataMembers.Add(new DataMember("MultiBootInfo_Memory_Low", 0)); + DataMembers.Add(new DataMember("Before_Kernel_Stack", new byte[0x50000])); + DataMembers.Add(new DataMember("Kernel_Stack", new byte[0])); + DataMembers.Add(new DataMember("MultiBootInfo_Structure", new uint[1])); + + if (mComPort > 0) { + new Define("DEBUGSTUB"); + } + + // This is our first entry point. Multiboot uses this as Cosmos entry point. + new Label("Kernel_Start", isGlobal: true); + + // Displays "Cosmos" in top left. Used to make sure Cosmos is booted in case of hang. + // ie bootloader debugging. This must be the FIRST code, even before setup so we know + // we are being called properly by the bootloader and that if there are problems its + // somwhere in our code, not the bootloader. + WriteDebugVideo("Cosmos pre boot"); + + // For when using Bochs, causes a break ASAP on entry after initial Cosmos display. + new LiteralAssemblerCode("xchg bx, bx"); + + // CLI ASAP + WriteDebugVideo("Clearing interrupts."); + new ClrInterruptFlag(); + + WriteDebugVideo("Begin multiboot info."); + new Comment(this, "MultiBoot compliant loader provides info in registers: "); + new Comment(this, "EBX=multiboot_info "); + new Comment(this, "EAX=0x2BADB002 - check if it's really Multiboot loader "); + new Comment(this, " ;- copy mb info - some stuff for you "); + new Comment(this, "BEGIN - Multiboot Info"); + new Mov { DestinationRef = Cosmos.Assembler.ElementReference.New("MultiBootInfo_Structure"), DestinationIsIndirect = true, SourceReg = Registers.EBX }; + new Add { DestinationReg = Registers.EBX, SourceValue = 4 }; + new Mov { DestinationReg = Registers.EAX, SourceReg = Registers.EBX, SourceIsIndirect = true }; + new Mov { DestinationRef = Cosmos.Assembler.ElementReference.New("MultiBootInfo_Memory_Low"), DestinationIsIndirect = true, SourceReg = Registers.EAX }; + new Add { DestinationReg = Registers.EBX, SourceValue = 4 }; + new Mov { + DestinationReg = Registers.EAX, + SourceReg = Registers.EBX, + SourceIsIndirect = true + }; + new Mov { DestinationRef = Cosmos.Assembler.ElementReference.New("MultiBootInfo_Memory_High"), DestinationIsIndirect = true, SourceReg = Registers.EAX }; + new Mov { + DestinationReg = Registers.ESP, + SourceRef = Cosmos.Assembler.ElementReference.New("Kernel_Stack") + }; + new Comment(this, "END - Multiboot Info"); + + WriteDebugVideo("Creating GDT."); + CreateGDT(); + + WriteDebugVideo("Creating IDT."); + CreateIDT(); + +#if LFB_1024_8 + new Comment("Set graphics fields"); + new Move { DestinationReg = Registers.EBX, SourceRef = Cosmos.Assembler.ElementReference.New("MultiBootInfo_Structure"), SourceIsIndirect = true }; + new Move { DestinationReg = Registers.EAX, SourceReg = Registers.EBX, SourceIsIndirect = true, SourceDisplacement = 72 }; + new Move { DestinationRef = Cosmos.Assembler.ElementReference.New("MultibootGraphicsRuntime_VbeControlInfoAddr"), DestinationIsIndirect = true, SourceReg = Registers.EAX }; + new Move { DestinationReg = Registers.EAX, SourceReg = Registers.EBX, SourceIsIndirect = true, SourceDisplacement = 76 }; + new Move { DestinationRef = Cosmos.Assembler.ElementReference.New("MultibootGraphicsRuntime_VbeModeInfoAddr"), DestinationIsIndirect = true, SourceReg = Registers.EAX }; + new Move { DestinationReg = Registers.EAX, SourceReg = Registers.EBX, SourceIsIndirect = true, SourceDisplacement = 80 }; + new Move { DestinationRef = Cosmos.Assembler.ElementReference.New("MultibootGraphicsRuntime_VbeMode"), DestinationIsIndirect = true, SourceReg = Registers.EAX }; +#endif + + WriteDebugVideo("Initializing SSE."); + new Comment(this, "BEGIN - SSE Init"); + // CR4[bit 9]=1, CR4[bit 10]=1, CR0[bit 2]=0, CR0[bit 1]=1 + new Mov { DestinationReg = Registers.EAX, SourceReg = Registers.CR4 }; + new Or { DestinationReg = Registers.EAX, SourceValue = 0x100 }; + new Mov { DestinationReg = Registers.CR4, SourceReg = Registers.EAX }; + new Mov { DestinationReg = Registers.EAX, SourceReg = Registers.CR4 }; + new Or { DestinationReg = Registers.EAX, SourceValue = 0x200 }; + new Mov { DestinationReg = Registers.CR4, SourceReg = Registers.EAX }; + new Mov { DestinationReg = Registers.EAX, SourceReg = Registers.CR0 }; + + new And { DestinationReg = Registers.EAX, SourceValue = 0xfffffffd }; + new Mov { DestinationReg = Registers.CR0, SourceReg = Registers.EAX }; + new Mov { DestinationReg = Registers.EAX, SourceReg = Registers.CR0 }; + + new And { DestinationReg = Registers.EAX, SourceValue = 1 }; + new Mov { DestinationReg = Registers.CR0, SourceReg = Registers.EAX }; + new Comment(this, "END - SSE Init"); + + if (mComPort > 0) { + WriteDebugVideo("Initializing DebugStub."); + new Call { DestinationLabel = "DebugStub_Init" }; + } + + // Jump to Kernel entry point + WriteDebugVideo("Jumping to kernel."); + new Call { DestinationLabel = EntryPointName }; + + new Comment(this, "Kernel done - loop till next IRQ"); + new Label(".loop"); + new ClrInterruptFlag(); + new Halt(); + new Jump { DestinationLabel = ".loop" }; + + if (mComPort > 0) { + var xGen = new XSharp.Compiler.AsmGenerator(); + foreach (var xFile in Directory.GetFiles(Cosmos.Build.Common.CosmosPaths.DebugStubSrc, "*.xs")) { + var xAsm = xGen.Generate(xFile); + foreach (var xData in xAsm.Data) { + Cosmos.Assembler.Assembler.CurrentInstance.DataMembers.Add(new DataMember() { RawAsm = xData }); + } + foreach (var xCode in xAsm.Code) { + new LiteralAssemblerCode(xCode); + } + } + } else { + new Label("DebugStub_Step"); + new Return(); + } + // Start emitting assembly labels + Cosmos.Assembler.Assembler.CurrentInstance.EmitAsmLabels = true; + } + } } \ No newline at end of file diff --git a/source/Cosmos.Core/INTs.cs b/source/Cosmos.Core/INTs.cs index a9f876f1c..fdeebc412 100644 --- a/source/Cosmos.Core/INTs.cs +++ b/source/Cosmos.Core/INTs.cs @@ -1,626 +1,630 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Runtime.InteropServices; - -namespace Cosmos.Core -{ - public class INTs - { - #region Enums - // TODO: Protect IRQs like memory and ports are - // TODO: Make IRQs so they are not hookable, and instead release high priority threads like FreeBSD (When we get threading) - public enum EFlagsEnum : uint - { - Carry = 1, - Parity = 1 << 2, - AuxilliaryCarry = 1 << 4, - Zero = 1 << 6, - Sign = 1 << 7, - Trap = 1 << 8, - InterruptEnable = 1 << 9, - Direction = 1 << 10, - Overflow = 1 << 11, - NestedTag = 1 << 14, - Resume = 1 << 16, - Virtual8086Mode = 1 << 17, - AlignmentCheck = 1 << 18, - VirtualInterrupt = 1 << 19, - VirtualInterruptPending = 1 << 20, - ID = 1 << 21 - } - - [StructLayout(LayoutKind.Explicit, Size = 0x68)] - public struct TSS - { - [FieldOffset(0)] - public ushort Link; - [FieldOffset(4)] - public uint ESP0; - [FieldOffset(8)] - public ushort SS0; - [FieldOffset(12)] - public uint ESP1; - [FieldOffset(16)] - public ushort SS1; - [FieldOffset(20)] - public uint ESP2; - [FieldOffset(24)] - public ushort SS2; - [FieldOffset(28)] - public uint CR3; - [FieldOffset(32)] - public uint EIP; - [FieldOffset(36)] - public EFlagsEnum EFlags; - [FieldOffset(40)] - public uint EAX; - [FieldOffset(44)] - public uint ECX; - [FieldOffset(48)] - public uint EDX; - [FieldOffset(52)] - public uint EBX; - [FieldOffset(56)] - public uint ESP; - [FieldOffset(60)] - public uint EBP; - [FieldOffset(64)] - public uint ESI; - [FieldOffset(68)] - public uint EDI; - [FieldOffset(72)] - public ushort ES; - [FieldOffset(76)] - public ushort CS; - [FieldOffset(80)] - public ushort SS; - [FieldOffset(84)] - public ushort DS; - [FieldOffset(88)] - public ushort FS; - [FieldOffset(92)] - public ushort GS; - [FieldOffset(96)] - public ushort LDTR; - [FieldOffset(102)] - public ushort IOPBOffset; - } - - [StructLayout(LayoutKind.Explicit, Size = 512)] - public struct MMXContext - { - } - - [StructLayout(LayoutKind.Explicit, Size = 80)] - public struct IRQContext - { - [FieldOffset(0)] - public unsafe MMXContext* MMXContext; - - [FieldOffset(4)] - public uint EDI; - - [FieldOffset(8)] - public uint ESI; - - [FieldOffset(12)] - public uint EBP; - - [FieldOffset(16)] - public uint ESP; - - [FieldOffset(20)] - public uint EBX; - - [FieldOffset(24)] - public uint EDX; - - [FieldOffset(28)] - public uint ECX; - - [FieldOffset(32)] - public uint EAX; - - [FieldOffset(36)] - public uint Interrupt; - - [FieldOffset(40)] - public uint Param; - - [FieldOffset(44)] - public uint EIP; - - [FieldOffset(48)] - public uint CS; - - [FieldOffset(52)] - public EFlagsEnum EFlags; - - [FieldOffset(56)] - public uint UserESP; - } - #endregion - - private static IRQDelegate[] mIRQ_Handlers = new IRQDelegate[256]; - - // We used to use: - //Interrupts.IRQ01 += HandleKeyboardInterrupt; - // But at one point we had issues with multi cast delegates, so we changed to this single cast option. - // [1:48:37 PM] Matthijs ter Woord: the issues were: "they didn't work, would crash kernel". not sure if we still have them.. - public static void SetIntHandler(byte aIntNo, IRQDelegate aHandler) - { - mIRQ_Handlers[aIntNo] = aHandler; - } - public static void SetIrqHandler(byte aIrqNo, IRQDelegate aHandler) - { - SetIntHandler((byte)(0x20 + aIrqNo), aHandler); - } - - private static void IRQ(uint irq, ref IRQContext aContext) - { - var xCallback = mIRQ_Handlers[irq]; - if (xCallback != null) - { - HMI.GCMonitor(); - xCallback(ref aContext); - HMI.GCFreeAll(); - } - } - - public static void HandleInterrupt_Default(ref IRQContext aContext) - { - if (aContext.Interrupt >= 0x20 && aContext.Interrupt <= 0x2F) - { - if (aContext.Interrupt >= 0x28) - { - Global.PIC.EoiSlave(); - } - else - { - Global.PIC.EoiMaster(); - } - } - } - - public delegate void IRQDelegate(ref IRQContext aContext); - public delegate void ExceptionInterruptDelegate(ref IRQContext aContext, ref bool aHandled); - - #region Default Interrupt Handlers - - //IRQ 0 - System timer. Reserved for the system. Cannot be changed by a user. - public static void HandleInterrupt_20(ref IRQContext aContext) - { - IRQ(0x20, ref aContext); - Global.PIC.EoiMaster(); - } - - //public static IRQDelegate IRQ01; - //IRQ 1 - Keyboard. Reserved for the system. Cannot be altered even if no keyboard is present or needed. - public static void HandleInterrupt_21(ref IRQContext aContext) - { - - IRQ(0x21, ref aContext); - Global.PIC.EoiMaster(); - } - public static void HandleInterrupt_22(ref IRQContext aContext) - { - - IRQ(0x22, ref aContext); - Global.PIC.EoiMaster(); - } - public static void HandleInterrupt_23(ref IRQContext aContext) - { - - IRQ(0x23, ref aContext); - Global.PIC.EoiMaster(); - } - public static void HandleInterrupt_24(ref IRQContext aContext) - { - - IRQ(0x24, ref aContext); - Global.PIC.EoiMaster(); - } - public static void HandleInterrupt_25(ref IRQContext aContext) - { - IRQ(0x25, ref aContext); - Global.PIC.EoiMaster(); - } - public static void HandleInterrupt_26(ref IRQContext aContext) - { - - IRQ(0x26, ref aContext); - Global.PIC.EoiMaster(); - } - public static void HandleInterrupt_27(ref IRQContext aContext) - { - - IRQ(0x27, ref aContext); - Global.PIC.EoiMaster(); - } - - public static void HandleInterrupt_28(ref IRQContext aContext) - { - - IRQ(0x28, ref aContext); - Global.PIC.EoiSlave(); - } - //IRQ 09 - (Added for AMD PCNet network card) - //public static IRQDelegate IRQ09; - - public static void HandleInterrupt_29(ref IRQContext aContext) - { - IRQ(0x29, ref aContext); - Global.PIC.EoiSlave(); - } - - //IRQ 10 - (Added for VIA Rhine network card) - //public static IRQDelegate IRQ10; - - public static void HandleInterrupt_2A(ref IRQContext aContext) - { - IRQ(0x2A, ref aContext); - Global.PIC.EoiSlave(); - } - - //IRQ 11 - (Added for RTL8139 network card) - //public static IRQDelegate IRQ11; - - public static void HandleInterrupt_2B(ref IRQContext aContext) - { - IRQ(0x2B, ref aContext); - Global.PIC.EoiSlave(); - } - - public static void HandleInterrupt_2C(ref IRQContext aContext) - { - - IRQ(0x2C, ref aContext); - Global.PIC.EoiSlave(); - } - - - public static void HandleInterrupt_2D(ref IRQContext aContext) - { - IRQ(0x2D, ref aContext); - Global.PIC.EoiSlave(); - } - //IRQ 14 - Primary IDE. If no Primary IDE this can be changed - public static void HandleInterrupt_2E(ref IRQContext aContext) - { - IRQ(0x2E, ref aContext); - Global.PIC.EoiSlave(); - } - //IRQ 15 - Secondary IDE - public static void HandleInterrupt_2F(ref IRQContext aContext) - { - IRQ(0x2F, ref aContext); - Global.PIC.EoiSlave(); - } - - public static event IRQDelegate Interrupt30; - // Interrupt 0x30, enter VMM - public static void HandleInterrupt_30(ref IRQContext aContext) - { - if (Interrupt30 != null) - { - Interrupt30(ref aContext); - } - } - - public static void HandleInterrupt_35(ref IRQContext aContext) - { - Global.Dbg.SendMessage("Interrupts", "Interrupt 35 handler"); - aContext.EAX *= 2; - aContext.EBX *= 2; - aContext.ECX *= 2; - aContext.EDX *= 2; - } - - public static void HandleInterrupt_40(ref IRQContext aContext) - { - IRQ(0x40, ref aContext); - } - public static void HandleInterrupt_41(ref IRQContext aContext) - { - IRQ(0x41, ref aContext); - } - public static void HandleInterrupt_42(ref IRQContext aContext) - { - IRQ(0x42, ref aContext); - } - public static void HandleInterrupt_43(ref IRQContext aContext) - { - IRQ(0x43, ref aContext); - } - public static void HandleInterrupt_44(ref IRQContext aContext) - { - IRQ(0x44, ref aContext); - } - public static void HandleInterrupt_45(ref IRQContext aContext) - { - IRQ(0x45, ref aContext); - } - public static void HandleInterrupt_46(ref IRQContext aContext) - { - IRQ(0x46, ref aContext); - } - public static void HandleInterrupt_47(ref IRQContext aContext) - { - IRQ(0x47, ref aContext); - } - public static void HandleInterrupt_48(ref IRQContext aContext) - { - IRQ(0x48, ref aContext); - } - public static void HandleInterrupt_49(ref IRQContext aContext) - { - IRQ(0x49, ref aContext); - } - - #endregion - - #region CPU Exceptions - - public static IRQDelegate GeneralProtectionFault; - - public static void HandleInterrupt_00(ref IRQContext aContext) - { - HandleException(aContext.EIP, "Divide by zero", "EDivideByZero", ref aContext); - } - - public static void HandleInterrupt_01(ref IRQContext aContext) - { - HandleException(aContext.EIP, "Debug Exception", "Debug Exception", ref aContext); - } - - public static void HandleInterrupt_02(ref IRQContext aContext) - { - HandleException(aContext.EIP, "Non Maskable Interrupt Exception", "Non Maskable Interrupt Exception", ref aContext); - } - - public static void HandleInterrupt_03(ref IRQContext aContext) - { - HandleException(aContext.EIP, "Breakpoint Exception", "Breakpoint Exception", ref aContext); - } - - public static void HandleInterrupt_04(ref IRQContext aContext) - { - HandleException(aContext.EIP, "Into Detected Overflow Exception", "Into Detected Overflow Exception", ref aContext); - } - - public static void HandleInterrupt_05(ref IRQContext aContext) - { - HandleException(aContext.EIP, "Out of Bounds Exception", "Out of Bounds Exception", ref aContext); - } - - public static void HandleInterrupt_06(ref IRQContext aContext) - { - HandleException(aContext.EIP, "Invalid Opcode", "EInvalidOpcode", ref aContext, true); - } - - public static void HandleInterrupt_07(ref IRQContext aContext) - { - HandleException(aContext.EIP, "No Coprocessor Exception", "No Coprocessor Exception", ref aContext); - } - - public static void HandleInterrupt_08(ref IRQContext aContext) - { - HandleException(aContext.EIP, "Double Fault Exception", "Double Fault Exception", ref aContext); - } - - public static void HandleInterrupt_09(ref IRQContext aContext) - { - HandleException(aContext.EIP, "Coprocessor Segment Overrun Exception", "Coprocessor Segment Overrun Exception", ref aContext); - } - - public static void HandleInterrupt_0A(ref IRQContext aContext) - { - HandleException(aContext.EIP, "Bad TSS Exception", "Bad TSS Exception", ref aContext); - } - - public static void HandleInterrupt_0B(ref IRQContext aContext) - { - HandleException(aContext.EIP, "Segment Not Present", "Segment Not Present", ref aContext); - } - - public static void HandleInterrupt_0C(ref IRQContext aContext) - { - HandleException(aContext.EIP, "Stack Fault Exception", "Stack Fault Exception", ref aContext); - } - public static void HandleInterrupt_0D(ref IRQContext aContext) - { - if (GeneralProtectionFault != null) - { - GeneralProtectionFault(ref aContext); - } - else - { - HandleException(aContext.EIP, "General Protection Fault", "GPF", ref aContext); - } - } - - public static void HandleInterrupt_0E(ref IRQContext aContext) - { - HandleException(aContext.EIP, "Page Fault Exception", "Page Fault Exception", ref aContext); - } - - public static void HandleInterrupt_0F(ref IRQContext aContext) - { - HandleException(aContext.EIP, "Unknown Interrupt Exception", "Unknown Interrupt Exception", ref aContext); - } - - public static void HandleInterrupt_10(ref IRQContext aContext) - { - HandleException(aContext.EIP, "x87 Floating Point Exception", "Coprocessor Fault Exception", ref aContext); - } - - public static void HandleInterrupt_11(ref IRQContext aContext) - { - HandleException(aContext.EIP, "Alignment Exception", "Alignment Exception", ref aContext); - } - - public static void HandleInterrupt_12(ref IRQContext aContext) - { - HandleException(aContext.EIP, "Machine Check Exception", "Machine Check Exception", ref aContext); - } - public static void HandleInterrupt_13(ref IRQContext aContext) - { - HandleException(aContext.EIP, "SIMD Floating Point Exception", "SIMD Floating Point Exception", ref aContext); - } - - - #endregion - - private static void HandleException(uint aEIP, string aDescription, string aName, ref IRQContext ctx, bool printOriginalEIP = false) - { - // At this point we are in a very unstable state. - // Try not to use any Cosmos routines, just - // report a crash dump. - const string xHex = "0123456789ABCDEF"; - uint xPtr = ctx.EIP; - - // we're printing exception info to the screen now: - // 0/0: x - // 1/0: exception number in hex - unsafe - { - byte* xAddress = (byte*)0xB8000; - PutErrorChar(0, 00, ' '); - PutErrorChar(0, 01, '*'); - PutErrorChar(0, 02, '*'); - PutErrorChar(0, 03, '*'); - PutErrorChar(0, 04, ' '); - PutErrorChar(0, 05, 'C'); - PutErrorChar(0, 06, 'P'); - PutErrorChar(0, 07, 'U'); - PutErrorChar(0, 08, ' '); - PutErrorChar(0, 09, 'E'); - PutErrorChar(0, 10, 'x'); - PutErrorChar(0, 11, 'c'); - PutErrorChar(0, 12, 'e'); - PutErrorChar(0, 13,'p'); - PutErrorChar(0, 14,'t'); - PutErrorChar(0, 15,'i'); - PutErrorChar(0, 16,'o'); - PutErrorChar(0, 17,'n'); - PutErrorChar(0, 18,' '); - PutErrorChar(0, 19,'x'); - PutErrorChar(0, 20,xHex[(int)((ctx.Interrupt >> 4) & 0xF)]); - PutErrorChar(0, 21,xHex[(int)(ctx.Interrupt & 0xF)]); - PutErrorChar(0, 22,' '); - PutErrorChar(0, 23,'*'); - PutErrorChar(0, 24,'*'); - PutErrorChar(0, 25,'*'); - PutErrorChar(0, 26,' '); - - if (printOriginalEIP) - { - PutErrorString(1, 0, "Original EIP: 0x"); - // start eip at 16 - PutErrorChar(1, 16, xHex[(int)((aEIP >> 28) & 0xF)]); - PutErrorChar(1, 17, xHex[(int)((aEIP >> 24) & 0xF)]); - PutErrorChar(1, 18, xHex[(int)((aEIP >> 20) & 0xF)]); - PutErrorChar(1, 19, xHex[(int)((aEIP >> 16) & 0xF)]); - PutErrorChar(1, 20, xHex[(int)((aEIP >> 12) & 0xF)]); - PutErrorChar(1, 21, xHex[(int)((aEIP >> 8) & 0xF)]); - PutErrorChar(1, 22, xHex[(int)((aEIP >> 4) & 0xF)]); - PutErrorChar(1, 23, xHex[(int)(aEIP & 0xF)]); - } - - } - - // lock up - while (true) - { - } - } - - private static void PutErrorChar(int line, int col, char c) - { - unsafe - { - byte* xAddress = (byte*)0xB8000; - - xAddress += ((line * 80) + col) * 2; - - xAddress[0] = (byte)c; - xAddress[1] = 0x0C; - } - } - - private static void PutErrorString(int line, int startCol, string error) - { - for (int i = 0; i < error.Length; i++) - { - PutErrorChar(line, startCol + i, error[i]); - } - } - - // This is to trick IL2CPU to compile it in - //TODO: Make a new attribute that IL2CPU sees when scanning to force inclusion so we dont have to do this. - // We dont actually need to cal this method - public static void Dummy() - { - // Compiler magic - bool xTest = false; - if (xTest) - { - unsafe - { - var xCtx = new IRQContext(); - HandleInterrupt_Default(ref xCtx); - HandleInterrupt_00(ref xCtx); - HandleInterrupt_01(ref xCtx); - HandleInterrupt_02(ref xCtx); - HandleInterrupt_03(ref xCtx); - HandleInterrupt_04(ref xCtx); - HandleInterrupt_05(ref xCtx); - HandleInterrupt_06(ref xCtx); - HandleInterrupt_07(ref xCtx); - HandleInterrupt_08(ref xCtx); - HandleInterrupt_09(ref xCtx); - HandleInterrupt_0A(ref xCtx); - HandleInterrupt_0B(ref xCtx); - HandleInterrupt_0C(ref xCtx); - HandleInterrupt_0D(ref xCtx); - HandleInterrupt_0E(ref xCtx); - HandleInterrupt_0F(ref xCtx); - HandleInterrupt_10(ref xCtx); - HandleInterrupt_11(ref xCtx); - HandleInterrupt_12(ref xCtx); - HandleInterrupt_13(ref xCtx); - HandleInterrupt_20(ref xCtx); - HandleInterrupt_21(ref xCtx); - HandleInterrupt_22(ref xCtx); - HandleInterrupt_23(ref xCtx); - HandleInterrupt_24(ref xCtx); - HandleInterrupt_25(ref xCtx); - HandleInterrupt_26(ref xCtx); - HandleInterrupt_27(ref xCtx); - HandleInterrupt_28(ref xCtx); - HandleInterrupt_29(ref xCtx); - HandleInterrupt_2A(ref xCtx); - HandleInterrupt_2B(ref xCtx); - HandleInterrupt_2C(ref xCtx); - HandleInterrupt_2D(ref xCtx); - HandleInterrupt_2E(ref xCtx); - HandleInterrupt_2F(ref xCtx); - HandleInterrupt_30(ref xCtx); - HandleInterrupt_35(ref xCtx); - HandleInterrupt_40(ref xCtx); - HandleInterrupt_41(ref xCtx); - HandleInterrupt_42(ref xCtx); - HandleInterrupt_43(ref xCtx); - HandleInterrupt_44(ref xCtx); - HandleInterrupt_45(ref xCtx); - HandleInterrupt_46(ref xCtx); - HandleInterrupt_47(ref xCtx); - HandleInterrupt_48(ref xCtx); - HandleInterrupt_49(ref xCtx); - } - } - } - - } +using System; +using System.Collections.Generic; +using System.Text; +using System.Runtime.InteropServices; + +namespace Cosmos.Core +{ + public class INTs + { + #region Enums + // TODO: Protect IRQs like memory and ports are + // TODO: Make IRQs so they are not hookable, and instead release high priority threads like FreeBSD (When we get threading) + public enum EFlagsEnum : uint + { + Carry = 1, + Parity = 1 << 2, + AuxilliaryCarry = 1 << 4, + Zero = 1 << 6, + Sign = 1 << 7, + Trap = 1 << 8, + InterruptEnable = 1 << 9, + Direction = 1 << 10, + Overflow = 1 << 11, + NestedTag = 1 << 14, + Resume = 1 << 16, + Virtual8086Mode = 1 << 17, + AlignmentCheck = 1 << 18, + VirtualInterrupt = 1 << 19, + VirtualInterruptPending = 1 << 20, + ID = 1 << 21 + } + + [StructLayout(LayoutKind.Explicit, Size = 0x68)] + public struct TSS + { + [FieldOffset(0)] + public ushort Link; + [FieldOffset(4)] + public uint ESP0; + [FieldOffset(8)] + public ushort SS0; + [FieldOffset(12)] + public uint ESP1; + [FieldOffset(16)] + public ushort SS1; + [FieldOffset(20)] + public uint ESP2; + [FieldOffset(24)] + public ushort SS2; + [FieldOffset(28)] + public uint CR3; + [FieldOffset(32)] + public uint EIP; + [FieldOffset(36)] + public EFlagsEnum EFlags; + [FieldOffset(40)] + public uint EAX; + [FieldOffset(44)] + public uint ECX; + [FieldOffset(48)] + public uint EDX; + [FieldOffset(52)] + public uint EBX; + [FieldOffset(56)] + public uint ESP; + [FieldOffset(60)] + public uint EBP; + [FieldOffset(64)] + public uint ESI; + [FieldOffset(68)] + public uint EDI; + [FieldOffset(72)] + public ushort ES; + [FieldOffset(76)] + public ushort CS; + [FieldOffset(80)] + public ushort SS; + [FieldOffset(84)] + public ushort DS; + [FieldOffset(88)] + public ushort FS; + [FieldOffset(92)] + public ushort GS; + [FieldOffset(96)] + public ushort LDTR; + [FieldOffset(102)] + public ushort IOPBOffset; + } + + [StructLayout(LayoutKind.Explicit, Size = 512)] + public struct MMXContext + { + } + + [StructLayout(LayoutKind.Explicit, Size = 80)] + public struct IRQContext + { + [FieldOffset(0)] + public unsafe MMXContext* MMXContext; + + [FieldOffset(4)] + public uint EDI; + + [FieldOffset(8)] + public uint ESI; + + [FieldOffset(12)] + public uint EBP; + + [FieldOffset(16)] + public uint ESP; + + [FieldOffset(20)] + public uint EBX; + + [FieldOffset(24)] + public uint EDX; + + [FieldOffset(28)] + public uint ECX; + + [FieldOffset(32)] + public uint EAX; + + [FieldOffset(36)] + public uint Interrupt; + + [FieldOffset(40)] + public uint Param; + + [FieldOffset(44)] + public uint EIP; + + [FieldOffset(48)] + public uint CS; + + [FieldOffset(52)] + public EFlagsEnum EFlags; + + [FieldOffset(56)] + public uint UserESP; + } + #endregion + + private static uint mLastKnownAddress; + + private static IRQDelegate[] mIRQ_Handlers = new IRQDelegate[256]; + + // We used to use: + //Interrupts.IRQ01 += HandleKeyboardInterrupt; + // But at one point we had issues with multi cast delegates, so we changed to this single cast option. + // [1:48:37 PM] Matthijs ter Woord: the issues were: "they didn't work, would crash kernel". not sure if we still have them.. + public static void SetIntHandler(byte aIntNo, IRQDelegate aHandler) + { + mIRQ_Handlers[aIntNo] = aHandler; + } + public static void SetIrqHandler(byte aIrqNo, IRQDelegate aHandler) + { + SetIntHandler((byte)(0x20 + aIrqNo), aHandler); + } + + private static void IRQ(uint irq, ref IRQContext aContext) + { + var xCallback = mIRQ_Handlers[irq]; + if (xCallback != null) + { + HMI.GCMonitor(); + xCallback(ref aContext); + HMI.GCFreeAll(); + } + } + + public static void HandleInterrupt_Default(ref IRQContext aContext) + { + if (aContext.Interrupt >= 0x20 && aContext.Interrupt <= 0x2F) + { + if (aContext.Interrupt >= 0x28) + { + Global.PIC.EoiSlave(); + } + else + { + Global.PIC.EoiMaster(); + } + } + } + + public delegate void IRQDelegate(ref IRQContext aContext); + public delegate void ExceptionInterruptDelegate(ref IRQContext aContext, ref bool aHandled); + + #region Default Interrupt Handlers + + //IRQ 0 - System timer. Reserved for the system. Cannot be changed by a user. + public static void HandleInterrupt_20(ref IRQContext aContext) + { + IRQ(0x20, ref aContext); + Global.PIC.EoiMaster(); + } + + //public static IRQDelegate IRQ01; + //IRQ 1 - Keyboard. Reserved for the system. Cannot be altered even if no keyboard is present or needed. + public static void HandleInterrupt_21(ref IRQContext aContext) + { + + IRQ(0x21, ref aContext); + Global.PIC.EoiMaster(); + } + public static void HandleInterrupt_22(ref IRQContext aContext) + { + + IRQ(0x22, ref aContext); + Global.PIC.EoiMaster(); + } + public static void HandleInterrupt_23(ref IRQContext aContext) + { + + IRQ(0x23, ref aContext); + Global.PIC.EoiMaster(); + } + public static void HandleInterrupt_24(ref IRQContext aContext) + { + + IRQ(0x24, ref aContext); + Global.PIC.EoiMaster(); + } + public static void HandleInterrupt_25(ref IRQContext aContext) + { + IRQ(0x25, ref aContext); + Global.PIC.EoiMaster(); + } + public static void HandleInterrupt_26(ref IRQContext aContext) + { + + IRQ(0x26, ref aContext); + Global.PIC.EoiMaster(); + } + public static void HandleInterrupt_27(ref IRQContext aContext) + { + + IRQ(0x27, ref aContext); + Global.PIC.EoiMaster(); + } + + public static void HandleInterrupt_28(ref IRQContext aContext) + { + + IRQ(0x28, ref aContext); + Global.PIC.EoiSlave(); + } + //IRQ 09 - (Added for AMD PCNet network card) + //public static IRQDelegate IRQ09; + + public static void HandleInterrupt_29(ref IRQContext aContext) + { + IRQ(0x29, ref aContext); + Global.PIC.EoiSlave(); + } + + //IRQ 10 - (Added for VIA Rhine network card) + //public static IRQDelegate IRQ10; + + public static void HandleInterrupt_2A(ref IRQContext aContext) + { + IRQ(0x2A, ref aContext); + Global.PIC.EoiSlave(); + } + + //IRQ 11 - (Added for RTL8139 network card) + //public static IRQDelegate IRQ11; + + public static void HandleInterrupt_2B(ref IRQContext aContext) + { + IRQ(0x2B, ref aContext); + Global.PIC.EoiSlave(); + } + + public static void HandleInterrupt_2C(ref IRQContext aContext) + { + + IRQ(0x2C, ref aContext); + Global.PIC.EoiSlave(); + } + + + public static void HandleInterrupt_2D(ref IRQContext aContext) + { + IRQ(0x2D, ref aContext); + Global.PIC.EoiSlave(); + } + //IRQ 14 - Primary IDE. If no Primary IDE this can be changed + public static void HandleInterrupt_2E(ref IRQContext aContext) + { + IRQ(0x2E, ref aContext); + Global.PIC.EoiSlave(); + } + //IRQ 15 - Secondary IDE + public static void HandleInterrupt_2F(ref IRQContext aContext) + { + IRQ(0x2F, ref aContext); + Global.PIC.EoiSlave(); + } + + public static event IRQDelegate Interrupt30; + // Interrupt 0x30, enter VMM + public static void HandleInterrupt_30(ref IRQContext aContext) + { + if (Interrupt30 != null) + { + Interrupt30(ref aContext); + } + } + + public static void HandleInterrupt_35(ref IRQContext aContext) + { + Global.Dbg.SendMessage("Interrupts", "Interrupt 35 handler"); + aContext.EAX *= 2; + aContext.EBX *= 2; + aContext.ECX *= 2; + aContext.EDX *= 2; + } + + public static void HandleInterrupt_40(ref IRQContext aContext) + { + IRQ(0x40, ref aContext); + } + public static void HandleInterrupt_41(ref IRQContext aContext) + { + IRQ(0x41, ref aContext); + } + public static void HandleInterrupt_42(ref IRQContext aContext) + { + IRQ(0x42, ref aContext); + } + public static void HandleInterrupt_43(ref IRQContext aContext) + { + IRQ(0x43, ref aContext); + } + public static void HandleInterrupt_44(ref IRQContext aContext) + { + IRQ(0x44, ref aContext); + } + public static void HandleInterrupt_45(ref IRQContext aContext) + { + IRQ(0x45, ref aContext); + } + public static void HandleInterrupt_46(ref IRQContext aContext) + { + IRQ(0x46, ref aContext); + } + public static void HandleInterrupt_47(ref IRQContext aContext) + { + IRQ(0x47, ref aContext); + } + public static void HandleInterrupt_48(ref IRQContext aContext) + { + IRQ(0x48, ref aContext); + } + public static void HandleInterrupt_49(ref IRQContext aContext) + { + IRQ(0x49, ref aContext); + } + + #endregion + + #region CPU Exceptions + + public static IRQDelegate GeneralProtectionFault; + + public static void HandleInterrupt_00(ref IRQContext aContext) + { + HandleException(aContext.EIP, "Divide by zero", "EDivideByZero", ref aContext); + } + + public static void HandleInterrupt_01(ref IRQContext aContext) + { + HandleException(aContext.EIP, "Debug Exception", "Debug Exception", ref aContext); + } + + public static void HandleInterrupt_02(ref IRQContext aContext) + { + HandleException(aContext.EIP, "Non Maskable Interrupt Exception", "Non Maskable Interrupt Exception", ref aContext); + } + + public static void HandleInterrupt_03(ref IRQContext aContext) + { + HandleException(aContext.EIP, "Breakpoint Exception", "Breakpoint Exception", ref aContext); + } + + public static void HandleInterrupt_04(ref IRQContext aContext) + { + HandleException(aContext.EIP, "Into Detected Overflow Exception", "Into Detected Overflow Exception", ref aContext); + } + + public static void HandleInterrupt_05(ref IRQContext aContext) + { + HandleException(aContext.EIP, "Out of Bounds Exception", "Out of Bounds Exception", ref aContext); + } + + public static void HandleInterrupt_06(ref IRQContext aContext) + { + // although mLastKnownAddress is a static, we need to get it here, any subsequent calls will change the value!!! + var xLastKnownAddress = mLastKnownAddress; + HandleException(aContext.EIP, "Invalid Opcode", "EInvalidOpcode", ref aContext, xLastKnownAddress); + } + + public static void HandleInterrupt_07(ref IRQContext aContext) + { + HandleException(aContext.EIP, "No Coprocessor Exception", "No Coprocessor Exception", ref aContext); + } + + public static void HandleInterrupt_08(ref IRQContext aContext) + { + HandleException(aContext.EIP, "Double Fault Exception", "Double Fault Exception", ref aContext); + } + + public static void HandleInterrupt_09(ref IRQContext aContext) + { + HandleException(aContext.EIP, "Coprocessor Segment Overrun Exception", "Coprocessor Segment Overrun Exception", ref aContext); + } + + public static void HandleInterrupt_0A(ref IRQContext aContext) + { + HandleException(aContext.EIP, "Bad TSS Exception", "Bad TSS Exception", ref aContext); + } + + public static void HandleInterrupt_0B(ref IRQContext aContext) + { + HandleException(aContext.EIP, "Segment Not Present", "Segment Not Present", ref aContext); + } + + public static void HandleInterrupt_0C(ref IRQContext aContext) + { + HandleException(aContext.EIP, "Stack Fault Exception", "Stack Fault Exception", ref aContext); + } + public static void HandleInterrupt_0D(ref IRQContext aContext) + { + if (GeneralProtectionFault != null) + { + GeneralProtectionFault(ref aContext); + } + else + { + HandleException(aContext.EIP, "General Protection Fault", "GPF", ref aContext); + } + } + + public static void HandleInterrupt_0E(ref IRQContext aContext) + { + HandleException(aContext.EIP, "Page Fault Exception", "Page Fault Exception", ref aContext); + } + + public static void HandleInterrupt_0F(ref IRQContext aContext) + { + HandleException(aContext.EIP, "Unknown Interrupt Exception", "Unknown Interrupt Exception", ref aContext); + } + + public static void HandleInterrupt_10(ref IRQContext aContext) + { + HandleException(aContext.EIP, "x87 Floating Point Exception", "Coprocessor Fault Exception", ref aContext); + } + + public static void HandleInterrupt_11(ref IRQContext aContext) + { + HandleException(aContext.EIP, "Alignment Exception", "Alignment Exception", ref aContext); + } + + public static void HandleInterrupt_12(ref IRQContext aContext) + { + HandleException(aContext.EIP, "Machine Check Exception", "Machine Check Exception", ref aContext); + } + public static void HandleInterrupt_13(ref IRQContext aContext) + { + HandleException(aContext.EIP, "SIMD Floating Point Exception", "SIMD Floating Point Exception", ref aContext); + } + + + #endregion + + private static void HandleException(uint aEIP, string aDescription, string aName, ref IRQContext ctx, uint lastKnownAddressValue = 0) + { + // At this point we are in a very unstable state. + // Try not to use any Cosmos routines, just + // report a crash dump. + const string xHex = "0123456789ABCDEF"; + uint xPtr = ctx.EIP; + + // we're printing exception info to the screen now: + // 0/0: x + // 1/0: exception number in hex + unsafe + { + byte* xAddress = (byte*)0xB8000; + PutErrorChar(0, 00, ' '); + PutErrorChar(0, 01, '*'); + PutErrorChar(0, 02, '*'); + PutErrorChar(0, 03, '*'); + PutErrorChar(0, 04, ' '); + PutErrorChar(0, 05, 'C'); + PutErrorChar(0, 06, 'P'); + PutErrorChar(0, 07, 'U'); + PutErrorChar(0, 08, ' '); + PutErrorChar(0, 09, 'E'); + PutErrorChar(0, 10, 'x'); + PutErrorChar(0, 11, 'c'); + PutErrorChar(0, 12, 'e'); + PutErrorChar(0, 13,'p'); + PutErrorChar(0, 14,'t'); + PutErrorChar(0, 15,'i'); + PutErrorChar(0, 16,'o'); + PutErrorChar(0, 17,'n'); + PutErrorChar(0, 18,' '); + PutErrorChar(0, 19,'x'); + PutErrorChar(0, 20,xHex[(int)((ctx.Interrupt >> 4) & 0xF)]); + PutErrorChar(0, 21,xHex[(int)(ctx.Interrupt & 0xF)]); + PutErrorChar(0, 22,' '); + PutErrorChar(0, 23,'*'); + PutErrorChar(0, 24,'*'); + PutErrorChar(0, 25,'*'); + PutErrorChar(0, 26,' '); + + if (lastKnownAddressValue != 0) + { + PutErrorString(1, 0, "Last known address: 0x"); + // start eip at 16 + PutErrorChar(1, 16, xHex[(int)((lastKnownAddressValue >> 28) & 0xF)]); + PutErrorChar(1, 17, xHex[(int)((lastKnownAddressValue >> 24) & 0xF)]); + PutErrorChar(1, 18, xHex[(int)((lastKnownAddressValue >> 20) & 0xF)]); + PutErrorChar(1, 19, xHex[(int)((lastKnownAddressValue >> 16) & 0xF)]); + PutErrorChar(1, 20, xHex[(int)((lastKnownAddressValue >> 12) & 0xF)]); + PutErrorChar(1, 21, xHex[(int)((lastKnownAddressValue >> 8) & 0xF)]); + PutErrorChar(1, 22, xHex[(int)((lastKnownAddressValue >> 4) & 0xF)]); + PutErrorChar(1, 23, xHex[(int)(lastKnownAddressValue & 0xF)]); + } + + } + + // lock up + while (true) + { + } + } + + private static void PutErrorChar(int line, int col, char c) + { + unsafe + { + byte* xAddress = (byte*)0xB8000; + + xAddress += ((line * 80) + col) * 2; + + xAddress[0] = (byte)c; + xAddress[1] = 0x0C; + } + } + + private static void PutErrorString(int line, int startCol, string error) + { + for (int i = 0; i < error.Length; i++) + { + PutErrorChar(line, startCol + i, error[i]); + } + } + + // This is to trick IL2CPU to compile it in + //TODO: Make a new attribute that IL2CPU sees when scanning to force inclusion so we dont have to do this. + // We dont actually need to cal this method + public static void Dummy() + { + // Compiler magic + bool xTest = false; + if (xTest) + { + unsafe + { + var xCtx = new IRQContext(); + HandleInterrupt_Default(ref xCtx); + HandleInterrupt_00(ref xCtx); + HandleInterrupt_01(ref xCtx); + HandleInterrupt_02(ref xCtx); + HandleInterrupt_03(ref xCtx); + HandleInterrupt_04(ref xCtx); + HandleInterrupt_05(ref xCtx); + HandleInterrupt_06(ref xCtx); + HandleInterrupt_07(ref xCtx); + HandleInterrupt_08(ref xCtx); + HandleInterrupt_09(ref xCtx); + HandleInterrupt_0A(ref xCtx); + HandleInterrupt_0B(ref xCtx); + HandleInterrupt_0C(ref xCtx); + HandleInterrupt_0D(ref xCtx); + HandleInterrupt_0E(ref xCtx); + HandleInterrupt_0F(ref xCtx); + HandleInterrupt_10(ref xCtx); + HandleInterrupt_11(ref xCtx); + HandleInterrupt_12(ref xCtx); + HandleInterrupt_13(ref xCtx); + HandleInterrupt_20(ref xCtx); + HandleInterrupt_21(ref xCtx); + HandleInterrupt_22(ref xCtx); + HandleInterrupt_23(ref xCtx); + HandleInterrupt_24(ref xCtx); + HandleInterrupt_25(ref xCtx); + HandleInterrupt_26(ref xCtx); + HandleInterrupt_27(ref xCtx); + HandleInterrupt_28(ref xCtx); + HandleInterrupt_29(ref xCtx); + HandleInterrupt_2A(ref xCtx); + HandleInterrupt_2B(ref xCtx); + HandleInterrupt_2C(ref xCtx); + HandleInterrupt_2D(ref xCtx); + HandleInterrupt_2E(ref xCtx); + HandleInterrupt_2F(ref xCtx); + HandleInterrupt_30(ref xCtx); + HandleInterrupt_35(ref xCtx); + HandleInterrupt_40(ref xCtx); + HandleInterrupt_41(ref xCtx); + HandleInterrupt_42(ref xCtx); + HandleInterrupt_43(ref xCtx); + HandleInterrupt_44(ref xCtx); + HandleInterrupt_45(ref xCtx); + HandleInterrupt_46(ref xCtx); + HandleInterrupt_47(ref xCtx); + HandleInterrupt_48(ref xCtx); + HandleInterrupt_49(ref xCtx); + } + } + } + + } } \ No newline at end of file diff --git a/source/Cosmos.IL2CPU/.editorconfig b/source/Cosmos.IL2CPU/.editorconfig index 25d0ab140..e3aefa8be 100644 --- a/source/Cosmos.IL2CPU/.editorconfig +++ b/source/Cosmos.IL2CPU/.editorconfig @@ -1,12 +1,15 @@ -; 4-column space indentation -[ILOpCode.cs] -indent_size = 2 - -[ILOpCodes/*.cs] -indent_size = 2 - -[ILOp.cs] -indent_size = 2 - -[IL/*.cs] -indent_size = 2 \ No newline at end of file +; 4-column space indentation +[ILOpCode.cs] +indent_size = 2 + +[ILOpCodes/*.cs] +indent_size = 2 + +[ILOp.cs] +indent_size = 2 + +[IL/*.cs] +indent_size = 2 + +[AppAssembler.cs] +indent_style = 4 \ No newline at end of file diff --git a/source/Cosmos.IL2CPU/AppAssembler.cs b/source/Cosmos.IL2CPU/AppAssembler.cs index 33dcc6558..68bf86469 100644 --- a/source/Cosmos.IL2CPU/AppAssembler.cs +++ b/source/Cosmos.IL2CPU/AppAssembler.cs @@ -1,1580 +1,1587 @@ -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; - -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 System.IO.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 Guid mCurrentMethodLabelEndGuid; - protected Guid mCurrentMethodGuid; - - public AppAssembler(int aComPort) - { - Assembler = new Cosmos.Assembler.Assembler(aComPort); - mLog = new System.IO.StreamWriter("Cosmos.Assembler.Log", false); - InitILOps(); - } - - public void Dispose() - { - if (mLog != null) - { - mLog.Dispose(); - mLog = null; - } - GC.SuppressFinalize(this); - } - - protected void MethodBegin(MethodInfo aMethod) - { - if (aMethod.MethodBase.GetFullName() == "SystemUInt64SystemCollectionsGenericList1SystemUInt64get_ItemSystemInt32") - { - Console.Write(""); - } - 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); - - // 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.Guid_NewGuid(); - - // 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.Guid_NewGuid(); - new Cosmos.Assembler.Label("GUID_" + xLabelGuid.ToString("N")); - - mCurrentMethodLabel = "METHOD_" + xLabelGuid.ToString("N"); - Cosmos.Assembler.Label.LastFullLabel = mCurrentMethodLabel; - - mCurrentMethodLabelEndGuid = DebugInfo.Guid_NewGuid(); - - 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 System.Reflection.MethodInfo; - if (xMethInfo != null) - { - xReturnSize = ILOp.Align(ILOp.SizeOfType(xMethInfo.ReturnType), 4); - } - if (aMethod.PlugMethod == null && !aMethod.IsInlineAssembler) - { - new Cosmos.Assembler.Label(ILOp.GetMethodLabel(aMethod) + 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 System.Reflection.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 = ILOp.GetMethodLabel(aMethod) + 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("N")); - } - - 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) - { - // 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}'", aMethod.MethodBase.GetFullName()); - mLog.Flush(); - if (aMethod.MethodAssembler != null) - { - mLog.WriteLine("Emitted using MethodAssembler", aMethod.MethodBase.GetFullName()); - mLog.Flush(); - var xAssembler = (AssemblerMethod)Activator.CreateInstance(aMethod.MethodAssembler); - xAssembler.AssembleNew(Assembler, aMethod.PluggedMethod); - } - else if (aMethod.IsInlineAssembler) - { - mLog.WriteLine("Emitted using Inline MethodAssembler", aMethod.MethodBase.GetFullName()); - mLog.Flush(); - 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("Method: " + 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); - } - - 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("--------- 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) - { - return X86.IL.Ldflda.ResolveField(method.MethodBase.DeclaringType, fieldId); - } - - protected void Ldarg(MethodInfo aMethod, int aIndex) - { - X86.IL.Ldarg.DoExecute(Assembler, aMethod, (ushort)aIndex); - } - - protected void Call(MethodInfo aMethod, MethodInfo aTargetMethod) - { - var xSize = X86.IL.Call.GetStackSizeToReservate(aTargetMethod.MethodBase); - if (xSize > 0) - { - new Sub { DestinationReg = Registers.ESP, SourceValue = xSize }; - } - new Call { DestinationLabel = ILOp.GetMethodLabel(aTargetMethod) }; - } - - 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, aFieldInfo, aMethod.PluggedMethod.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); - } - } - - } - } - if (!xType.IsInterface) - { - Push(aGetTypeID(xType)); - } - 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) - { - 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); - } - - 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); - 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++; - } - } - Call(aFrom, aTo); - } - 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("")) }; - - foreach (var xDataMember in Assembler.DataMembers) - { - if (!xDataMember.Name.StartsWith("StringLiteral")) - { - continue; - } - if (xDataMember.Name.EndsWith("__Contents")) - { - continue; - } - new Mov { DestinationRef = Cosmos.Assembler.ElementReference.New(xDataMember.Name), DestinationIsIndirect = true, SourceReg = Registers.EAX }; - } - 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 }; - Cosmos.Assembler.Assembler.WriteDebugVideo("Initializing string IDs."); - new Call { DestinationLabel = InitStringIDsLabel }; - - // 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); - 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); - - 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"); - - } - } - - 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 = (System.Reflection.MethodInfo)xMethodBase; - xMethodBase = xMethodInfo.GetGenericMethodDefinition(); - if (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; - } - } +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; + +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 System.IO.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 Guid mCurrentMethodLabelEndGuid; + protected Guid mCurrentMethodGuid; + + public AppAssembler(int aComPort) + { + Assembler = new Cosmos.Assembler.Assembler(aComPort); + mLog = new System.IO.StreamWriter("Cosmos.Assembler.Log", false); + InitILOps(); + } + + 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); + + // 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.Guid_NewGuid(); + + // 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.Guid_NewGuid(); + new Cosmos.Assembler.Label("GUID_" + xLabelGuid.ToString("N")); + + mCurrentMethodLabel = "METHOD_" + xLabelGuid.ToString("N"); + Cosmos.Assembler.Label.LastFullLabel = mCurrentMethodLabel; + + mCurrentMethodLabelEndGuid = DebugInfo.Guid_NewGuid(); + + 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 System.Reflection.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 System.Reflection.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("N")); + } + + 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) + { + // 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}'", aMethod.MethodBase.GetFullName()); + mLog.Flush(); + if (aMethod.MethodAssembler != null) + { + mLog.WriteLine("Emitted using MethodAssembler", aMethod.MethodBase.GetFullName()); + mLog.Flush(); + var xAssembler = (AssemblerMethod)Activator.CreateInstance(aMethod.MethodAssembler); + xAssembler.AssembleNew(Assembler, aMethod.PluggedMethod); + } + else if (aMethod.IsInlineAssembler) + { + mLog.WriteLine("Emitted using Inline MethodAssembler", aMethod.MethodBase.GetFullName()); + mLog.Flush(); + 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("Method: " + 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); + } + + 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("--------- 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) + { + return X86.IL.Ldflda.ResolveField(method.MethodBase.DeclaringType, fieldId); + } + + protected void Ldarg(MethodInfo aMethod, int aIndex) + { + X86.IL.Ldarg.DoExecute(Assembler, aMethod, (ushort)aIndex); + } + + protected void Call(MethodInfo aMethod, MethodInfo aTargetMethod) + { + var xSize = X86.IL.Call.GetStackSizeToReservate(aTargetMethod.MethodBase); + if (xSize > 0) + { + new Sub { DestinationReg = Registers.ESP, SourceValue = xSize }; + } + new Call { DestinationLabel = ILOp.GetMethodLabel(aTargetMethod) }; + } + + 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, aFieldInfo, aMethod.PluggedMethod.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); + } + } + + } + } + if (!xType.IsInterface) + { + Push(aGetTypeID(xType)); + } + 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) + { + 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); + } + + 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); + 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++; + } + } + Call(aFrom, aTo); + } + 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("")) }; + + foreach (var xDataMember in Assembler.DataMembers) + { + if (!xDataMember.Name.StartsWith("StringLiteral")) + { + continue; + } + if (xDataMember.Name.EndsWith("__Contents")) + { + continue; + } + new Mov { DestinationRef = Cosmos.Assembler.ElementReference.New(xDataMember.Name), DestinationIsIndirect = true, SourceReg = Registers.EAX }; + } + 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 }; + Cosmos.Assembler.Assembler.WriteDebugVideo("Initializing string IDs."); + new Call { DestinationLabel = InitStringIDsLabel }; + + // 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); + 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); + + 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"); + + } + } + + 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 = (System.Reflection.MethodInfo)xMethodBase; + xMethodBase = xMethodInfo.GetGenericMethodDefinition(); + if (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; + } + } } \ No newline at end of file