/** @file
  Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
  SPDX-License-Identifier: BSD-3-Clause
**/

#include "ImageTool.h"

#include "ElfScanCommon.h"

#include "../../UefiPayloadPkg/PayloadLoaderPeim/ElfLib/ElfCommon.h"

#if ELF_ARCH == 32

#include "../../UefiPayloadPkg/PayloadLoaderPeim/ElfLib/Elf32.h"

#define ELFCLASS    ELFCLASS32
#define Elf_Ehdr    Elf32_Ehdr
#define Elf_Shdr    Elf32_Shdr
#define Elf_Sym     Elf32_Sym
#define Elf_Rel     Elf32_Rel
#define Elf_Rela    Elf32_Rela
#define Elf_Size    Elf32_Size
#define Elf_Addr    Elf32_Addr
#define ELF_R_TYPE  ELF32_R_TYPE
#define ELF_R_SYM   ELF32_R_SYM

#elif ELF_ARCH == 64

#include "../../UefiPayloadPkg/PayloadLoaderPeim/ElfLib/Elf64.h"

#define ELFCLASS    ELFCLASS64
#define Elf_Ehdr    Elf64_Ehdr
#define Elf_Shdr    Elf64_Shdr
#define Elf_Sym     Elf64_Sym
#define Elf_Rel     Elf64_Rel
#define Elf_Rela    Elf64_Rela
#define Elf_Size    Elf64_Size
#define Elf_Addr    Elf64_Addr
#define ELF_R_TYPE  ELF64_R_TYPE
#define ELF_R_SYM   ELF64_R_SYM

#endif

#define ELF_SUFFIX__(Name, Arch)  Name##Arch
#define ELF_SUFFIX_(Name, Arch)   ELF_SUFFIX__ (Name, Arch)
#define ELF_SUFFIX(Name)          ELF_SUFFIX_ (Name, ELF_ARCH)

typedef struct {
  const Elf_Ehdr  *Ehdr;
  uint32_t        Alignment;
  Elf_Addr        BaseAddress;
  bool            HasPieRelocs;
} tool_elf_scan_context;


#define GetShrbyIndex  ELF_SUFFIX(GetShrbyIndex)
static
Elf_Shdr *
GetShdrByIndex (
  IN const Elf_Ehdr  *Ehdr,
  IN UINT32          Index
  )
{
  UINTN Offset;

  assert (Index < Ehdr->e_shnum);

  Offset = (UINTN)Ehdr->e_shoff + Index * Ehdr->e_shentsize;

  return (Elf_Shdr *)((UINT8 *)Ehdr + Offset);
}

#define GetString  ELF_SUFFIX(GetString)
static
const char *
GetString (
  IN const Elf_Ehdr  *Ehdr,
  IN UINT32          Offset,
  IN UINT32          Index
  )
{
  const Elf_Shdr *Shdr;

  if (Index == 0) {
    Shdr = GetShdrByIndex (Ehdr, Ehdr->e_shstrndx);
  } else {
    Shdr = GetShdrByIndex (Ehdr, Index);
  }

  if (Offset >= Shdr->sh_size) {
    fprintf (stderr, "ImageTool: Invalid ELF string offset\n");
    return NULL;
  }

  return (const char *)Ehdr + Shdr->sh_offset + Offset;
}

#define IsShdrLoadable  ELF_SUFFIX(IsShdrLoadable)
static
BOOLEAN
IsShdrLoadable (
  IN const Elf_Ehdr  *Ehdr,
  IN const Elf_Shdr  *Shdr
  )
{
  return (Shdr->sh_flags & SHF_ALLOC) != 0;
}

#define ParseElfFile  ELF_SUFFIX(ParseElfFile)
static
RETURN_STATUS
ParseElfFile (
  OUT tool_elf_scan_context  *Context,
  IN  const void             *File,
  IN  uint32_t               FileSize
  )
{
  static const unsigned char Ident[] = {
    ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3, ELFCLASS, ELFDATA2LSB
  };
  const Elf_Ehdr  *Ehdr;
  bool            AlignmentSet;
  Elf_Size        Alignment;
  bool            BaseAddressSet;
  Elf_Addr        BaseAddress;
  bool            HasPieRelocs;
  const Elf_Shdr  *Shdr;
  UINTN           Offset;
  UINT32          Index;
  char            *Last;

  assert (File != NULL || FileSize == 0);

  Ehdr = (const Elf_Ehdr *)File;

  //
  // Check header
  //
  if ((FileSize < sizeof (*Ehdr))
    || (memcmp (Ident, Ehdr->e_ident, sizeof (Ident)) != 0)) {
    return RETURN_UNSUPPORTED;
  }

  if ((Ehdr->e_type != ET_EXEC) && (Ehdr->e_type != ET_DYN)) {
    fprintf (stderr, "ImageTool: ELF e_type not ET_EXEC or ET_DYN\n");
    return RETURN_INCOMPATIBLE_VERSION;
  }

  //
  // Check section headers
  //
  BaseAddressSet = false;
  BaseAddress    = 0;
  Alignment      = 0;
  AlignmentSet   = false;
  HasPieRelocs   = false;
  for (Index = 0; Index < Ehdr->e_shnum; ++Index) {
    Offset = (UINTN)Ehdr->e_shoff + Index * Ehdr->e_shentsize;

    if (FileSize < (Offset + sizeof (*Shdr))) {
      fprintf (stderr, "ImageTool: ELF section header is outside file\n");
      return RETURN_VOLUME_CORRUPTED;
    }

    Shdr = (const Elf_Shdr *)((const char *)Ehdr + Offset);

    if ((Shdr->sh_type != SHT_NOBITS)
      && ((FileSize < Shdr->sh_offset) || ((FileSize - Shdr->sh_offset) < Shdr->sh_size))) {
      fprintf (stderr, "ImageTool: ELF section %d points outside file\n", Index);
      return RETURN_VOLUME_CORRUPTED;
    }

    if (Shdr->sh_link >= Ehdr->e_shnum) {
      fprintf (stderr, "ImageTool: ELF %d-th section's sh_link is out of range\n", Index);
      return RETURN_VOLUME_CORRUPTED;
    }

    if ((Shdr->sh_type == SHT_RELA) || (Shdr->sh_type == SHT_REL)) {
      if (Shdr->sh_info >= Ehdr->e_shnum) {
        fprintf (stderr, "ImageTool: ELF %d-th section's sh_info is out of range\n", Index);
        return RETURN_VOLUME_CORRUPTED;
      }

      if (Shdr->sh_info == 0) {
        HasPieRelocs = TRUE;
      }
    }

    if (!BaseAddressSet || Shdr->sh_addr < BaseAddress) {
      BaseAddressSet = TRUE;
      BaseAddress    = Shdr->sh_addr;
    }

    if (!IsShdrLoadable (Ehdr, Shdr)) {
      continue;
    }

    if (!AlignmentSet || Alignment < Shdr->sh_addralign) {
      AlignmentSet = TRUE;
      Alignment    = Shdr->sh_addralign;
    }
  }

  if (Ehdr->e_shstrndx >= Ehdr->e_shnum) {
    fprintf (stderr, "ImageTool: Invalid section name string table\n");
    return RETURN_VOLUME_CORRUPTED;
  }
  Shdr = GetShdrByIndex (Ehdr, Ehdr->e_shstrndx);

  if (Shdr->sh_type != SHT_STRTAB) {
    fprintf (stderr, "ImageTool: ELF string table section has wrong type\n");
    return RETURN_VOLUME_CORRUPTED;
  }

  Last = (char *)((UINT8 *)Ehdr + Shdr->sh_offset + Shdr->sh_size - 1);
  if (*Last != '\0') {
    fprintf (stderr, "ImageTool: ELF string table section is not NUL-terminated\n");
    return RETURN_VOLUME_CORRUPTED;
  }

  if (!AlignmentSet || (!IS_POW2(Alignment)) || (Alignment > MAX_PE_ALIGNMENT)) {
    fprintf (stderr, "ImageTool: Invalid section alignment\n");
    return RETURN_VOLUME_CORRUPTED;
  }

  if (!BaseAddressSet || !IS_ALIGNED (BaseAddress, Alignment)) {
    fprintf (stderr, "ImageTool: Invalid base address %llx\n", (unsigned long long)BaseAddress);
    return RETURN_VOLUME_CORRUPTED;
  }

  memset (Context, 0, sizeof (*Context));

  Context->Ehdr         = Ehdr;
  Context->Alignment    = (uint32_t)Alignment;
  Context->BaseAddress  = BaseAddress;
  Context->HasPieRelocs = HasPieRelocs;

  return RETURN_SUCCESS;
}

#define ProcessRelocSection  ELF_SUFFIX(ProcessRelocSection)
static
bool
ProcessRelocSection (
  const tool_elf_scan_context  *Context,
  const Elf_Shdr               *Shdr
  )
{
  const Elf_Ehdr  *Ehdr;
  bool            HasPieRelocs;
  const Elf_Shdr  *SecShdr;

  Ehdr         = Context->Ehdr;
  HasPieRelocs = Context->HasPieRelocs;
  //
  // PIE relocations will target dummy section 0.
  //
  if (Shdr->sh_info != 0) {
    //
    // If PIE relocations exist, prefer them.
    //
    if (HasPieRelocs) {
      return FALSE;
    }
    //
    // Only translate relocations targetting sections that are translated.
    //
    SecShdr = GetShdrByIndex (Ehdr, Shdr->sh_info);

    if (!IsShdrLoadable (Ehdr, SecShdr)) {
      return FALSE;
    }
  } else {
    assert (HasPieRelocs);
  }

  return TRUE;
}

#define SetRelocs  ELF_SUFFIX(SetRelocs)
static
RETURN_STATUS
SetRelocs (
  OUT image_tool_image_info_t      *ImageInfo,
  IN  const tool_elf_scan_context  *Context
  )
{
  const Elf_Ehdr  *Ehdr;
  Elf_Addr        BaseAddress;
  UINT32          Index;
  const Elf_Shdr  *RelShdr;
  UINTN           RelIdx;
  const Elf_Rela  *Rel;
  UINT32          RelNum;

  Ehdr        = Context->Ehdr;
  BaseAddress = Context->BaseAddress;

  RelNum = 0;

  for (Index = 0; Index < Ehdr->e_shnum; Index++) {
    RelShdr = GetShdrByIndex (Ehdr, Index);

    if ((RelShdr->sh_type != SHT_REL) && (RelShdr->sh_type != SHT_RELA)) {
      continue;
    }

    if (!ProcessRelocSection (Context, RelShdr)) {
      continue;
    }

    for (RelIdx = 0; RelIdx < RelShdr->sh_size; RelIdx += (UINTN)RelShdr->sh_entsize) {
      Rel = (const Elf_Rela *)((const char *)Ehdr + RelShdr->sh_offset + RelIdx);
      //
      // Assume ELF virtual addresses match corresponding PE virtual adresses one to one,
      // so we don't need to recalculate relocations computed by the linker at all r_offset's.
      // We only need to transform ELF relocations' format into PE one.
      //
#if ELF_ARCH == 64
      if (Ehdr->e_machine == EM_X86_64) {
        switch (ELF_R_TYPE(Rel->r_info)) {
          case R_X86_64_NONE:
            break;
          case R_X86_64_RELATIVE:
          case R_X86_64_64:
            ImageInfo->RelocInfo.Relocs[RelNum].Type   = EFI_IMAGE_REL_BASED_DIR64;
            ImageInfo->RelocInfo.Relocs[RelNum].Target = (uint32_t)(Rel->r_offset - BaseAddress);
            ++RelNum;

            break;
          case R_X86_64_32:

            ImageInfo->RelocInfo.Relocs[RelNum].Type   = EFI_IMAGE_REL_BASED_HIGHLOW;
            ImageInfo->RelocInfo.Relocs[RelNum].Target = (uint32_t)(Rel->r_offset - BaseAddress);
            ++RelNum;

            break;
          case R_X86_64_32S:
          case R_X86_64_PLT32:
          case R_X86_64_PC32:
            break;
          case R_X86_64_GOTPCREL:
          case R_X86_64_GOTPCRELX:
          case R_X86_64_REX_GOTPCRELX:
            //
            // Relocations of these types point to instructions' arguments containing
            // offsets relative to RIP leading to .got entries. As sections' virtual
            // addresses do not change during ELF->PE transform, we don't need to
            // add them to relocations' list. But .got entries contain virtual
            // addresses which must be updated.
            //
            // At r_offset the following value is stored: G + GOT + A - P.
            // To derive .got entry address (G + GOT) compute: value - A + P.
            //
            // Such a method of finding relocatable .got entries can not be used,
            // due to a BUG in clang compiler, which sometimes generates
            // R_X86_64_REX_GOTPCRELX relocations instead of R_X86_64_PC32.
            //
            break;
          default:
            fprintf (
              stderr,
              "ImageTool: Unsupported ELF EM_X86_64 relocation 0x%llx in %s\n",
              (unsigned long long)ELF_R_TYPE(Rel->r_info),
              ImageInfo->DebugInfo.SymbolsPath
              );
            return RETURN_INCOMPATIBLE_VERSION;
        }
      } else if (Ehdr->e_machine == EM_AARCH64) {
        switch (ELF_R_TYPE(Rel->r_info)) {
          case R_AARCH64_NONE0:
          case R_AARCH64_NONE:
          case R_AARCH64_LD64_GOTOFF_LO15:
          case R_AARCH64_LD64_GOTPAGE_LO15:
          case R_AARCH64_LD64_GOT_LO12_NC:
          case R_AARCH64_ADR_GOT_PAGE:
          case R_AARCH64_ADR_PREL_PG_HI21:
          case R_AARCH64_ADD_ABS_LO12_NC:
          case R_AARCH64_LDST8_ABS_LO12_NC:
          case R_AARCH64_LDST16_ABS_LO12_NC:
          case R_AARCH64_LDST32_ABS_LO12_NC:
          case R_AARCH64_LDST64_ABS_LO12_NC:
          case R_AARCH64_LDST128_ABS_LO12_NC:
          case R_AARCH64_ADR_PREL_LO21:
          case R_AARCH64_CONDBR19:
          case R_AARCH64_LD_PREL_LO19:
          case R_AARCH64_CALL26:
          case R_AARCH64_JUMP26:
          case R_AARCH64_PREL64:
          case R_AARCH64_PREL32:
          case R_AARCH64_PREL16:
            break;
          case R_AARCH64_ABS64:
          case R_AARCH64_RELATIVE:
            ImageInfo->RelocInfo.Relocs[RelNum].Type   = EFI_IMAGE_REL_BASED_DIR64;
            ImageInfo->RelocInfo.Relocs[RelNum].Target = (uint32_t)(Rel->r_offset - BaseAddress);
            ++RelNum;

            break;
          case R_AARCH64_ABS32:

            ImageInfo->RelocInfo.Relocs[RelNum].Type   = EFI_IMAGE_REL_BASED_HIGHLOW;
            ImageInfo->RelocInfo.Relocs[RelNum].Target = (uint32_t)(Rel->r_offset - BaseAddress);
            ++RelNum;

            break;
          default:
            fprintf (
              stderr,
              "ImageTool: Unsupported ELF EM_AARCH64 relocation 0x%llx in %s\n",
              (unsigned long long)ELF_R_TYPE(Rel->r_info),
              ImageInfo->DebugInfo.SymbolsPath
              );
            return RETURN_INCOMPATIBLE_VERSION;
        }
      }
#elif ELF_ARCH == 32
      if (Ehdr->e_machine == EM_386) {
        switch (ELF_R_TYPE(Rel->r_info)) {
          case R_386_NONE:
            break;
          case R_386_32:

            ImageInfo->RelocInfo.Relocs[RelNum].Type   = EFI_IMAGE_REL_BASED_HIGHLOW;
            ImageInfo->RelocInfo.Relocs[RelNum].Target = (Rel->r_offset - BaseAddress);
            ++RelNum;

            break;
          case R_386_PLT32:
          case R_386_PC32:
            break;
          default:
            fprintf (stderr, "ImageTool: Unsupported ELF EM_386 relocation 0x%x in %s\n", ELF_R_TYPE(Rel->r_info), ImageInfo->DebugInfo.SymbolsPath);
            return RETURN_INCOMPATIBLE_VERSION;
        }
      } else if (Ehdr->e_machine == EM_ARM) {
        switch (ELF32_R_TYPE(Rel->r_info)) {
          case R_ARM_NONE:
          case R_ARM_PC24:
          case R_ARM_REL32:
          case R_ARM_XPC25:
          case R_ARM_THM_PC22:
          case R_ARM_THM_JUMP19:
          case R_ARM_CALL:
          case R_ARM_JMP24:
          case R_ARM_THM_JUMP24:
          case R_ARM_PREL31:
          case R_ARM_MOVW_PREL_NC:
          case R_ARM_MOVT_PREL:
          case R_ARM_THM_MOVW_PREL_NC:
          case R_ARM_THM_MOVT_PREL:
          case R_ARM_THM_JMP6:
          case R_ARM_THM_ALU_PREL_11_0:
          case R_ARM_THM_PC12:
          case R_ARM_REL32_NOI:
          case R_ARM_ALU_PC_G0_NC:
          case R_ARM_ALU_PC_G0:
          case R_ARM_ALU_PC_G1_NC:
          case R_ARM_ALU_PC_G1:
          case R_ARM_ALU_PC_G2:
          case R_ARM_LDR_PC_G1:
          case R_ARM_LDR_PC_G2:
          case R_ARM_LDRS_PC_G0:
          case R_ARM_LDRS_PC_G1:
          case R_ARM_LDRS_PC_G2:
          case R_ARM_LDC_PC_G0:
          case R_ARM_LDC_PC_G1:
          case R_ARM_LDC_PC_G2:
          case R_ARM_THM_JUMP11:
          case R_ARM_THM_JUMP8:
          case R_ARM_TLS_GD32:
          case R_ARM_TLS_LDM32:
          case R_ARM_TLS_IE32:
            break;
          case R_ARM_ABS32:

            ImageInfo->RelocInfo.Relocs[RelNum].Type   = EFI_IMAGE_REL_BASED_HIGHLOW;
            ImageInfo->RelocInfo.Relocs[RelNum].Target = (Rel->r_offset - BaseAddress);
            ++RelNum;

            break;
          default:
            fprintf (stderr, "ImageTool: Unsupported ELF EM_ARM relocation 0x%x in %s\n", ELF_R_TYPE(Rel->r_info), ImageInfo->DebugInfo.SymbolsPath);
            return RETURN_INCOMPATIBLE_VERSION;
        }
      }
#endif
    }
  }

  ImageInfo->RelocInfo.NumRelocs = RelNum;

  return RETURN_SUCCESS;
}

#define CreateIntermediate  ELF_SUFFIX(CreateIntermediate)
static
RETURN_STATUS
CreateIntermediate (
  OUT image_tool_image_info_t      *ImageInfo,
  IN  const tool_elf_scan_context  *Context
  )
{
  const Elf_Ehdr       *Ehdr;
  Elf_Addr             BaseAddress;
  const Elf_Shdr       *Shdr;
  UINT32               Index;
  image_tool_segment_t *Segments;
  image_tool_reloc_t   *Relocs;
  UINT32               SIndex;
  const Elf_Rel        *Rel;
  UINTN                RIndex;
  const char           *Name;
  UINT32               NumRelocs;

  Ehdr        = Context->Ehdr;
  BaseAddress = Context->BaseAddress;

  Segments  = NULL;
  SIndex    = 0;
  Relocs    = NULL;
  NumRelocs = 0;

  for (Index = 0; Index < Ehdr->e_shnum; ++Index) {
    Shdr = GetShdrByIndex (Ehdr, Index);

    if ((Shdr->sh_type == SHT_REL) || (Shdr->sh_type == SHT_RELA)) {
      if ((Shdr->sh_flags & SHF_ALLOC) != 0) {
        fprintf (stderr, "ImageTool: Loadable reloc sections are unsupported\n");
        return RETURN_VOLUME_CORRUPTED;
      }

      if (!ProcessRelocSection (Context, Shdr)) {
        continue;
      }

      for (RIndex = 0; RIndex < Shdr->sh_size; RIndex += (UINTN)Shdr->sh_entsize) {
        Rel = (Elf_Rel *)((UINT8 *)Ehdr + Shdr->sh_offset + RIndex);
#if ELF_ARCH == 64
        if (Ehdr->e_machine == EM_X86_64) {
          if ((ELF_R_TYPE(Rel->r_info) == R_X86_64_RELATIVE)
            || (ELF_R_TYPE(Rel->r_info) == R_X86_64_64)
            || (ELF_R_TYPE(Rel->r_info) == R_X86_64_32)
            || (ELF_R_TYPE(Rel->r_info) == R_X86_64_GOTPCREL)
            || (ELF_R_TYPE(Rel->r_info) == R_X86_64_GOTPCRELX)
            || (ELF_R_TYPE(Rel->r_info) == R_X86_64_REX_GOTPCRELX)) {
            ++NumRelocs;
          }
        } else if (Ehdr->e_machine == EM_AARCH64) {
          if ((ELF_R_TYPE(Rel->r_info) == R_AARCH64_ABS64)
            || (ELF_R_TYPE(Rel->r_info) == R_AARCH64_RELATIVE)
            || (ELF_R_TYPE(Rel->r_info) == R_AARCH64_ABS32)) {
            ++NumRelocs;
          }
        }
#elif ELF_ARCH == 32
        if (Ehdr->e_machine == EM_386) {
          if (ELF_R_TYPE(Rel->r_info) == R_386_32) {
            ++NumRelocs;
          }
        } else if (Ehdr->e_machine == EM_ARM) {
          if (ELF_R_TYPE(Rel->r_info) == R_ARM_ABS32) {
            ++NumRelocs;
          }
        }
#endif
      }
    }

    if (!IsShdrLoadable (Ehdr, Shdr)) {
      continue;
    }

    ++ImageInfo->SegmentInfo.NumSegments;
  }

  if (ImageInfo->SegmentInfo.NumSegments == 0) {
    fprintf (stderr, "ImageTool: No loadable sections\n");
    return RETURN_VOLUME_CORRUPTED;
  }

  Segments = AllocateZeroPool (sizeof (*Segments) * ImageInfo->SegmentInfo.NumSegments);
  if (Segments == NULL) {
    fprintf (stderr, "ImageTool: Could not allocate memory for Segments\n");
    return RETURN_OUT_OF_RESOURCES;
  };

  ImageInfo->SegmentInfo.Segments = Segments;

  if (NumRelocs != 0) {
    Relocs = AllocateZeroPool (sizeof (*Relocs) * NumRelocs);
    if (Relocs == NULL) {
      fprintf (stderr, "ImageTool: Could not allocate memory for Relocs\n");
      return RETURN_OUT_OF_RESOURCES;
    };

    ImageInfo->RelocInfo.Relocs = Relocs;
  }

  for (Index = 0; Index < Ehdr->e_shnum; ++Index) {
    Shdr = GetShdrByIndex (Ehdr, Index);

    if ((Shdr->sh_flags & SHF_ALLOC) == 0) {
      continue;
    }

    if (Shdr->sh_type != SHT_PROGBITS && Shdr->sh_type != SHT_NOBITS) {
      fprintf (stderr, "ImageTool: Segment #%d type %x unsupported\n", Index, Shdr->sh_type);
      return RETURN_VOLUME_CORRUPTED;
    }

    Name = GetString (Ehdr, Shdr->sh_name, 0);
    if (Name == NULL) {
      return RETURN_VOLUME_CORRUPTED;
    }

    Segments[SIndex].Name = AllocateZeroPool (strlen (Name) + 1);
    if (Segments[SIndex].Name == NULL) {
      fprintf (stderr, "ImageTool: Could not allocate memory for Segment #%d Name\n", Index);
      return RETURN_OUT_OF_RESOURCES;
    };

    memcpy (Segments[SIndex].Name, Name, strlen (Name));

    Segments[SIndex].ImageAddress = (uint32_t)(Shdr->sh_addr - BaseAddress);
    Segments[SIndex].ImageSize    = ALIGN_VALUE ((uint32_t)Shdr->sh_size, Context->Alignment);
    Segments[SIndex].Read         = true;
    Segments[SIndex].Write        = (Shdr->sh_flags & SHF_WRITE) != 0;
    Segments[SIndex].Execute      = (Shdr->sh_flags & SHF_EXECINSTR) != 0;

    Segments[SIndex].Data = AllocateZeroPool (Segments[SIndex].ImageSize);
    if (Segments[SIndex].Data == NULL) {
      fprintf (stderr, "ImageTool: Could not allocate memory for Segment #%d Data\n", Index);
      return RETURN_OUT_OF_RESOURCES;
    };

    if (Shdr->sh_type == SHT_PROGBITS) {
      memcpy (Segments[SIndex].Data, (const char *)Ehdr + Shdr->sh_offset, (size_t)Shdr->sh_size);
    }

    ++SIndex;
  }

  assert (SIndex == ImageInfo->SegmentInfo.NumSegments);

  return SetRelocs (ImageInfo, Context);
}

#define ScanElf  ELF_SUFFIX(ScanElf)
RETURN_STATUS
ScanElf (
  OUT image_tool_image_info_t  *ImageInfo,
  IN  const void               *File,
  IN  uint32_t                 FileSize,
  IN  const char               *SymbolsPath OPTIONAL
  )
{
  RETURN_STATUS          Status;
  tool_elf_scan_context  Context;
  const Elf_Ehdr         *Ehdr;
  Elf_Addr               BaseAddress;

  assert (File != NULL || FileSize == 0);

  Status = ParseElfFile (&Context, File, FileSize);
  if (RETURN_ERROR (Status)) {
    return Status;
  }

  Ehdr        = Context.Ehdr;
  BaseAddress = Context.BaseAddress;

  memset (ImageInfo, 0, sizeof (*ImageInfo));

  ImageInfo->HeaderInfo.BaseAddress       = BaseAddress;
  ImageInfo->HeaderInfo.EntryPointAddress = (uint32_t)(Ehdr->e_entry - BaseAddress);
  ImageInfo->SegmentInfo.SegmentAlignment = (uint32_t)Context.Alignment;
  ImageInfo->RelocInfo.RelocsStripped     = false;

  if (SymbolsPath != NULL) {
    ImageInfo->DebugInfo.SymbolsPathLen = strlen (SymbolsPath);
  } else {
    assert (ImageInfo->DebugInfo.SymbolsPathLen == 0);
  }

  switch (Ehdr->e_machine) {
#if ELF_ARCH == 64
    case EM_X86_64:
      ImageInfo->HeaderInfo.Machine = IMAGE_FILE_MACHINE_X64;
      break;
    case EM_AARCH64:
      ImageInfo->HeaderInfo.Machine = IMAGE_FILE_MACHINE_ARM64;
      break;
#elif ELF_ARCH == 32
    case EM_386:
      ImageInfo->HeaderInfo.Machine = IMAGE_FILE_MACHINE_I386;
      break;
    case EM_ARM:
      ImageInfo->HeaderInfo.Machine = IMAGE_FILE_MACHINE_ARMTHUMB_MIXED;
      break;
#endif
    default:
      fprintf (stderr, "ImageTool: Unknown ELF architecture %d\n", Ehdr->e_machine);
      return RETURN_INCOMPATIBLE_VERSION;
  }

  ImageInfo->DebugInfo.SymbolsPath = AllocatePool (ImageInfo->DebugInfo.SymbolsPathLen + 1);
  if (ImageInfo->DebugInfo.SymbolsPath == NULL) {
    fprintf (stderr, "ImageTool: Could not allocate memory for Debug Data\n");
    return RETURN_OUT_OF_RESOURCES;
  }

  if (SymbolsPath != NULL) {
    memmove (ImageInfo->DebugInfo.SymbolsPath, SymbolsPath, ImageInfo->DebugInfo.SymbolsPathLen + 1);
  } else {
    *ImageInfo->DebugInfo.SymbolsPath = '\0';
  }

  //
  // There is no corresponding ELF property.
  //
  ImageInfo->HeaderInfo.Subsystem = 0;

  Status = CreateIntermediate (ImageInfo, &Context);
  if (RETURN_ERROR (Status)) {
    ToolImageDestruct (ImageInfo);
  }

  return Status;
}
