/** @file

  Functions to get info and load PE/COFF image.

Copyright (c) 2004 - 2018, Intel Corporation. All rights reserved.<BR>
Portions Copyright (c) 2011 - 2013, ARM Ltd. All rights reserved.<BR>
Portions Copyright (c) 2020, Hewlett Packard Enterprise Development LP. All rights reserved.<BR>
Portions Copyright (c) 2022, Loongson Technology Corporation Limited. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <Common/UefiBaseTypes.h>
#include <CommonLib.h>
#include <IndustryStandard/PeImage.h>
#include "PeCoffLib.h"

typedef union {
  VOID                         *Header;
  EFI_IMAGE_OPTIONAL_HEADER32  *Optional32;
  EFI_IMAGE_OPTIONAL_HEADER64  *Optional64;
} EFI_IMAGE_OPTIONAL_HEADER_POINTER;

STATIC
RETURN_STATUS
PeCoffLoaderGetPeHeader (
  IN OUT PE_COFF_LOADER_IMAGE_CONTEXT    *ImageContext,
  OUT    EFI_IMAGE_OPTIONAL_HEADER_UNION **PeHdr,
  OUT    EFI_TE_IMAGE_HEADER             **TeHdr
  );

STATIC
RETURN_STATUS
PeCoffLoaderCheckImageType (
  IN OUT PE_COFF_LOADER_IMAGE_CONTEXT    *ImageContext,
  IN     EFI_IMAGE_OPTIONAL_HEADER_UNION *PeHdr,
  IN     EFI_TE_IMAGE_HEADER             *TeHdr
  );

STATIC
VOID *
PeCoffLoaderImageAddress (
  IN OUT PE_COFF_LOADER_IMAGE_CONTEXT  *ImageContext,
  IN     UINTN                         Address
  );

RETURN_STATUS
PeCoffLoaderRelocateIa32Image (
  IN UINT16      *Reloc,
  IN OUT CHAR8   *Fixup,
  IN OUT CHAR8   **FixupData,
  IN UINT64      Adjust
  );


RETURN_STATUS
PeCoffLoaderRelocateArmImage (
  IN UINT16      **Reloc,
  IN OUT CHAR8   *Fixup,
  IN OUT CHAR8   **FixupData,
  IN UINT64      Adjust
  );

RETURN_STATUS
PeCoffLoaderRelocateRiscVImage (
  IN UINT16      *Reloc,
  IN OUT CHAR8   *Fixup,
  IN OUT CHAR8   **FixupData,
  IN UINT64      Adjust
  );

RETURN_STATUS
PeCoffLoaderRelocateLoongArch64Image (
  IN UINT16      *Reloc,
  IN OUT CHAR8   *Fixup,
  IN OUT CHAR8   **FixupData,
  IN UINT64      Adjust
  );

/**
  Retrieves the PE or TE Header from a PE/COFF or TE image

  @param ImageContext  The context of the image being loaded
  @param PeHdr         The buffer in which to return the PE header
  @param TeHdr         The buffer in which to return the TE header

  @return RETURN_SUCCESS if the PE or TE Header is read,
  Otherwise, the error status from reading the PE/COFF or TE image using the ImageRead function.
**/
STATIC
RETURN_STATUS
PeCoffLoaderGetPeHeader (
  IN OUT PE_COFF_LOADER_IMAGE_CONTEXT    *ImageContext,
  OUT    EFI_IMAGE_OPTIONAL_HEADER_UNION **PeHdr,
  OUT    EFI_TE_IMAGE_HEADER             **TeHdr
  )
{
  RETURN_STATUS         Status;
  EFI_IMAGE_DOS_HEADER  DosHdr;
  UINTN                 Size;

  ImageContext->IsTeImage = FALSE;
  //
  // Read the DOS image headers
  //
  Size = sizeof (EFI_IMAGE_DOS_HEADER);
  Status = ImageContext->ImageRead (
                          ImageContext->Handle,
                          0,
                          &Size,
                          &DosHdr
                          );
  if (RETURN_ERROR (Status)) {
    ImageContext->ImageError = IMAGE_ERROR_IMAGE_READ;
    return Status;
  }

  ImageContext->PeCoffHeaderOffset = 0;
  if (DosHdr.e_magic == EFI_IMAGE_DOS_SIGNATURE) {
    //
    // DOS image header is present, so read the PE header after the DOS image header
    //
    ImageContext->PeCoffHeaderOffset = DosHdr.e_lfanew;
  }
  //
  // Get the PE/COFF Header pointer
  //
  *PeHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *) ((UINTN)ImageContext->Handle + ImageContext->PeCoffHeaderOffset);
  if ((*PeHdr)->Pe32.Signature != EFI_IMAGE_NT_SIGNATURE) {
    //
    // Check the PE/COFF Header Signature. If not, then try to get a TE header
    //
    *TeHdr = (EFI_TE_IMAGE_HEADER *)*PeHdr;
    if ((*TeHdr)->Signature != EFI_TE_IMAGE_HEADER_SIGNATURE) {
      return RETURN_UNSUPPORTED;
    }
    ImageContext->IsTeImage = TRUE;
  }

  return RETURN_SUCCESS;
}

/**
  Checks the PE or TE header of a PE/COFF or TE image to determine if it supported

  @param ImageContext  The context of the image being loaded
  @param PeHdr         The buffer in which to return the PE header
  @param TeHdr         The buffer in which to return the TE header

  @retval RETURN_SUCCESS if the PE/COFF or TE image is supported
  @retval RETURN_UNSUPPORTED of the PE/COFF or TE image is not supported.

**/
STATIC
RETURN_STATUS
PeCoffLoaderCheckImageType (
  IN OUT PE_COFF_LOADER_IMAGE_CONTEXT          *ImageContext,
  IN     EFI_IMAGE_OPTIONAL_HEADER_UNION       *PeHdr,
  IN     EFI_TE_IMAGE_HEADER                   *TeHdr
  )
{
  //
  // See if the machine type is supported.
  // We support a native machine type (IA-32/Itanium-based)
  //
  if (ImageContext->IsTeImage == FALSE) {
    ImageContext->Machine = PeHdr->Pe32.FileHeader.Machine;
  } else {
    ImageContext->Machine = TeHdr->Machine;
  }

  if (ImageContext->Machine != IMAGE_FILE_MACHINE_I386 && \
      ImageContext->Machine != IMAGE_FILE_MACHINE_X64  && \
      ImageContext->Machine != IMAGE_FILE_MACHINE_ARMTHUMB_MIXED && \
      ImageContext->Machine != IMAGE_FILE_MACHINE_EBC  && \
      ImageContext->Machine != IMAGE_FILE_MACHINE_ARM64 && \
      ImageContext->Machine != IMAGE_FILE_MACHINE_RISCV64 && \
      ImageContext->Machine != IMAGE_FILE_MACHINE_LOONGARCH64) {
    //
    // unsupported PeImage machine type
    //
    return RETURN_UNSUPPORTED;
  }

  //
  // See if the image type is supported.  We support EFI Applications,
  // EFI Boot Service Drivers, EFI Runtime Drivers and EFI SAL Drivers.
  //
  if (ImageContext->IsTeImage == FALSE) {
    ImageContext->ImageType = PeHdr->Pe32.OptionalHeader.Subsystem;
  } else {
    ImageContext->ImageType = (UINT16) (TeHdr->Subsystem);
  }

  if (ImageContext->ImageType != EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION && \
      ImageContext->ImageType != EFI_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER && \
      ImageContext->ImageType != EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER && \
      ImageContext->ImageType != EFI_IMAGE_SUBSYSTEM_SAL_RUNTIME_DRIVER) {
    //
    // unsupported PeImage subsystem type
    //
    return RETURN_UNSUPPORTED;
  }

  return RETURN_SUCCESS;
}

/**
  Retrieves information on a PE/COFF image

  @param This         Calling context
  @param ImageContext The context of the image being loaded

  @retval RETURN_SUCCESS           The information on the PE/COFF image was collected.
  @retval RETURN_INVALID_PARAMETER ImageContext is NULL.
  @retval RETURN_UNSUPPORTED       The PE/COFF image is not supported.
  @retval Otherwise                The error status from reading the PE/COFF image using the
                                  ImageContext->ImageRead() function

**/
RETURN_STATUS
EFIAPI
PeCoffLoaderGetImageInfo (
  IN OUT PE_COFF_LOADER_IMAGE_CONTEXT           *ImageContext
  )
{
  RETURN_STATUS                   Status;
  EFI_IMAGE_OPTIONAL_HEADER_UNION *PeHdr;
  EFI_TE_IMAGE_HEADER             *TeHdr;
  EFI_IMAGE_DATA_DIRECTORY        *DebugDirectoryEntry;
  UINTN                           Size;
  UINTN                           Index;
  UINTN                           DebugDirectoryEntryRva;
  UINTN                           DebugDirectoryEntryFileOffset;
  UINTN                           SectionHeaderOffset;
  EFI_IMAGE_SECTION_HEADER        SectionHeader;
  EFI_IMAGE_DEBUG_DIRECTORY_ENTRY DebugEntry;
  EFI_IMAGE_OPTIONAL_HEADER_POINTER OptionHeader;

  PeHdr = NULL;
  TeHdr = NULL;
  DebugDirectoryEntry    = NULL;
  DebugDirectoryEntryRva = 0;

  if (NULL == ImageContext) {
    return RETURN_INVALID_PARAMETER;
  }
  //
  // Assume success
  //
  ImageContext->ImageError  = IMAGE_ERROR_SUCCESS;

  Status                    = PeCoffLoaderGetPeHeader (ImageContext, &PeHdr, &TeHdr);
  if (RETURN_ERROR (Status)) {
    return Status;
  }

  //
  // Verify machine type
  //
  Status = PeCoffLoaderCheckImageType (ImageContext, PeHdr, TeHdr);
  if (RETURN_ERROR (Status)) {
    return Status;
  }
  OptionHeader.Header = (VOID *) &(PeHdr->Pe32.OptionalHeader);

  //
  // Retrieve the base address of the image
  //
  if (!(ImageContext->IsTeImage)) {
    if (PeHdr->Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
      ImageContext->ImageAddress = (PHYSICAL_ADDRESS) OptionHeader.Optional32->ImageBase;
    } else {
      ImageContext->ImageAddress = (PHYSICAL_ADDRESS) OptionHeader.Optional64->ImageBase;
    }
  } else {
    ImageContext->ImageAddress = (PHYSICAL_ADDRESS) (TeHdr->ImageBase + TeHdr->StrippedSize - sizeof (EFI_TE_IMAGE_HEADER));
  }
  //
  // Initialize the alternate destination address to 0 indicating that it
  // should not be used.
  //
  ImageContext->DestinationAddress = 0;

  //
  // Initialize the codeview pointer.
  //
  ImageContext->CodeView    = NULL;
  ImageContext->PdbPointer  = NULL;

  //
  // Three cases with regards to relocations:
  // - Image has base relocs, RELOCS_STRIPPED==0    => image is relocatable
  // - Image has no base relocs, RELOCS_STRIPPED==1 => Image is not relocatable
  // - Image has no base relocs, RELOCS_STRIPPED==0 => Image is relocatable but
  //   has no base relocs to apply
  // Obviously having base relocations with RELOCS_STRIPPED==1 is invalid.
  //
  // Look at the file header to determine if relocations have been stripped, and
  // save this info in the image context for later use.
  //
  if ((!(ImageContext->IsTeImage)) && ((PeHdr->Pe32.FileHeader.Characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED) != 0)) {
    ImageContext->RelocationsStripped = TRUE;
  } else if ((ImageContext->IsTeImage) && (TeHdr->DataDirectory[0].Size == 0) && (TeHdr->DataDirectory[0].VirtualAddress == 0)) {
    ImageContext->RelocationsStripped = TRUE;
  } else {
    ImageContext->RelocationsStripped = FALSE;
  }

  if (!(ImageContext->IsTeImage)) {

    if (PeHdr->Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
      ImageContext->ImageSize         = (UINT64) OptionHeader.Optional32->SizeOfImage;
      ImageContext->SectionAlignment  = OptionHeader.Optional32->SectionAlignment;
      ImageContext->SizeOfHeaders     = OptionHeader.Optional32->SizeOfHeaders;

      //
      // Modify ImageSize to contain .PDB file name if required and initialize
      // PdbRVA field...
      //
      if (OptionHeader.Optional32->NumberOfRvaAndSizes > EFI_IMAGE_DIRECTORY_ENTRY_DEBUG) {
        DebugDirectoryEntry = (EFI_IMAGE_DATA_DIRECTORY *) &(OptionHeader.Optional32->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_DEBUG]);
        DebugDirectoryEntryRva = DebugDirectoryEntry->VirtualAddress;
      }
    } else {
      ImageContext->ImageSize         = (UINT64) OptionHeader.Optional64->SizeOfImage;
      ImageContext->SectionAlignment  = OptionHeader.Optional64->SectionAlignment;
      ImageContext->SizeOfHeaders     = OptionHeader.Optional64->SizeOfHeaders;

      //
      // Modify ImageSize to contain .PDB file name if required and initialize
      // PdbRVA field...
      //
      if (OptionHeader.Optional64->NumberOfRvaAndSizes > EFI_IMAGE_DIRECTORY_ENTRY_DEBUG) {
        DebugDirectoryEntry = (EFI_IMAGE_DATA_DIRECTORY *) &(OptionHeader.Optional64->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_DEBUG]);
        DebugDirectoryEntryRva = DebugDirectoryEntry->VirtualAddress;
      }
    }

    if (DebugDirectoryEntryRva != 0) {
      //
      // Determine the file offset of the debug directory...  This means we walk
      // the sections to find which section contains the RVA of the debug
      // directory
      //
      DebugDirectoryEntryFileOffset = 0;

      SectionHeaderOffset = (UINTN)(
                               ImageContext->PeCoffHeaderOffset +
                               sizeof (UINT32) +
                               sizeof (EFI_IMAGE_FILE_HEADER) +
                               PeHdr->Pe32.FileHeader.SizeOfOptionalHeader
                               );

      for (Index = 0; Index < PeHdr->Pe32.FileHeader.NumberOfSections; Index++) {
        //
        // Read section header from file
        //
        Size = sizeof (EFI_IMAGE_SECTION_HEADER);
        Status = ImageContext->ImageRead (
                                 ImageContext->Handle,
                                 SectionHeaderOffset,
                                 &Size,
                                 &SectionHeader
                                 );
        if (RETURN_ERROR (Status)) {
          ImageContext->ImageError = IMAGE_ERROR_IMAGE_READ;
          return Status;
        }

        if (DebugDirectoryEntryRva >= SectionHeader.VirtualAddress &&
            DebugDirectoryEntryRva < SectionHeader.VirtualAddress + SectionHeader.Misc.VirtualSize) {
            DebugDirectoryEntryFileOffset =
            DebugDirectoryEntryRva - SectionHeader.VirtualAddress + SectionHeader.PointerToRawData;
          break;
        }

        SectionHeaderOffset += sizeof (EFI_IMAGE_SECTION_HEADER);
      }

      if (DebugDirectoryEntryFileOffset != 0) {
        for (Index = 0; Index < DebugDirectoryEntry->Size; Index += sizeof (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY)) {
          //
          // Read next debug directory entry
          //
          Size = sizeof (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY);
          Status = ImageContext->ImageRead (
                                   ImageContext->Handle,
                                   DebugDirectoryEntryFileOffset + Index,
                                   &Size,
                                   &DebugEntry
                                   );
          if (RETURN_ERROR (Status)) {
            ImageContext->ImageError = IMAGE_ERROR_IMAGE_READ;
            return Status;
          }

          if (DebugEntry.Type == EFI_IMAGE_DEBUG_TYPE_CODEVIEW) {
            ImageContext->DebugDirectoryEntryRva = (UINT32) (DebugDirectoryEntryRva + Index);
            if (DebugEntry.RVA == 0 && DebugEntry.FileOffset != 0) {
              ImageContext->ImageSize += DebugEntry.SizeOfData;
            }

            return RETURN_SUCCESS;
          }
        }
      }
    }
  } else {
    ImageContext->ImageSize         = 0;
    ImageContext->SectionAlignment  = 4096;
    ImageContext->SizeOfHeaders     = sizeof (EFI_TE_IMAGE_HEADER) + (UINTN) TeHdr->BaseOfCode - (UINTN) TeHdr->StrippedSize;

    DebugDirectoryEntry             = &TeHdr->DataDirectory[1];
    DebugDirectoryEntryRva          = DebugDirectoryEntry->VirtualAddress;
    SectionHeaderOffset             = (UINTN) (sizeof (EFI_TE_IMAGE_HEADER));

    DebugDirectoryEntryFileOffset   = 0;

    for (Index = 0; Index < TeHdr->NumberOfSections;) {
      //
      // Read section header from file
      //
      Size = sizeof (EFI_IMAGE_SECTION_HEADER);
      Status = ImageContext->ImageRead (
                               ImageContext->Handle,
                               SectionHeaderOffset,
                               &Size,
                               &SectionHeader
                               );
      if (RETURN_ERROR (Status)) {
        ImageContext->ImageError = IMAGE_ERROR_IMAGE_READ;
        return Status;
      }

      if (DebugDirectoryEntryRva >= SectionHeader.VirtualAddress &&
          DebugDirectoryEntryRva < SectionHeader.VirtualAddress + SectionHeader.Misc.VirtualSize) {
        DebugDirectoryEntryFileOffset = DebugDirectoryEntryRva -
          SectionHeader.VirtualAddress +
          SectionHeader.PointerToRawData +
          sizeof (EFI_TE_IMAGE_HEADER) -
          TeHdr->StrippedSize;

        //
        // File offset of the debug directory was found, if this is not the last
        // section, then skip to the last section for calculating the image size.
        //
        if (Index < (UINTN) TeHdr->NumberOfSections - 1) {
          SectionHeaderOffset += (TeHdr->NumberOfSections - 1 - Index) * sizeof (EFI_IMAGE_SECTION_HEADER);
          Index = TeHdr->NumberOfSections - 1;
          continue;
        }
      }

      //
      // In Te image header there is not a field to describe the ImageSize.
      // Actually, the ImageSize equals the RVA plus the VirtualSize of
      // the last section mapped into memory (Must be rounded up to
      // a multiple of Section Alignment). Per the PE/COFF specification, the
      // section headers in the Section Table must appear in order of the RVA
      // values for the corresponding sections. So the ImageSize can be determined
      // by the RVA and the VirtualSize of the last section header in the
      // Section Table.
      //
      if ((++Index) == (UINTN) TeHdr->NumberOfSections) {
        ImageContext->ImageSize = (SectionHeader.VirtualAddress + SectionHeader.Misc.VirtualSize +
                                   ImageContext->SectionAlignment - 1) & ~(ImageContext->SectionAlignment - 1);
      }

      SectionHeaderOffset += sizeof (EFI_IMAGE_SECTION_HEADER);
    }

    if (DebugDirectoryEntryFileOffset != 0) {
      for (Index = 0; Index < DebugDirectoryEntry->Size; Index += sizeof (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY)) {
        //
        // Read next debug directory entry
        //
        Size = sizeof (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY);
        Status = ImageContext->ImageRead (
                                 ImageContext->Handle,
                                 DebugDirectoryEntryFileOffset,
                                 &Size,
                                 &DebugEntry
                                 );
        if (RETURN_ERROR (Status)) {
          ImageContext->ImageError = IMAGE_ERROR_IMAGE_READ;
          return Status;
        }

        if (DebugEntry.Type == EFI_IMAGE_DEBUG_TYPE_CODEVIEW) {
          ImageContext->DebugDirectoryEntryRva = (UINT32) (DebugDirectoryEntryRva + Index);
          return RETURN_SUCCESS;
        }
      }
    }
  }

  return RETURN_SUCCESS;
}

/**
  Converts an image address to the loaded address

  @param ImageContext  The context of the image being loaded
  @param Address       The address to be converted to the loaded address

  @return NULL if the address can not be converted, otherwise, the converted address

--*/
STATIC
VOID *
PeCoffLoaderImageAddress (
  IN OUT PE_COFF_LOADER_IMAGE_CONTEXT          *ImageContext,
  IN     UINTN                                 Address
  )
{
  if (Address >= ImageContext->ImageSize) {
    ImageContext->ImageError = IMAGE_ERROR_INVALID_IMAGE_ADDRESS;
    return NULL;
  }

  return (UINT8 *) ((UINTN) ImageContext->ImageAddress + Address);
}

/**
  Relocates a PE/COFF image in memory

  @param This         Calling context
  @param ImageContext Contains information on the loaded image to relocate

  @retval RETURN_SUCCESS      if the PE/COFF image was relocated
  @retval RETURN_LOAD_ERROR   if the image is not a valid PE/COFF image
  @retval RETURN_UNSUPPORTED  not support

**/
RETURN_STATUS
EFIAPI
PeCoffLoaderRelocateImage (
  IN OUT PE_COFF_LOADER_IMAGE_CONTEXT  *ImageContext
  )
{
  RETURN_STATUS                         Status;
  EFI_IMAGE_OPTIONAL_HEADER_UNION       *PeHdr;
  EFI_TE_IMAGE_HEADER                   *TeHdr;
  EFI_IMAGE_DATA_DIRECTORY              *RelocDir;
  UINT64                                Adjust;
  EFI_IMAGE_BASE_RELOCATION             *RelocBase;
  EFI_IMAGE_BASE_RELOCATION             *RelocBaseEnd;
  UINT16                                *Reloc;
  UINT16                                *RelocEnd;
  CHAR8                                 *Fixup;
  CHAR8                                 *FixupBase;
  UINT16                                *F16;
  UINT32                                *F32;
  UINT64                                *F64;
  CHAR8                                 *FixupData;
  PHYSICAL_ADDRESS                      BaseAddress;
  UINT16                                MachineType;
  EFI_IMAGE_OPTIONAL_HEADER_POINTER     OptionHeader;

  PeHdr = NULL;
  TeHdr = NULL;
  //
  // Assume success
  //
  ImageContext->ImageError = IMAGE_ERROR_SUCCESS;

  //
  // If there are no relocation entries, then we are done
  //
  if (ImageContext->RelocationsStripped) {
    return RETURN_SUCCESS;
  }

  //
  // Use DestinationAddress field of ImageContext as the relocation address even if it is 0.
  //
  BaseAddress = ImageContext->DestinationAddress;

  if (!(ImageContext->IsTeImage)) {
    PeHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)((UINTN)ImageContext->ImageAddress +
                                            ImageContext->PeCoffHeaderOffset);
    OptionHeader.Header = (VOID *) &(PeHdr->Pe32.OptionalHeader);
    if (PeHdr->Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
      Adjust = (UINT64) BaseAddress - OptionHeader.Optional32->ImageBase;
      OptionHeader.Optional32->ImageBase = (UINT32) BaseAddress;
      MachineType = ImageContext->Machine;
      //
      // Find the relocation block
      //
      // Per the PE/COFF spec, you can't assume that a given data directory
      // is present in the image. You have to check the NumberOfRvaAndSizes in
      // the optional header to verify a desired directory entry is there.
      //
      if (OptionHeader.Optional32->NumberOfRvaAndSizes > EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC) {
        RelocDir  = &OptionHeader.Optional32->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC];
        if ((RelocDir != NULL) && (RelocDir->Size > 0)) {
          RelocBase = PeCoffLoaderImageAddress (ImageContext, RelocDir->VirtualAddress);
          RelocBaseEnd = PeCoffLoaderImageAddress (
                           ImageContext,
                           RelocDir->VirtualAddress + RelocDir->Size - 1
                           );
          if (RelocBase == NULL || RelocBaseEnd == NULL || RelocBaseEnd < RelocBase) {
            ImageContext->ImageError = IMAGE_ERROR_FAILED_RELOCATION;
            return RETURN_LOAD_ERROR;
          }
        } else {
          //
          // Set base and end to bypass processing below.
          //
          RelocBase = RelocBaseEnd = 0;
        }
      } else {
        //
        // Set base and end to bypass processing below.
        //
        RelocBase = RelocBaseEnd = 0;
      }
    } else {
      Adjust = (UINT64) BaseAddress - OptionHeader.Optional64->ImageBase;
      OptionHeader.Optional64->ImageBase = BaseAddress;
      MachineType = ImageContext->Machine;
      //
      // Find the relocation block
      //
      // Per the PE/COFF spec, you can't assume that a given data directory
      // is present in the image. You have to check the NumberOfRvaAndSizes in
      // the optional header to verify a desired directory entry is there.
      //
      if (OptionHeader.Optional64->NumberOfRvaAndSizes > EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC) {
        RelocDir  = &OptionHeader.Optional64->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC];
        if ((RelocDir != NULL) && (RelocDir->Size > 0)) {
          RelocBase = PeCoffLoaderImageAddress (ImageContext, RelocDir->VirtualAddress);
          RelocBaseEnd = PeCoffLoaderImageAddress (
                           ImageContext,
                           RelocDir->VirtualAddress + RelocDir->Size - 1
                          );
          if (RelocBase == NULL || RelocBaseEnd == NULL || RelocBaseEnd < RelocBase) {
            ImageContext->ImageError = IMAGE_ERROR_FAILED_RELOCATION;
            return RETURN_LOAD_ERROR;
          }
        } else {
          //
          // Set base and end to bypass processing below.
          //
          RelocBase = RelocBaseEnd = 0;
        }
      } else {
        //
        // Set base and end to bypass processing below.
        //
        RelocBase = RelocBaseEnd = 0;
      }
    }
  } else {
    TeHdr             = (EFI_TE_IMAGE_HEADER *) (UINTN) (ImageContext->ImageAddress);
    Adjust            = (UINT64) (BaseAddress - TeHdr->ImageBase);
    TeHdr->ImageBase  = (UINT64) (BaseAddress);
    MachineType = TeHdr->Machine;

    //
    // Find the relocation block
    //
    RelocDir = &TeHdr->DataDirectory[0];
    RelocBase = (EFI_IMAGE_BASE_RELOCATION *)(UINTN)(
                                    ImageContext->ImageAddress +
                                    RelocDir->VirtualAddress +
                                    sizeof(EFI_TE_IMAGE_HEADER) -
                                    TeHdr->StrippedSize
                                    );
    RelocBaseEnd = (EFI_IMAGE_BASE_RELOCATION *) ((UINTN) RelocBase + (UINTN) RelocDir->Size - 1);
  }

  //
  // Run the relocation information and apply the fixups
  //
  FixupData = ImageContext->FixupData;
  while (RelocBase < RelocBaseEnd) {

    Reloc     = (UINT16 *) ((CHAR8 *) RelocBase + sizeof (EFI_IMAGE_BASE_RELOCATION));
    RelocEnd  = (UINT16 *) ((CHAR8 *) RelocBase + RelocBase->SizeOfBlock);
    if (!(ImageContext->IsTeImage)) {
      FixupBase = PeCoffLoaderImageAddress (ImageContext, RelocBase->VirtualAddress);
      if (FixupBase == NULL) {
        ImageContext->ImageError = IMAGE_ERROR_FAILED_RELOCATION;
        return RETURN_LOAD_ERROR;
      }
    } else {
      FixupBase = (CHAR8 *)(UINTN)(ImageContext->ImageAddress +
                    RelocBase->VirtualAddress +
                    sizeof(EFI_TE_IMAGE_HEADER) -
                    TeHdr->StrippedSize
                    );
    }

    if ((CHAR8 *) RelocEnd < (CHAR8 *) ((UINTN) ImageContext->ImageAddress) ||
        (CHAR8 *) RelocEnd > (CHAR8 *)((UINTN)ImageContext->ImageAddress +
          (UINTN)ImageContext->ImageSize)) {
      ImageContext->ImageError = IMAGE_ERROR_FAILED_RELOCATION;
      return RETURN_LOAD_ERROR;
    }

    //
    // Run this relocation record
    //
    while (Reloc < RelocEnd) {

      Fixup = FixupBase + (*Reloc & 0xFFF);
      switch ((*Reloc) >> 12) {
      case EFI_IMAGE_REL_BASED_ABSOLUTE:
        break;

      case EFI_IMAGE_REL_BASED_HIGH:
        F16   = (UINT16 *) Fixup;
        *F16 = (UINT16) (*F16 + ((UINT16) ((UINT32) Adjust >> 16)));
        if (FixupData != NULL) {
          *(UINT16 *) FixupData = *F16;
          FixupData             = FixupData + sizeof (UINT16);
        }
        break;

      case EFI_IMAGE_REL_BASED_LOW:
        F16   = (UINT16 *) Fixup;
        *F16  = (UINT16) (*F16 + (UINT16) Adjust);
        if (FixupData != NULL) {
          *(UINT16 *) FixupData = *F16;
          FixupData             = FixupData + sizeof (UINT16);
        }
        break;

      case EFI_IMAGE_REL_BASED_HIGHLOW:
        F32   = (UINT32 *) Fixup;
        *F32  = *F32 + (UINT32) Adjust;
        if (FixupData != NULL) {
          FixupData             = ALIGN_POINTER (FixupData, sizeof (UINT32));
          *(UINT32 *) FixupData = *F32;
          FixupData             = FixupData + sizeof (UINT32);
        }
        break;

      case EFI_IMAGE_REL_BASED_DIR64:
        F64   = (UINT64 *) Fixup;
        *F64  = *F64 + (UINT64) Adjust;
        if (FixupData != NULL) {
          FixupData             = ALIGN_POINTER (FixupData, sizeof (UINT64));
          *(UINT64 *) FixupData = *F64;
          FixupData             = FixupData + sizeof (UINT64);
        }
        break;

      case EFI_IMAGE_REL_BASED_HIGHADJ:
        //
        // Return the same EFI_UNSUPPORTED return code as
        // PeCoffLoaderRelocateImageEx() returns if it does not recognize
        // the relocation type.
        //
        ImageContext->ImageError = IMAGE_ERROR_FAILED_RELOCATION;
        return RETURN_UNSUPPORTED;

      default:
        switch (MachineType) {
        case IMAGE_FILE_MACHINE_I386:
          Status = PeCoffLoaderRelocateIa32Image (Reloc, Fixup, &FixupData, Adjust);
          break;
        case IMAGE_FILE_MACHINE_ARMTHUMB_MIXED:
          Status = PeCoffLoaderRelocateArmImage (&Reloc, Fixup, &FixupData, Adjust);
          break;
        case IMAGE_FILE_MACHINE_RISCV64:
          Status = PeCoffLoaderRelocateRiscVImage (Reloc, Fixup, &FixupData, Adjust);
          break;
        case IMAGE_FILE_MACHINE_LOONGARCH64:
          Status = PeCoffLoaderRelocateLoongArch64Image (Reloc, Fixup, &FixupData, Adjust);
          break;
        default:
          Status = RETURN_UNSUPPORTED;
          break;
        }
        if (RETURN_ERROR (Status)) {
          ImageContext->ImageError = IMAGE_ERROR_FAILED_RELOCATION;
          return Status;
        }
      }

      //
      // Next relocation record
      //
      Reloc += 1;
    }

    //
    // Next reloc block
    //
    RelocBase = (EFI_IMAGE_BASE_RELOCATION *) RelocEnd;
  }

  return RETURN_SUCCESS;
}

/**
  Loads a PE/COFF image into memory

  @param This         Calling context
  @param ImageContext Contains information on image to load into memory

  @retval RETURN_SUCCESS            if the PE/COFF image was loaded
  @retval RETURN_BUFFER_TOO_SMALL   if the caller did not provide a large enough buffer
  @retval RETURN_LOAD_ERROR         if the image is a runtime driver with no relocations
  @retval RETURN_INVALID_PARAMETER  if the image address is invalid

**/
RETURN_STATUS
EFIAPI
PeCoffLoaderLoadImage (
  IN OUT PE_COFF_LOADER_IMAGE_CONTEXT  *ImageContext
  )
{
  RETURN_STATUS                         Status;
  EFI_IMAGE_OPTIONAL_HEADER_UNION       *PeHdr;
  EFI_TE_IMAGE_HEADER                   *TeHdr;
  PE_COFF_LOADER_IMAGE_CONTEXT          CheckContext;
  EFI_IMAGE_SECTION_HEADER              *FirstSection;
  EFI_IMAGE_SECTION_HEADER              *Section;
  UINTN                                 NumberOfSections;
  UINTN                                 Index;
  CHAR8                                 *Base;
  CHAR8                                 *End;
  CHAR8                                 *MaxEnd;
  EFI_IMAGE_DATA_DIRECTORY              *DirectoryEntry;
  EFI_IMAGE_DEBUG_DIRECTORY_ENTRY       *DebugEntry;
  UINTN                                 Size;
  UINT32                                TempDebugEntryRva;
  EFI_IMAGE_OPTIONAL_HEADER_POINTER     OptionHeader;

  PeHdr = NULL;
  TeHdr = NULL;
  OptionHeader.Header = NULL;
  //
  // Assume success
  //
  ImageContext->ImageError = IMAGE_ERROR_SUCCESS;

  //
  // Copy the provided context info into our local version, get what we
  // can from the original image, and then use that to make sure everything
  // is legit.
  //
  CopyMem (&CheckContext, ImageContext, sizeof (PE_COFF_LOADER_IMAGE_CONTEXT));

  Status = PeCoffLoaderGetImageInfo (&CheckContext);
  if (RETURN_ERROR (Status)) {
    return Status;
  }

  //
  // Make sure there is enough allocated space for the image being loaded
  //
  if (ImageContext->ImageSize < CheckContext.ImageSize) {
    ImageContext->ImageError = IMAGE_ERROR_INVALID_IMAGE_SIZE;
    return RETURN_BUFFER_TOO_SMALL;
  }

  //
  // If there's no relocations, then make sure it's not a runtime driver,
  // and that it's being loaded at the linked address.
  //
  if (CheckContext.RelocationsStripped) {
    //
    // If the image does not contain relocations and it is a runtime driver
    // then return an error.
    //
    if (CheckContext.ImageType == EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER) {
      ImageContext->ImageError = IMAGE_ERROR_INVALID_SUBSYSTEM;
      return RETURN_LOAD_ERROR;
    }
    //
    // If the image does not contain relocations, and the requested load address
    // is not the linked address, then return an error.
    //
    if (CheckContext.ImageAddress != ImageContext->ImageAddress) {
      ImageContext->ImageError = IMAGE_ERROR_INVALID_IMAGE_ADDRESS;
      return RETURN_INVALID_PARAMETER;
    }
  }
  //
  // Make sure the allocated space has the proper section alignment
  //
  if (!(ImageContext->IsTeImage)) {
    if ((ImageContext->ImageAddress & (CheckContext.SectionAlignment - 1)) != 0) {
      ImageContext->ImageError = IMAGE_ERROR_INVALID_SECTION_ALIGNMENT;
      return RETURN_INVALID_PARAMETER;
    }
  }
  //
  // Read the entire PE/COFF or TE header into memory
  //
  if (!(ImageContext->IsTeImage)) {
    Status = ImageContext->ImageRead (
                            ImageContext->Handle,
                            0,
                            &ImageContext->SizeOfHeaders,
                            (VOID *) (UINTN) ImageContext->ImageAddress
                            );

    PeHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)
      ((UINTN)ImageContext->ImageAddress + ImageContext->PeCoffHeaderOffset);

    OptionHeader.Header = (VOID *) &(PeHdr->Pe32.OptionalHeader);

    FirstSection = (EFI_IMAGE_SECTION_HEADER *) (
                      (UINTN)ImageContext->ImageAddress +
                      ImageContext->PeCoffHeaderOffset +
                      sizeof(UINT32) +
                      sizeof(EFI_IMAGE_FILE_HEADER) +
                      PeHdr->Pe32.FileHeader.SizeOfOptionalHeader
      );
    NumberOfSections = (UINTN) (PeHdr->Pe32.FileHeader.NumberOfSections);
  } else {
    Status = ImageContext->ImageRead (
                            ImageContext->Handle,
                            0,
                            &ImageContext->SizeOfHeaders,
                            (VOID *) (UINTN) ImageContext->ImageAddress
                            );

    TeHdr             = (EFI_TE_IMAGE_HEADER *) (UINTN) (ImageContext->ImageAddress);

    FirstSection = (EFI_IMAGE_SECTION_HEADER *) (
          (UINTN)ImageContext->ImageAddress +
          sizeof(EFI_TE_IMAGE_HEADER)
          );
    NumberOfSections  = (UINTN) (TeHdr->NumberOfSections);

  }

  if (RETURN_ERROR (Status)) {
    ImageContext->ImageError = IMAGE_ERROR_IMAGE_READ;
    return RETURN_LOAD_ERROR;
  }

  //
  // Load each section of the image
  //
  Section = FirstSection;
  for (Index = 0, MaxEnd = NULL; Index < NumberOfSections; Index++) {

    //
    // Compute sections address
    //
    Base = PeCoffLoaderImageAddress (ImageContext, Section->VirtualAddress);
    End = PeCoffLoaderImageAddress (
            ImageContext,
            Section->VirtualAddress + Section->Misc.VirtualSize - 1
            );

    //
    // If the base start or end address resolved to 0, then fail.
    //
    if ((Base == NULL) || (End == NULL)) {
      ImageContext->ImageError = IMAGE_ERROR_SECTION_NOT_LOADED;
      return RETURN_LOAD_ERROR;
    }


    if (ImageContext->IsTeImage) {
      Base  = (CHAR8 *) ((UINTN) Base + sizeof (EFI_TE_IMAGE_HEADER) - (UINTN) TeHdr->StrippedSize);
      End   = (CHAR8 *) ((UINTN) End + sizeof (EFI_TE_IMAGE_HEADER) - (UINTN) TeHdr->StrippedSize);
    }

    if (End > MaxEnd) {
      MaxEnd = End;
    }

    //
    // Read the section
    //
    Size = (UINTN) Section->Misc.VirtualSize;
    if ((Size == 0) || (Size > Section->SizeOfRawData)) {
      Size = (UINTN) Section->SizeOfRawData;
    }

    if (Section->SizeOfRawData) {
      if (!(ImageContext->IsTeImage)) {
        Status = ImageContext->ImageRead (
                                ImageContext->Handle,
                                Section->PointerToRawData,
                                &Size,
                                Base
                                );
      } else {
        Status = ImageContext->ImageRead (
                                ImageContext->Handle,
                                Section->PointerToRawData + sizeof (EFI_TE_IMAGE_HEADER) - (UINTN) TeHdr->StrippedSize,
                                &Size,
                                Base
                                );
      }

      if (RETURN_ERROR (Status)) {
        ImageContext->ImageError = IMAGE_ERROR_IMAGE_READ;
        return Status;
      }
    }

    //
    // If raw size is less then virt size, zero fill the remaining
    //

    if (Size < Section->Misc.VirtualSize) {
      ZeroMem (Base + Size, Section->Misc.VirtualSize - Size);
    }

    //
    // Next Section
    //
    Section += 1;
  }

  //
  // Get image's entry point
  //
  if (!(ImageContext->IsTeImage)) {
    ImageContext->EntryPoint = (PHYSICAL_ADDRESS) (UINTN) PeCoffLoaderImageAddress (
                                                                ImageContext,
                                                                PeHdr->Pe32.OptionalHeader.AddressOfEntryPoint
                                                                );
  } else {
    ImageContext->EntryPoint = (UINTN)ImageContext->ImageAddress +
                               (UINTN)TeHdr->AddressOfEntryPoint +
                               (UINTN)sizeof(EFI_TE_IMAGE_HEADER) -
                               (UINTN) TeHdr->StrippedSize;
  }

  //
  // Determine the size of the fixup data
  //
  // Per the PE/COFF spec, you can't assume that a given data directory
  // is present in the image. You have to check the NumberOfRvaAndSizes in
  // the optional header to verify a desired directory entry is there.
  //
  if (!(ImageContext->IsTeImage)) {
    if (PeHdr->Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
      if (OptionHeader.Optional32->NumberOfRvaAndSizes > EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC) {
        DirectoryEntry = (EFI_IMAGE_DATA_DIRECTORY *)
          &OptionHeader.Optional32->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC];
        ImageContext->FixupDataSize = DirectoryEntry->Size / sizeof (UINT16) * sizeof (UINTN);
      } else {
        ImageContext->FixupDataSize = 0;
      }
    } else {
      if (OptionHeader.Optional64->NumberOfRvaAndSizes > EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC) {
        DirectoryEntry = (EFI_IMAGE_DATA_DIRECTORY *)
          &OptionHeader.Optional64->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC];
        ImageContext->FixupDataSize = DirectoryEntry->Size / sizeof (UINT16) * sizeof (UINTN);
      } else {
        ImageContext->FixupDataSize = 0;
      }
    }
  } else {
    DirectoryEntry              = &TeHdr->DataDirectory[0];
    ImageContext->FixupDataSize = DirectoryEntry->Size / sizeof (UINT16) * sizeof (UINTN);
  }
  //
  // Consumer must allocate a buffer for the relocation fixup log.
  // Only used for runtime drivers.
  //
  ImageContext->FixupData = NULL;

  //
  // Load the Codeview info if present
  //
  if (ImageContext->DebugDirectoryEntryRva != 0) {
    if (!(ImageContext->IsTeImage)) {
      DebugEntry = PeCoffLoaderImageAddress (
                    ImageContext,
                    ImageContext->DebugDirectoryEntryRva
                    );
    } else {
      DebugEntry = (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY *)(UINTN)(
                                               ImageContext->ImageAddress +
                                               ImageContext->DebugDirectoryEntryRva +
                                               sizeof(EFI_TE_IMAGE_HEADER) -
                                               TeHdr->StrippedSize
                                               );
    }

    if (DebugEntry != NULL) {
      TempDebugEntryRva = DebugEntry->RVA;
      if (DebugEntry->RVA == 0 && DebugEntry->FileOffset != 0) {
        Section--;
        if ((UINTN) Section->SizeOfRawData < Section->Misc.VirtualSize) {
          TempDebugEntryRva = Section->VirtualAddress + Section->Misc.VirtualSize;
        } else {
          TempDebugEntryRva = Section->VirtualAddress + Section->SizeOfRawData;
        }
      }

      if (TempDebugEntryRva != 0) {
        if (!(ImageContext->IsTeImage)) {
          ImageContext->CodeView = PeCoffLoaderImageAddress (ImageContext, TempDebugEntryRva);
        } else {
          ImageContext->CodeView = (VOID *)(
                      (UINTN)ImageContext->ImageAddress +
                      (UINTN)TempDebugEntryRva +
                      (UINTN)sizeof(EFI_TE_IMAGE_HEADER) -
                (UINTN) TeHdr->StrippedSize
            );
        }

        if (ImageContext->CodeView == NULL) {
          ImageContext->ImageError = IMAGE_ERROR_IMAGE_READ;
          return RETURN_LOAD_ERROR;
        }

        if (DebugEntry->RVA == 0) {
          Size = DebugEntry->SizeOfData;
          if (!(ImageContext->IsTeImage)) {
            Status = ImageContext->ImageRead (
                                    ImageContext->Handle,
                                    DebugEntry->FileOffset,
                                    &Size,
                                    ImageContext->CodeView
                                    );
          } else {
            Status = ImageContext->ImageRead (
                                    ImageContext->Handle,
                                    DebugEntry->FileOffset + sizeof (EFI_TE_IMAGE_HEADER) - TeHdr->StrippedSize,
                                    &Size,
                                    ImageContext->CodeView
                                    );
            //
            // Should we apply fix up to this field according to the size difference between PE and TE?
            // Because now we maintain TE header fields unfixed, this field will also remain as they are
            // in original PE image.
            //
          }

          if (RETURN_ERROR (Status)) {
            ImageContext->ImageError = IMAGE_ERROR_IMAGE_READ;
            return RETURN_LOAD_ERROR;
          }

          DebugEntry->RVA = TempDebugEntryRva;
        }

        switch (*(UINT32 *) ImageContext->CodeView) {
        case CODEVIEW_SIGNATURE_NB10:
          ImageContext->PdbPointer = (CHAR8 *) ImageContext->CodeView + sizeof (EFI_IMAGE_DEBUG_CODEVIEW_NB10_ENTRY);
          break;

        case CODEVIEW_SIGNATURE_RSDS:
          ImageContext->PdbPointer = (CHAR8 *) ImageContext->CodeView + sizeof (EFI_IMAGE_DEBUG_CODEVIEW_RSDS_ENTRY);
          break;

        case CODEVIEW_SIGNATURE_MTOC:
          ImageContext->PdbPointer = (CHAR8 *) ImageContext->CodeView + sizeof (EFI_IMAGE_DEBUG_CODEVIEW_MTOC_ENTRY);

        default:
          break;
        }
      }
    }
  }

  return Status;
}

/**
  Returns a pointer to the PDB file name for a raw PE/COFF image that is not
  loaded into system memory with the PE/COFF Loader Library functions.

  Returns the PDB file name for the PE/COFF image specified by Pe32Data.  If
  the PE/COFF image specified by Pe32Data is not a valid, then NULL is
  returned.  If the PE/COFF image specified by Pe32Data does not contain a
  debug directory entry, then NULL is returned.  If the debug directory entry
  in the PE/COFF image specified by Pe32Data does not contain a PDB file name,
  then NULL is returned.
  If Pe32Data is NULL, then return NULL.

  @param  Pe32Data   Pointer to the PE/COFF image that is loaded in system
                     memory.

  @return The PDB file name for the PE/COFF image specified by Pe32Data or NULL
          if it cannot be retrieved.

**/
VOID *
EFIAPI
PeCoffLoaderGetPdbPointer (
  IN VOID  *Pe32Data
  )
{
  EFI_IMAGE_DOS_HEADER                  *DosHdr;
  EFI_IMAGE_OPTIONAL_HEADER_PTR_UNION   Hdr;
  EFI_IMAGE_DATA_DIRECTORY              *DirectoryEntry;
  EFI_IMAGE_DEBUG_DIRECTORY_ENTRY       *DebugEntry;
  UINTN                                 DirCount;
  VOID                                  *CodeViewEntryPointer;
  INTN                                  TEImageAdjust;
  UINT32                                NumberOfRvaAndSizes;
  UINT16                                Magic;
  EFI_IMAGE_SECTION_HEADER              *SectionHeader;
  UINT32                                Index, Index1;

  if (Pe32Data == NULL) {
    return NULL;
  }

  TEImageAdjust       = 0;
  DirectoryEntry      = NULL;
  DebugEntry          = NULL;
  NumberOfRvaAndSizes = 0;
  Index               = 0;
  Index1              = 0;
  SectionHeader       = NULL;

  DosHdr = (EFI_IMAGE_DOS_HEADER *)Pe32Data;
  if (EFI_IMAGE_DOS_SIGNATURE == DosHdr->e_magic) {
    //
    // DOS image header is present, so read the PE header after the DOS image header.
    //
    Hdr.Pe32 = (EFI_IMAGE_NT_HEADERS32 *)((UINTN) Pe32Data + (UINTN) ((DosHdr->e_lfanew) & 0x0ffff));
  } else {
    //
    // DOS image header is not present, so PE header is at the image base.
    //
    Hdr.Pe32 = (EFI_IMAGE_NT_HEADERS32 *)Pe32Data;
  }

  if (EFI_TE_IMAGE_HEADER_SIGNATURE == Hdr.Te->Signature) {
    if (Hdr.Te->DataDirectory[EFI_TE_IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress != 0) {
      DirectoryEntry  = &Hdr.Te->DataDirectory[EFI_TE_IMAGE_DIRECTORY_ENTRY_DEBUG];
      TEImageAdjust   = sizeof (EFI_TE_IMAGE_HEADER) - Hdr.Te->StrippedSize;

      //
      // Get the DebugEntry offset in the raw data image.
      //
      SectionHeader = (EFI_IMAGE_SECTION_HEADER *) (Hdr.Te + 1);
      Index = Hdr.Te->NumberOfSections;
      for (Index1 = 0; Index1 < Index; Index1 ++) {
        if ((DirectoryEntry->VirtualAddress >= SectionHeader[Index1].VirtualAddress) &&
           (DirectoryEntry->VirtualAddress < (SectionHeader[Index1].VirtualAddress + SectionHeader[Index1].Misc.VirtualSize))) {
          DebugEntry = (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY *)((UINTN) Hdr.Te +
                        DirectoryEntry->VirtualAddress -
                        SectionHeader [Index1].VirtualAddress +
                        SectionHeader [Index1].PointerToRawData +
                        TEImageAdjust);
          break;
        }
      }
    }
  } else if (EFI_IMAGE_NT_SIGNATURE == Hdr.Pe32->Signature) {
    //
    // NOTE: We use Machine field to identify PE32/PE32+, instead of Magic.
    //       It is due to backward-compatibility, for some system might
    //       generate PE32+ image with PE32 Magic.
    //
    switch (Hdr.Pe32->FileHeader.Machine) {
    case IMAGE_FILE_MACHINE_I386:
    case IMAGE_FILE_MACHINE_ARMTHUMB_MIXED:
      //
      // Assume PE32 image with IA32 Machine field.
      //
      Magic = EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC;
      break;
    case IMAGE_FILE_MACHINE_X64:
      //
      // Assume PE32+ image with X64 Machine field
      //
      Magic = EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC;
      break;
    default:
      //
      // For unknown Machine field, use Magic in optional Header
      //
      Magic = Hdr.Pe32->OptionalHeader.Magic;
    }

    SectionHeader = (EFI_IMAGE_SECTION_HEADER *) (
                       (UINT8 *) Hdr.Pe32 +
                       sizeof (UINT32) +
                       sizeof (EFI_IMAGE_FILE_HEADER) +
                       Hdr.Pe32->FileHeader.SizeOfOptionalHeader
                       );
    Index = Hdr.Pe32->FileHeader.NumberOfSections;

    if (EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC == Magic) {
      //
      // Use PE32 offset get Debug Directory Entry
      //
      NumberOfRvaAndSizes = Hdr.Pe32->OptionalHeader.NumberOfRvaAndSizes;
      DirectoryEntry = (EFI_IMAGE_DATA_DIRECTORY *)&(Hdr.Pe32->OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_DEBUG]);
    } else if (Hdr.Pe32->OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
      //
      // Use PE32+ offset get Debug Directory Entry
      //
      NumberOfRvaAndSizes = Hdr.Pe32Plus->OptionalHeader.NumberOfRvaAndSizes;
      DirectoryEntry = (EFI_IMAGE_DATA_DIRECTORY *)&(Hdr.Pe32Plus->OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_DEBUG]);
    }

    if (NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY_DEBUG || DirectoryEntry->VirtualAddress == 0) {
      DirectoryEntry = NULL;
      DebugEntry = NULL;
    } else {
      //
      // Get the DebugEntry offset in the raw data image.
      //
      for (Index1 = 0; Index1 < Index; Index1 ++) {
        if ((DirectoryEntry->VirtualAddress >= SectionHeader[Index1].VirtualAddress) &&
           (DirectoryEntry->VirtualAddress < (SectionHeader[Index1].VirtualAddress + SectionHeader[Index1].Misc.VirtualSize))) {
          DebugEntry = (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY *) (
                       (UINTN) Pe32Data +
                       DirectoryEntry->VirtualAddress -
                       SectionHeader[Index1].VirtualAddress +
                       SectionHeader[Index1].PointerToRawData);
          break;
        }
      }
    }
  } else {
    return NULL;
  }

  if (NULL == DebugEntry || NULL == DirectoryEntry) {
    return NULL;
  }

  //
  // Scan the directory to find the debug entry.
  //
  for (DirCount = 0; DirCount < DirectoryEntry->Size; DirCount += sizeof (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY), DebugEntry++) {
    if (EFI_IMAGE_DEBUG_TYPE_CODEVIEW == DebugEntry->Type) {
      if (DebugEntry->SizeOfData > 0) {
        //
        // Get the DebugEntry offset in the raw data image.
        //
        CodeViewEntryPointer = NULL;
        for (Index1 = 0; Index1 < Index; Index1 ++) {
          if ((DebugEntry->RVA >= SectionHeader[Index1].VirtualAddress) &&
             (DebugEntry->RVA < (SectionHeader[Index1].VirtualAddress + SectionHeader[Index1].Misc.VirtualSize))) {
            CodeViewEntryPointer = (VOID *) (
                                   ((UINTN)Pe32Data) +
                                   (UINTN) DebugEntry->RVA -
                                   SectionHeader[Index1].VirtualAddress +
                                   SectionHeader[Index1].PointerToRawData +
                                   (UINTN)TEImageAdjust);
            break;
          }
        }
        if (Index1 >= Index) {
          //
          // Can't find CodeViewEntryPointer in raw PE/COFF image.
          //
          continue;
        }
        switch (* (UINT32 *) CodeViewEntryPointer) {
        case CODEVIEW_SIGNATURE_NB10:
          return (VOID *) ((CHAR8 *)CodeViewEntryPointer + sizeof (EFI_IMAGE_DEBUG_CODEVIEW_NB10_ENTRY));
        case CODEVIEW_SIGNATURE_RSDS:
          return (VOID *) ((CHAR8 *)CodeViewEntryPointer + sizeof (EFI_IMAGE_DEBUG_CODEVIEW_RSDS_ENTRY));
        case CODEVIEW_SIGNATURE_MTOC:
          return (VOID *) ((CHAR8 *)CodeViewEntryPointer + sizeof (EFI_IMAGE_DEBUG_CODEVIEW_MTOC_ENTRY));
        default:
          break;
        }
      }
    }
  }

  return NULL;
}


RETURN_STATUS
EFIAPI
PeCoffLoaderGetEntryPoint (
  IN  VOID  *Pe32Data,
  OUT VOID  **EntryPoint,
  OUT VOID  **BaseOfImage
  )
{
  EFI_IMAGE_DOS_HEADER                  *DosHdr;
  EFI_IMAGE_OPTIONAL_HEADER_PTR_UNION   Hdr;

  DosHdr = (EFI_IMAGE_DOS_HEADER *)Pe32Data;
  if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE) {
    //
    // DOS image header is present, so read the PE header after the DOS image header.
    //
    Hdr.Pe32 = (EFI_IMAGE_NT_HEADERS32 *)((UINTN) Pe32Data + (UINTN) ((DosHdr->e_lfanew) & 0x0ffff));
  } else {
    //
    // DOS image header is not present, so PE header is at the image base.
    //
    Hdr.Pe32 = (EFI_IMAGE_NT_HEADERS32 *)Pe32Data;
  }

  //
  // Calculate the entry point relative to the start of the image.
  // AddressOfEntryPoint is common for PE32 & PE32+
  //
  if (Hdr.Te->Signature == EFI_TE_IMAGE_HEADER_SIGNATURE) {
    *BaseOfImage = (VOID *)(UINTN)(Hdr.Te->ImageBase + Hdr.Te->StrippedSize - sizeof (EFI_TE_IMAGE_HEADER));
    *EntryPoint = (VOID *)((UINTN)*BaseOfImage + (Hdr.Te->AddressOfEntryPoint & 0x0ffffffff) + sizeof(EFI_TE_IMAGE_HEADER) - Hdr.Te->StrippedSize);
    return RETURN_SUCCESS;
  } else if (Hdr.Pe32->Signature == EFI_IMAGE_NT_SIGNATURE) {
    *EntryPoint = (VOID *)(UINTN)Hdr.Pe32->OptionalHeader.AddressOfEntryPoint;
    if (Hdr.Pe32->OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
      *BaseOfImage = (VOID *)(UINTN)Hdr.Pe32->OptionalHeader.ImageBase;
    } else {
      *BaseOfImage = (VOID *)(UINTN)Hdr.Pe32Plus->OptionalHeader.ImageBase;
    }
    *EntryPoint = (VOID *)(UINTN)((UINTN)*EntryPoint + (UINTN)*BaseOfImage);
    return RETURN_SUCCESS;
  }

  return RETURN_UNSUPPORTED;
}
