// ----------------------------------------------------------------------------
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ----------------------------------------------------------------------------

//
// Simple PE Loader for Singularity
//
// Currently does not support:
//      x64
//      sections loaded at separate addresses
//      loading at preferred address

// #define verbose

// #define SINGULARITY_ASMP

namespace Microsoft.Singularity.Loader
{
    using Microsoft.Singularity;
    using Microsoft.Singularity.Io;
    using Microsoft.Singularity.Memory;

    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Runtime.CompilerServices;
    using System.Threading;
    using Microsoft.Singularity.V1.Services;
    using Microsoft.Singularity.Hal;

    using PageType = System.GCs.PageType;

    [CLSCompliant(false)]
    [AccessedByRuntime("output to header : defined in hal.cpp/peimage.cpp")]
    internal class PEImage
    {
        // PE Signature
        internal readonly uint signature;

        // IMAGE_FILE_HEADER
        internal readonly ushort machine;
        internal readonly ushort numberOfSections;
        internal readonly uint   timeDateStamp;
        internal readonly ushort sizeOfOptionalHeader;
        internal readonly ushort characteristics;

#if PTR_SIZE_64
        internal readonly ulong imageBase;
#else
        internal readonly uint imageBase;
#endif
        // IMAGE_OPTIONAL_HEADER32
        internal readonly ushort magic;
        internal readonly uint   addressOfEntryPoint;
        internal readonly uint   sectionAlignment;
        internal readonly uint   fileAlignment;
        private  readonly uint   sizeOfImage; // use loadedSize (this is rnd'd)
        internal readonly uint   sizeOfHeaders;
        internal readonly uint   checkSum;
        internal readonly ushort dllCharacteristics;
        internal readonly uint   loaderFlags;
        internal readonly uint   numberOfRvaAndSizes;

        // IMAGE_DATA_DIRECTORY and SECTION_HEADERs
        internal readonly DirectoryEntry[] directory;
        internal readonly SectionHeader[] sections;
        internal readonly uint loadedSize;

        internal const ushort IMAGE_FILE_32BIT_MACHINE      = 0x0100; // 32 bit machine.
        internal const ushort IMAGE_FILE_DLL                = 0x2000; // File is a DLL.

        internal const ushort IMAGE_DOS_SIGNATURE           = 0x5A4D;     // MZ
        internal const uint   IMAGE_NT_SIGNATURE            = 0x00004550; // PE00
        internal const ushort IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x010b;
        internal const ushort IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x020b;

        enum Machine : ushort {
            UNKNOWN           = 0,
                I386              = 0x014c, // Intel 386.
                R3000             = 0x0162, // MIPS little-endian, = 0x160 big-endian
                R4000             = 0x0166, // MIPS little-endian
                R10000            = 0x0168, // MIPS little-endian
                WCEMIPSV2         = 0x0169, // MIPS little-endian WCE v2
                ALPHA             = 0x0184, // Alpha_AXP
                SH3               = 0x01a2, // SH3 little-endian
                SH3DSP            = 0x01a3,
                SH3E              = 0x01a4, // SH3E little-endian
                SH4               = 0x01a6, // SH4 little-endian
                SH5               = 0x01a8, // SH5
                ARM               = 0x01c0, // ARM Little-Endian
                THUMB             = 0x01c2,
                AM33              = 0x01d3,
                POWERPC           = 0x01F0, // IBM PowerPC Little-Endian
                POWERPCFP         = 0x01f1,
                IA64              = 0x0200, // Intel 64
                MIPS16            = 0x0266, // MIPS
                ALPHA64           = 0x0284, // ALPHA64
                MIPSFPU           = 0x0366, // MIPS
                MIPSFPU16         = 0x0466, // MIPS
                TRICORE           = 0x0520, // Infineon
                EBC               = 0x0EBC, // EFI Byte Code
                X64               = 0x8664, // AMD64, etc.
                M32R              = 0x9041, // M32R little-endian
                CEE               = 0xC0EE,
                };

        // Directory Entries

        enum DirEntry {
            Export      = 0,    // Export Directory
            Import      = 1,    // Import Directory
            Security    = 4,    // Security Directory
            BaseReloc   = 5,    // Base Relocation Table
            Debug       = 6,    // Debug Directory
            Copyright   = 7,    // Description String
        };

        internal UIntPtr VirtualAddress;

        // Corresponds to the WinNT IMAGE_NT_HEADERS data structure
        internal static PEImage kernel = null;
        internal static ExportTable kernelExports = null;
        internal static PEImage mpAbi = null;
        internal static ExportTable mpAbiExports = null;

        internal PEImage(IoMemory mem)
        {
            Kernel.Waypoint(601);
            if (mem.Length < 64) {
                throw new BadImageFormatException("Invalid MZ header");
            }

            magic = mem.Read16Unchecked(0);
            Kernel.Waypoint(602);

            int offset = (int)mem.Read32Unchecked(60);

            if (magic != IMAGE_DOS_SIGNATURE || offset < 64 || offset + 120 > mem.Length) {
                throw new BadImageFormatException("Invalid MZ header");
            }

            signature                   = mem.Read32Unchecked(offset + 0);
            machine                     = mem.Read16Unchecked(offset + 4);
            numberOfSections            = mem.Read16Unchecked(offset + 6);
            timeDateStamp               = mem.Read32Unchecked(offset + 8);
            sizeOfOptionalHeader        = mem.Read16Unchecked(offset + 20);
            characteristics             = mem.Read16Unchecked(offset + 22);
            magic                       = mem.Read16Unchecked(offset + 24);
            addressOfEntryPoint         = mem.Read32Unchecked(offset + 40);
            sectionAlignment            = mem.Read32Unchecked(offset + 56);
            fileAlignment               = mem.Read32Unchecked(offset + 60);
            sizeOfImage                 = mem.Read32Unchecked(offset + 80);
            sizeOfHeaders               = mem.Read32Unchecked(offset + 84);
            checkSum                    = mem.Read32Unchecked(offset + 88);
            dllCharacteristics          = mem.Read16Unchecked(offset + 94);
            loaderFlags                 = mem.Read32Unchecked(offset + 112);

#if PTR_SIZE_64
            imageBase                   = mem.Read64Unchecked(offset + 48);
            numberOfRvaAndSizes         = mem.Read32Unchecked(offset + 132);
            offset += 136;

            Kernel.Waypoint(603);
            // Verify that we have a valid NT header
            DebugStub.WriteLine("size of optional header ={0}", __arglist(sizeOfOptionalHeader));
            if (signature != IMAGE_NT_SIGNATURE ||
                sizeOfOptionalHeader != 240 ||
                magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC ||
                numberOfRvaAndSizes != 16) {

                throw new BadImageFormatException("Invalid PE header");
            }
#else
            imageBase = mem.Read32Unchecked(offset + 52);
            numberOfRvaAndSizes         = mem.Read32Unchecked(offset + 116);
            offset += 120;

            Kernel.Waypoint(603);
            // Verify that we have a valid NT header

            if (signature != IMAGE_NT_SIGNATURE ||
                sizeOfOptionalHeader != 224 ||
                magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC ||
                numberOfRvaAndSizes != 16)
            {

                throw new BadImageFormatException("Invalid PE header");
            }
#endif

            VirtualAddress = mem.VirtualAddress;

            Kernel.Waypoint(693);

            directory = new DirectoryEntry[numberOfRvaAndSizes];

            for (uint i = 0; i < numberOfRvaAndSizes; i++) {
                directory[i] = new DirectoryEntry(mem, ref offset);
            }

            sections = new SectionHeader[numberOfSections];
            for (uint i = 0; i < numberOfSections; i++) {
                sections[i] = new SectionHeader(mem, ref offset);

            }

            Kernel.Waypoint(694);

            // Calc the sizes of the loaded regions according to starting virtual address
            loadedSize = sizeOfImage;
            for (uint i = 0; i < numberOfSections; i++) {
                if (!sections[i].IsDiscardable) {
                    uint addr = sections[i].virtualAddress;
                    uint size = sections[i].virtualSize;
                    uint last = addr + size;

                    if (loadedSize < last) {
                        loadedSize = last;
                    }
                }
            }

            Kernel.Waypoint(604);
        }

        internal ExportTable GetExportTable(IoMemory mem)
        {
            if (directory[(int)DirEntry.Export].virtualAddress == 0) {
                return null;
            }
            return new ExportTable(mem, mem.VirtualAddress,
                                   directory[(int)DirEntry.Export]);
        }

        internal ImportTable GetImportTable(IoMemory mem)
        {
            if (directory[(int)DirEntry.Import].virtualAddress == 0) {
                return null;
            }
            return new ImportTable(mem, directory[(int)DirEntry.Import]);
        }

        [NoHeapAllocation]
        internal uint GetRelocationsRaw()
        {
            uint relocationAddr = directory[(int)DirEntry.BaseReloc].virtualAddress;

            for (uint i = 0; i < numberOfSections; i++) {
                if (sections[i].virtualAddress == relocationAddr) {
                    return sections[i].pointerToRawData;
                }
            }
            return 0;
        }

        ////////////////////////////////////////////////////// Static Methods.
        //
        public static unsafe void Initialize()
        {
            Platform p = Platform.ThePlatform;
            IoMemory pages = IoMemory.MapPhysicalMemory((UIntPtr) p.KernelDllFirstPage,
                                                        4096, true, false);
            kernel = new PEImage(pages);
            pages = IoMemory.MapPhysicalMemory((UIntPtr) p.KernelDllBase,
                                               kernel.loadedSize, true, false);

            kernelExports = kernel.GetExportTable(pages);

            DebugStub.WriteLine("PEImage.Initialize: Notify KD of kernel load.");
            DebugStub.LoadedBinary(pages.VirtualAddress,
                                   kernel.sizeOfImage,
#if ISA_ARM
                                   "kernel.arm",
#elif ISA_IX64
                                   "kernel.x64",
#elif ISA_IX86
                                   "kernel.x86",
#endif
                                   kernel.checkSum,
                                   kernel.timeDateStamp,
                                   false);

            // This breakpoint is triggered by a "-d" to windbg.
            if (DebugStub.PollForBreak()) {
                DebugStub.Break();
            }

#if GENERATE_ABI_SHIM
            DebugStub.WriteLine("    InitializeMpAbi() *****");
            InitializeMpAbi();
#endif
        }

        public static void InitializeMpAbi()
        {
            // Note: When mapping the abi stub (MpSyscalls.x86), we must
            // map it with writable flag! as we want to resolve the stub's
            // imports
            IoMemory mpPages = IoMemory.MapPhysicalMemory(Platform.MP_ABI_BASE, 4096, true, true);
            mpAbi = new PEImage(mpPages);
            mpPages = IoMemory.MapPhysicalMemory(Platform.MP_ABI_BASE, mpAbi.loadedSize, true, true);
            mpAbiExports = mpAbi.GetExportTable(mpPages);

            // Resolve imports in abi shim dll with the kernel exports
            // which are the real stub that will do IPI stuffs
            ImportTable mpAbiIt = mpAbi.GetImportTable(mpPages);

            DebugStub.WriteLine("HSG: ** Resolving Imports");
            mpAbiIt.ResolveImports(kernelExports);
        }

        public static void Finalize()
        {
            // Uncomment the following line to notify debugger when we shutdown.
            // DebugStub.UnloadedBinary(UIntPtr.MaxValue, false);
        }

        internal static PEImage KernelImage
        {
            [NoHeapAllocation]
            get { return kernel; }
        }

        public static PEImage Load(Process process, IoMemory rawMemory,
                                   out IoMemory loadedMemory, bool isForMp)
        {
            return Load(process, rawMemory, out loadedMemory,
                        isForMp, process == Process.kernelProcess);
        }

        public static PEImage Load(Process process, IoMemory rawMemory,
                                   out IoMemory loadedMemory, bool isForMp,
                                   bool inKernelSpace)
        {
            UIntPtr entryPoint = UIntPtr.Zero;
            loadedMemory = null;

            DiagnosisService.DeferedUpdateNotification();

            if (null == rawMemory || 0 == rawMemory.Length) {
                DebugStub.WriteLine("No PXE image to load!");
                return null;
            }

            //
            // Allocate memory and copy the PXE image from memory to memory
            //
#if verbose
            Tracing.Log(Tracing.Debug, " PXE at {0:x8}, Length ={1:x8}",
                        (uint)rawMemory.VirtualAddress,
                        rawMemory.Length);
#endif

            Kernel.Waypoint(580);
            Tracing.Log(Tracing.Debug, "Loading:");
            PEImage image = new PEImage(rawMemory);
            image.DumpLimitedToStream();

#if verbose
            Tracing.Log(Tracing.Debug, "  Loaded Size={0:x8}", image.loadedSize);
#endif
            Kernel.Waypoint(581);
            if (0 == image.loadedSize) {
                throw new BadImageFormatException("Invalid PE, no content");
            }

            try {
                if (inKernelSpace) {
                    loadedMemory = IoMemory.AllocateFixed(
                        (UIntPtr)image.loadedSize,
                        process, PageType.System);
                }
                else {
                    // Situate the process image in the user range
                    loadedMemory = IoMemory.AllocateUserFixed(
                        (UIntPtr)image.loadedSize,
                        process, PageType.System);
                }

                Kernel.Waypoint(582);


#if verbose
                Tracing.Log(Tracing.Debug, " loaded at {0:x8}, Length ={1:x8}",
                            (uint)loadedMemory.VirtualAddress,
                            loadedMemory.Length);
#endif
                // Copy the header so the debugger can find it.
                IoMemory.Copy(rawMemory, 0, loadedMemory, 0, (int)image.sizeOfHeaders);

#if verbose
                image.DumpLimitedToStream();
#endif

                Kernel.Waypoint(583);
                // load sections into memory where they belong.
                for (int i = 0; i < image.numberOfSections; i++) {
                    if (image.sections[i].IsDiscardable ||
                        image.sections[i].sizeOfRawData == 0) {
                        continue;
                    }

                    int targetOffset = (int)image.sections[i].virtualAddress;
                    int sourceOffset = (int)image.sections[i].pointerToRawData;
                    uint rawSize = Math.Min(image.sections[i].sizeOfRawData,
                                            image.sections[i].virtualSize);
#if verbose
                    Tracing.Log(Tracing.Debug, "section[{0}] source={1:x8}..{2:x8} target={3:x8}..{4:x8}",
                                      i,
                                      sourceOffset, sourceOffset + rawSize,
                                      targetOffset, targetOffset + rawSize);
#endif

                    if (image.sections[i].virtualSize > image.sections[i].sizeOfRawData) {

                        //  The memory allocated for the new image is not zeroed, therefore
                        //  we need to clear the remaining region of a section that is not getting
                        //  copied from the source. NOTE BSS sections rely on this to be zeroed
                        //  This is fixing some random bugs with uninitialized variables in unmanaged code

                        unsafe {

                            byte * dest = (byte *) loadedMemory.VirtualAddress + targetOffset + image.sections[i].sizeOfRawData;
                            uint length = image.sections[i].virtualSize - image.sections[i].sizeOfRawData;

                            Buffer.ZeroMemory(dest, length);
                        }
                    }

                    IoMemory.Copy(rawMemory, sourceOffset, loadedMemory, targetOffset, (int)rawSize);
                }
                Kernel.Waypoint(584);

                //
                // Handle Relocations
                //
                int relocationTableOffset = (int)image.GetRelocationsRaw();
                UIntPtr diff = (UIntPtr) loadedMemory.VirtualAddress - (UIntPtr) image.imageBase;
                image.VirtualAddress = loadedMemory.VirtualAddress;

#if verbose
                Tracing.Log(Tracing.Debug, " Base loaded={0:x8}, relocated ={1:x8} diff={2:x8}",
                            image.imageBase, (uint)loadedMemory.VirtualAddress, diff);

                Tracing.Log(Tracing.Debug, " relocationTableOffset ={0:x8} ", relocationTableOffset);
#endif

                if (relocationTableOffset > 0) {
                    Relocations.FixupBlocks(rawMemory, (int)relocationTableOffset,
                                            loadedMemory, diff);
                    // TODO: We should probably zero the relocation table.
                }

                Kernel.Waypoint(585);

                //
                // Resolve Imports
                //
                ImportTable it = image.GetImportTable(loadedMemory);
                Kernel.Waypoint(586);

#if !PAGING

#if GENERATE_ABI_SHIM
                if (it != null) {
#endif
                    //it.DumpIAT("Import directory");

                    // if this is loading for remote processor
                    // then solve the imports with abi stub
                    if (isForMp) {
                        it.ResolveImports(mpAbiExports);
                    }
                    else {
                        it.ResolveImports(kernelExports);
                    }

                    //DumpIAT(loadedMemory, "Import Address Table",
                    // ref image.directory[12]);
#if GENERATE_ABI_SHIM
                }
#endif

#else
                if (it != null) {
                    // Ring-3 protection domains come with a set of
                    // stubs that accomplish the ring3-to-ring0 ABI
                    // transitions. Use these when called for.
                    ExportTable abiStubs = process.Domain.ABIStubs;

                    if (abiStubs != null) {
                        it.ResolveImports(abiStubs);
                    }
                    else {
                    it.ResolveImports(kernelExports);
                }
                }
#endif

                Kernel.Waypoint(587);

                //
                // Dump Exports
                //
                ExportTable et = image.GetExportTable(loadedMemory);
                if (et != null) {
                    et.Dump();
                }

                Kernel.Waypoint(588);

                entryPoint = (UIntPtr)loadedMemory.VirtualAddress + image.addressOfEntryPoint;

                Tracing.Log(Tracing.Debug, "  Loaded: {0:x8}..{1:x8}, Entry: {2:x8}",
                                  (ulong)loadedMemory.VirtualAddress,
                                  (ulong)(loadedMemory.VirtualAddress + loadedMemory.Length),
                                  (ulong)(entryPoint));
            }
            finally {
                if (entryPoint == UIntPtr.Zero && loadedMemory != null) {
                    // TODO: Need to dispose of target Range.
                    loadedMemory = null;
                }
            }
            return image;
        }

        // HelloMpLoad skips several procedures to be able to
        // load HelloMp.x86 properly
        public static PEImage HelloMpLoad(Process process, IoMemory rawMemory,
                                          out IoMemory loadedMemory)
        {
            UIntPtr entryPoint = UIntPtr.Zero;
            loadedMemory = null;

            if (null == rawMemory || 0 == rawMemory.Length) {
                DebugStub.WriteLine("No PXE image to load!");
                return null;
            }

            Kernel.Waypoint(580);
            Tracing.Log(Tracing.Debug, "Loading:");
            PEImage image = new PEImage(rawMemory);
            image.DumpLimitedToStream();

            Kernel.Waypoint(581);
            if (0 == image.loadedSize) {
                throw new BadImageFormatException("Invalid PE, no content");
            }

            try {
                // The loadedImage is current loaded to physical memory
                // directly
                loadedMemory = IoMemory.AllocatePhysical
                    ((UIntPtr)image.loadedSize);

                Kernel.Waypoint(582);

                // Copy the header so the debugger can find it.
                IoMemory.Copy(rawMemory, 0, loadedMemory, 0,
                              (int)image.sizeOfHeaders);
                Kernel.Waypoint(583);
                // load sections into memory where they belong.
                for (int i = 0; i < image.numberOfSections; i++) {
                    if (image.sections[i].IsDiscardable ||
                        image.sections[i].sizeOfRawData == 0) {
                        continue;
                    }
                    int targetOffset = (int)image.sections[i].virtualAddress;
                    int sourceOffset = (int)image.sections[i].pointerToRawData;
                    uint rawSize = Math.Min(image.sections[i].sizeOfRawData,
                                            image.sections[i].virtualSize);
                    IoMemory.Copy(rawMemory, sourceOffset, loadedMemory,
                                  targetOffset, (int)rawSize);
                }
                Kernel.Waypoint(584);

                int relocationTableOffset = (int)image.GetRelocationsRaw();
                UIntPtr diff = (UIntPtr) loadedMemory.VirtualAddress -
                    (UIntPtr) image.imageBase;
                entryPoint = (UIntPtr)loadedMemory.VirtualAddress +
                    image.addressOfEntryPoint;
            }
            finally {
                if (entryPoint == UIntPtr.Zero && loadedMemory != null) {
                    // TODO: Need to dispose of target Range.
                    loadedMemory = null;
                }
            }
            return image;
        }


        [NoHeapAllocation]
        public UIntPtr GetEntryPoint(IoMemory loadedImage)
        {
            return loadedImage.VirtualAddress + addressOfEntryPoint;
        }

        [AccessedByRuntime("output to header : defined in PEImage.cpp")]
        [OutsideGCDomain]
        [StackBound(256)]
        [NoInline]
        [NoStackLinkCheck]
        [NoStackOverflowCheck]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [NoHeapAllocation]
        internal static extern int HalCallEntryPoint(UIntPtr entry, int threadIndex,
                                                     bool runAtRing3);

        [NoHeapAllocation]
        internal static int CallEntryPoint(UIntPtr entry, int threadIndex,
                                           bool runAtRing3)
        {
            return HalCallEntryPoint(entry, threadIndex, runAtRing3);
        }

        //
        // Output Routines
        //
        [Conditional("DEBUG")]
        internal void DumpLimitedToStream()
        {
            Tracing.Log(Tracing.Debug, "  Entry point:         {0:x8}", addressOfEntryPoint);
            Tracing.Log(Tracing.Debug, "  Image base:          {0:x8}", imageBase);
#if verbose
            Tracing.Log(Tracing.Debug, "  Section alignment:   {0:x8}", sectionAlignment);
            Tracing.Log(Tracing.Debug, "  File alignment:      {0:x8}", fileAlignment);
            Tracing.Log(Tracing.Debug, "  Directories:         {0:x8}", numberOfRvaAndSizes);
#endif
            Tracing.Log(Tracing.Debug, "  Loaded Size:         {0:x8}", loadedSize);
            if (directory[0].virtualAddress != 0) {
                directory[0].DumpToStream("Export directory      ");
            }
            if (directory[1].virtualAddress != 0) {
                directory[1].DumpToStream("Import directory      ");
            }
            if (directory[5].virtualAddress != 0) {
                directory[5].DumpToStream("Relocation Table      ");
            }
            if (directory[6].virtualAddress != 0) {
                directory[6].DumpToStream("Debug Directory       ");
            }
            if (directory[11].virtualAddress != 0) {
                directory[11].DumpToStream("Bound Import Table   ");
            }
            if (directory[12].virtualAddress != 0) {
                directory[12].DumpToStream("Import Address Table ");
            }

            for (uint i = 0; i < numberOfSections; i++) {
                sections[i].DumpToStream();
            }
        }

    }
}
