//++
//
//  Copyright (c) Microsoft Corporation
//
//  Module Name:
//
//    blpecoff.cpp
//
//  Abstract:
//
//    This module implements PE / COFF support for the boot loader.
//
//--

#include "bl.h"

#define IMAGE_DOS_SIGNATURE                 0x05A4D         // MZ
#define IMAGE_NT_SIGNATURE                  0x000004550     // PE00

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    UINT16 e_magic;                     // Magic number
    UINT16 e_cblp;                      // Bytes on last page of file
    UINT16 e_cp;                        // Pages in file
    UINT16 e_crlc;                      // Relocations
    UINT16 e_cparhdr;                   // Size of header in paragraphs
    UINT16 e_minalloc;                  // Minimum extra paragraphs needed
    UINT16 e_maxalloc;                  // Maximum extra paragraphs needed
    UINT16 e_ss;                        // Initial (relative) SS value
    UINT16 e_sp;                        // Initial SP value
    UINT16 e_csum;                      // Checksum
    UINT16 e_ip;                        // Initial IP value
    UINT16 e_cs;                        // Initial (relative) CS value
    UINT16 e_lfarlc;                    // File address of relocation table
    UINT16 e_ovno;                      // Overlay number
    UINT16 e_res[4];                    // Reserved words
    UINT16 e_oemid;                     // OEM identifier (for e_oeminfo)
    UINT16 e_oeminfo;                   // OEM information; e_oemid specific
    UINT16 e_res2[10];                  // Reserved words
    INT32 e_lfanew;                      // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;


typedef struct _IMAGE_FILE_HEADER {
    UINT16 Machine;
    UINT16 NumberOfSections;
    UINT32 TimeDateStamp;
    UINT32 PointerToSymbolTable;
    UINT32 NumberOfSymbols;
    UINT16 SizeOfOptionalHeader;
    UINT16 Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

typedef struct _IMAGE_DATA_DIRECTORY {
    UINT32 VirtualAddress;
    UINT32 Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

#define IMAGE_DIRECTORY_ENTRY_BASERELOC     5

#if defined(BOOT_X86)

typedef struct _IMAGE_OPTIONAL_HEADER_32 {
    UINT16 Magic;
    UINT8 MajorLinkerVersion;
    UINT8 MinorLinkerVersion;
    UINT32 SizeOfCode;
    UINT32 SizeOfInitializedData;
    UINT32 SizeOfUninitializedData;
    UINT32 AddressOfEntryPoint;
    UINT32 BaseOfCode;
    UINT32 BaseOfData;
    UINT32 ImageBase;
    UINT32 SectionAlignment;
    UINT32 FileAlignment;
    UINT16 MajorOperatingSystemVersion;
    UINT16 MinorOperatingSystemVersion;
    UINT16 MajorImageVersion;
    UINT16 MinorImageVersion;
    UINT16 MajorSubsystemVersion;
    UINT16 MinorSubsystemVersion;
    UINT32 Win32VersionValue;
    UINT32 SizeOfImage;
    UINT32 SizeOfHeaders;
    UINT32 CheckSum;
    UINT16 Subsystem;
    UINT16 DllCharacteristics;
    UINT32 SizeOfStackReserve;
    UINT32 SizeOfStackCommit;
    UINT32 SizeOfHeapReserve;
    UINT32 SizeOfHeapCommit;
    UINT32 LoaderFlags;
    UINT32 NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

typedef struct _IMAGE_NT_HEADERS32 {
    UINT32 Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

typedef IMAGE_NT_HEADERS32 IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

#elif defined(BOOT_X64)

typedef struct _IMAGE_OPTIONAL_HEADER64 {
    UINT16 Magic;
    UINT8 MajorLinkerVersion;
    UINT8 MinorLinkerVersion;
    UINT32 SizeOfCode;
    UINT32 SizeOfInitializedData;
    UINT32 SizeOfUninitializedData;
    UINT32 AddressOfEntryPoint;
    UINT32 BaseOfCode;
    UINT64 ImageBase;
    UINT32 SectionAlignment;
    UINT32 FileAlignment;
    UINT16 MajorOperatingSystemVersion;
    UINT16 MinorOperatingSystemVersion;
    UINT16 MajorImageVersion;
    UINT16 MinorImageVersion;
    UINT16 MajorSubsystemVersion;
    UINT16 MinorSubsystemVersion;
    UINT32 Win32VersionValue;
    UINT32 SizeOfImage;
    UINT32 SizeOfHeaders;
    UINT32 CheckSum;
    UINT16 Subsystem;
    UINT16 DllCharacteristics;
    UINT64 SizeOfStackReserve;
    UINT64 SizeOfStackCommit;
    UINT64 SizeOfHeapReserve;
    UINT64 SizeOfHeapCommit;
    UINT32 LoaderFlags;
    UINT32 NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

typedef struct _IMAGE_NT_HEADERS64 {
    UINT32 Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

typedef IMAGE_NT_HEADERS64 IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

#endif

#define IMAGE_SIZEOF_SHORT_NAME              8

typedef struct _IMAGE_SECTION_HEADER {
    UINT8 Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
        UINT32 PhysicalAddress;
        UINT32 VirtualSize;
    } Misc;
    UINT32 VirtualAddress;
    UINT32 SizeOfRawData;
    UINT32 PointerToRawData;
    UINT32 PointerToRelocations;
    UINT32 PointerToLinenumbers;
    UINT16 NumberOfRelocations;
    UINT16 NumberOfLinenumbers;
    UINT32 Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

typedef struct _IMAGE_BASE_RELOCATION {
    UINT32   VirtualAddress;
    UINT32   SizeOfBlock;
    UINT16  TypeOffset[0];
} IMAGE_BASE_RELOCATION, *PIMAGE_BASE_RELOCATION;

#define IMAGE_REL_BASED_ABSOLUTE              0
#define IMAGE_REL_BASED_HIGHLOW               3
#define IMAGE_REL_BASED_DIR64                 10

VOID
BlPeGetVirtualRange(
    PVOID Image,
    PVOID *VirtualBase,
    ULONG_PTR *VirtualSize
    )

//++
//
//  Routine Description:
//
//    This function queries the virtual range for the specified image.
//
//  Arguments:
//
//    Image       - Supplies a pointer to the image.
//
//    VirtualBase - Receives the virtual base address of the image.
//
//    VirtualSize - Receives the virtual size of the image.
//
//--

{
    PIMAGE_DOS_HEADER DosHeader;
    UINT32 Index;
    PIMAGE_NT_HEADERS NtHeader;
    PIMAGE_SECTION_HEADER Section;
    UINT32 SectionEnd;

    DosHeader = (PIMAGE_DOS_HEADER) Image;

    if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE) {

        BlRtlPrintf("PECOFF: Invalid image!\n");
        BlRtlHalt();
    }

    NtHeader = (PIMAGE_NT_HEADERS) ((ULONG_PTR) Image + DosHeader->e_lfanew);

    if (NtHeader->Signature != IMAGE_NT_SIGNATURE) {

        BlRtlPrintf("PECOFF: Invalid image!\n");
        BlRtlHalt();
    }

    if (NtHeader->FileHeader.NumberOfSections == 0) {

        BlRtlPrintf("PECOFF: Invalid image!\n");
        BlRtlHalt();
    }

    if ((NtHeader->OptionalHeader.ImageBase % PAGE_SIZE) != 0) {

        BlRtlPrintf("PECOFF: Invalid image!\n");
        BlRtlHalt();
    }

    *VirtualBase = (PVOID) NtHeader->OptionalHeader.ImageBase;
    *VirtualSize = 0;

    Section = (PIMAGE_SECTION_HEADER) (((ULONG_PTR) &NtHeader->OptionalHeader) + NtHeader->FileHeader.SizeOfOptionalHeader);

    for (Index = 0; Index < NtHeader->FileHeader.NumberOfSections; Index += 1) {

        SectionEnd = Section[Index].VirtualAddress + Section[Index].Misc.VirtualSize;

        if (SectionEnd > *VirtualSize) {

            *VirtualSize = SectionEnd;
        }
    }

    *VirtualSize = ROUND_UP_TO_PAGES(*VirtualSize);

    return;
}

VOID
BlPeApplyFixupBlock(
    PIMAGE_BASE_RELOCATION Block,
    ULONG_PTR VirtualBase,
    ULONG_PTR RelocDiff
    )

//++
//
//  Routine Description:
//
//    This function applies all of the base fixups in the specified block to the image.
//
//  Arguments:
//
//    Block       - Supplies a pointer to the base relocation fixup block.
//
//    VirtualBase - Supplies the target address of the image.
//
//    RelocDiff   - Supplies the offset of the image target address from the base address.
//
//--

{
    PUINT16 Reloc;
    PUINT16 BlockEnd;
    ULONG_PTR BlockBase;
    ULONG_PTR Target;

    Reloc = Block->TypeOffset;
    BlockEnd = (PUINT16) ( ((PUINT8) Block) + Block->SizeOfBlock);
    BlockBase = VirtualBase + Block->VirtualAddress;

#if PECOFF_VERBOSE

    BlRtlPrintf("PECOFF: Reloc Block %p:\n", Block->VirtualAddress);

#endif

    for (; Reloc < BlockEnd; Reloc++) {

        Target = BlockBase + (*Reloc & 0xfff);

#if PECOFF_VERBOSE

        switch (*Reloc >> 12) {

            case IMAGE_REL_BASED_ABSOLUTE: {

                BlRtlPrintf("PECOFF:  %p: abs:%x \r", (PUINT32) Target, * (PUINT32) Target);
                break;
            }

            case IMAGE_REL_BASED_HIGHLOW: {

                BlRtlPrintf("PECOFF:  %p: r32:%x->%x \r", (PUINT32) Target, * (PUINT32) Target, * (PUINT32) Target + (UINT32) RelocDiff);
                break;
            }

            case IMAGE_REL_BASED_DIR64: {

                BlRtlPrintf("PECOFF:  %p: r64:%lx->%lx \r", (PUINT64) Target, * (PUINT64) Target, * (PUINT64) Target + (UINT64) RelocDiff);
                break;
            }

            default: {

                BlRtlPrintf("PECOFF:  %p: %x ??? \r", (PUINT32) Target, Reloc[0] >> 12);
                break;
            }
        }

#endif

        switch (*Reloc >> 12) {
            case IMAGE_REL_BASED_ABSOLUTE: {

                break;
            }

            case IMAGE_REL_BASED_HIGHLOW: {

                * (PUINT32) Target += (UINT32) RelocDiff;
                break;
            }

            case IMAGE_REL_BASED_DIR64: {

                * (PUINT64 *) Target += (UINT64) RelocDiff;
                break;
            }

            default: {

                BlRtlPrintf("PECOFF: Illegal relocation.\n");
                BlRtlHalt();
            }
        }
    }
}

VOID
BlPeLoadImage(
    PVOID LoadBase,
    PVOID Image,
    PVOID *EntryPoint
    )

//++
//
//  Routine Description:
//
//    This function loads the specified image.
//
//  Arguments:
//
//    Image       - Supplies a pointer to the image to load.
//
//    EntryPoint  - Receives a pointer to the entry point of the image.
//
//--

{
    ULONG_PTR BytesToCopy;
    PIMAGE_DOS_HEADER DosHeader;
    UINT32 Index;
    PIMAGE_NT_HEADERS NtHeader;
    PIMAGE_SECTION_HEADER Section;
    ULONG_PTR VirtualBase;
    ULONG_PTR RelocDiff;

    VirtualBase = (ULONG_PTR) LoadBase;
    DosHeader = (PIMAGE_DOS_HEADER) Image;

    if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE) {

        BlRtlPrintf("PECOFF: Missing MZ!\n");
        BlRtlHalt();
    }

    NtHeader = (PIMAGE_NT_HEADERS) ((ULONG_PTR) Image + DosHeader->e_lfanew);

    if (NtHeader->Signature != IMAGE_NT_SIGNATURE) {

        BlRtlPrintf("PECOFF: Missing PE!\n");
        BlRtlHalt();
    }

    if (NtHeader->FileHeader.NumberOfSections == 0) {

        BlRtlPrintf("PECOFF: No sections!\n");
        BlRtlHalt();
    }

    if ((NtHeader->OptionalHeader.ImageBase % PAGE_SIZE) != 0) {

        BlRtlPrintf("PECOFF: Not page aligned.\n");
        BlRtlHalt();
    }

    BlRtlCopyMemory((PVOID) VirtualBase,
                    Image,
                    NtHeader->OptionalHeader.SizeOfHeaders);

    Section = (PIMAGE_SECTION_HEADER) (((ULONG_PTR) &NtHeader->OptionalHeader) + NtHeader->FileHeader.SizeOfOptionalHeader);

    for (Index = 0; Index < NtHeader->FileHeader.NumberOfSections; Index += 1) {

        BlRtlZeroMemory((PVOID) (VirtualBase + Section[Index].VirtualAddress), Section[Index].Misc.VirtualSize);

        if (Section[Index].SizeOfRawData < Section[Index].Misc.VirtualSize) {

            BytesToCopy = Section[Index].SizeOfRawData;

        } else {

            BytesToCopy = Section[Index].Misc.VirtualSize;
        }

        BlRtlCopyMemory((PVOID) (VirtualBase + Section[Index].VirtualAddress),
                        (PVOID) (((ULONG_PTR) Image) + Section[Index].PointerToRawData),
                        BytesToCopy);

#if PECOFF_VERBOSE

        {
            CHAR Temp[IMAGE_SIZEOF_SHORT_NAME + 1];

            BlRtlCopyMemory(Temp,
                            Section[Index].Name,
                            IMAGE_SIZEOF_SHORT_NAME);

            Temp[IMAGE_SIZEOF_SHORT_NAME] = 0;

            BlRtlPrintf("PECOFF: %p ... %p (%p ... %p) %s\n",
                        VirtualBase + Section[Index].VirtualAddress,
                        VirtualBase + Section[Index].VirtualAddress + Section[Index].Misc.VirtualSize - 1,
                        (((ULONG_PTR) Image) + Section[Index].PointerToRawData),
                        (((ULONG_PTR) Image) + Section[Index].PointerToRawData) + BytesToCopy,
                        Temp);
        }

#endif

    }

    RelocDiff = VirtualBase - (ULONG_PTR)NtHeader->OptionalHeader.ImageBase;

    if (RelocDiff != 0) {

        PUINT8 RelocList;
        PUINT8 RelocListEnd;
        PIMAGE_BASE_RELOCATION Block;

        RelocList = (PUINT8) (VirtualBase + NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
        RelocListEnd = RelocList + NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;

#if PECOFF_VERBOSE

        BlRtlPrintf("PECOFF: Relocs: %p ... %p\n", RelocList, RelocListEnd);

#endif

        while (RelocList < RelocListEnd) {

            Block = (PIMAGE_BASE_RELOCATION) RelocList;

            BlPeApplyFixupBlock(Block, VirtualBase, RelocDiff);

            RelocList += Block->SizeOfBlock;
        }
    }

    *EntryPoint = (PVOID) (VirtualBase + NtHeader->OptionalHeader.AddressOfEntryPoint);

    return;
}

