text: /** @file
    ELF library
  
    Copyright (c) 2019 - 2021, Intel Corporation. All rights reserved.<BR>
    SPDX-License-Identifier: BSD-2-Clause-Patent
  
  **/
  
  #include "ElfLibInternal.h"
  
  /**
    Return the section header specified by Index.
  
    @param ImageBase      The image base.
    @param Index          The section index.
  
    @return Pointer to the section header.
  **/
  Elf64_Shdr *
  GetElf64SectionByIndex (
    IN  UINT8                 *ImageBase,
    IN  UINT32                Index
    )
  {
    Elf64_Ehdr        *Ehdr;
  
    Ehdr  = (Elf64_Ehdr *)ImageBase;
    if (Index >= Ehdr->e_shnum) {
      return NULL;
    }
  
    return (Elf64_Shdr *)(ImageBase + Ehdr->e_shoff + Index * Ehdr->e_shentsize);
  }
  
  /**
    Return the segment header specified by Index.
  
    @param ImageBase      The image base.
    @param Index          The segment index.
  
    @return Pointer to the segment header.
  **/
  Elf64_Phdr *
  GetElf64SegmentByIndex (
    IN  UINT8                 *ImageBase,
    IN  UINT32                Index
    )
  {
    Elf64_Ehdr        *Ehdr;
  
    Ehdr  = (Elf64_Ehdr *)ImageBase;
    if (Index >= Ehdr->e_phnum) {
      return NULL;
    }
  
    return (Elf64_Phdr *)(ImageBase + Ehdr->e_phoff + Index * Ehdr->e_phentsize);
  }
  
  /**
    Return the section header specified by the range.
  
    @param ImageBase      The image base.
    @param Offset         The section offset.
    @param Size           The section size.
  
    @return Pointer to the section header.
  **/
  Elf64_Shdr *
  GetElf64SectionByRange (
    IN  UINT8                 *ImageBase,
    IN  UINT64                Offset,
    IN  UINT64                Size
    )
  {
    UINT32                    Index;
    Elf64_Ehdr                *Ehdr;
    Elf64_Shdr                *Shdr;
  
    Ehdr = (Elf64_Ehdr *)ImageBase;
  
    Shdr = (Elf64_Shdr *) (ImageBase + Ehdr->e_shoff);
    for (Index = 0; Index < Ehdr->e_shnum; Index++) {
      if ((Shdr->sh_offset == Offset) && (Shdr->sh_size == Size)) {
        return Shdr;
      }
      Shdr = ELF_NEXT_ENTRY (Elf64_Shdr, Shdr, Ehdr->e_shentsize);
    }
    return NULL;
  }
  
  /**
    Fix up the image based on the relocation entries.
  
    @param Rela                Relocation entries.
    @param RelaSize            Total size of relocation entries.
    @param RelaEntrySize       Relocation entry size.
    @param RelaType            Type of relocation entry.
    @param Delta               The delta between preferred image base and the actual image base.
    @param DynamicLinking      TRUE when fixing up according to dynamic relocation.
  
    @retval EFI_SUCCESS   The image fix up is processed successfully.
  **/
  EFI_STATUS
  ProcessRelocation64 (
    IN  Elf64_Rela            *Rela,
    IN  UINT64                RelaSize,
    IN  UINT64                RelaEntrySize,
    IN  UINT64                RelaType,
    IN  INTN                  Delta,
    IN  BOOLEAN               DynamicLinking
    )
  {
    UINTN                     Index;
    UINT64                    *Ptr;
    UINT32                    Type;
  
    for ( Index = 0
        ; MultU64x64 (RelaEntrySize, Index) < RelaSize
        ; Index++, Rela = ELF_NEXT_ENTRY (Elf64_Rela, Rela, RelaEntrySize)
        ) {
      //
      // r_offset is the virtual address of the storage unit affected by the relocation.
      //
      Ptr = (UINT64 *)(UINTN)(Rela->r_offset + Delta);
      Type  = ELF64_R_TYPE(Rela->r_info);
      switch (Type) {
        case R_X86_64_NONE:
        case R_X86_64_PC32:
        case R_X86_64_PLT32:
        case R_X86_64_GOTPCREL:
        case R_X86_64_GOTPCRELX:
        case R_X86_64_REX_GOTPCRELX:
          break;
  
        case R_X86_64_64:
          if (DynamicLinking) {
            //
            // Dynamic section doesn't contain entries of this type.
            //
            DEBUG ((DEBUG_INFO, "Unsupported relocation type %02X\n", Type));
            ASSERT (FALSE);
          } else {
            *Ptr += Delta;
          }
          break;
  
        case R_X86_64_32:
          //
          // Dynamic section doesn't contain entries of this type.
          //
          DEBUG ((DEBUG_INFO, "Unsupported relocation type %02X\n", Type));
          ASSERT (FALSE);
          break;
  
        case R_X86_64_RELATIVE:
          if (DynamicLinking) {
            //
            // A: Represents the addend used to compute the value of the relocatable field.
            // B: Represents the base address at which a shared object has been loaded into memory during execution.
            //    Generally, a shared object is built with a 0 base virtual address, but the execution address will be different.
            //
            // B (Base Address) in ELF spec is slightly different:
            //   An executable or shared object file's base address (on platforms that support the concept) is calculated during
            //   execution from three values: the virtual memory load address, the maximum page size, and the lowest virtual address
            //   of a program's loadable segment. To compute the base address, one determines the memory address associated with the
            //   lowest p_vaddr value for a PT_LOAD segment. This address is truncated to the nearest multiple of the maximum page size.
            //   The corresponding p_vaddr value itself is also truncated to the nearest multiple of the maximum page size.
            //
            //   *** The base address is the difference between the truncated memory address and the truncated p_vaddr value. ***
            //
            // Delta in this function is B.
            //
            // Calculation: B + A
            //
            if (RelaType == SHT_RELA) {
              *Ptr = Delta + Rela->r_addend;
            } else {
              //
              // A is stored in the field of relocation for REL type.
              //
              *Ptr = Delta + *Ptr;
            }
          } else {
            //
            // non-Dynamic section doesn't contain entries of this type.
            //
            DEBUG ((DEBUG_INFO, "Unsupported relocation type %02X\n", Type));
            ASSERT (FALSE);
          }
          break;
  
        default:
          DEBUG ((DEBUG_INFO, "Unsupported relocation type %02X\n", Type));
      }
    }
    return EFI_SUCCESS;
  }
  
  /**
    Relocate the DYN type image.
  
    @param ElfCt                Point to image context.
  
    @retval EFI_SUCCESS      The relocation succeeds.
    @retval EFI_UNSUPPORTED  The image doesn't contain a dynamic section.
  **/
  EFI_STATUS
  RelocateElf64Dynamic (
    IN    ELF_IMAGE_CONTEXT      *ElfCt
    )
  {
    UINT32                       Index;
    Elf64_Phdr                   *Phdr;
    Elf64_Shdr                   *DynShdr;
    Elf64_Shdr                   *RelShdr;
    Elf64_Dyn                    *Dyn;
    UINT64                       RelaAddress;
    UINT64                       RelaCount;
    UINT64                       RelaSize;
    UINT64                       RelaEntrySize;
    UINT64                       RelaType;
  
    //
    // 1. Locate the dynamic section.
    //
    // If an object file participates in dynamic linking, its program header table
    // will have an element of type PT_DYNAMIC.
    // This ``segment'' contains the .dynamic section. A special symbol, _DYNAMIC,
    // labels the section, which contains an array of Elf32_Dyn or Elf64_Dyn.
    //
    DynShdr = NULL;
    for (Index = 0; Index < ElfCt->PhNum; Index++) {
      Phdr = GetElf64SegmentByIndex (ElfCt->FileBase, Index);
      ASSERT (Phdr != NULL);
      if (Phdr->p_type == PT_DYNAMIC) {
        //
        // Verify the existence of the dynamic section.
        //
        DynShdr = GetElf64SectionByRange (ElfCt->FileBase, Phdr->p_offset, Phdr->p_filesz);
        break;
      }
    }
  
    //
    // It's abnormal a DYN ELF doesn't contain a dynamic section.
    //
    ASSERT (DynShdr != NULL);
    if (DynShdr == NULL) {
      return EFI_UNSUPPORTED;
    }
    ASSERT (DynShdr->sh_type == SHT_DYNAMIC);
    ASSERT (DynShdr->sh_entsize >= sizeof (*Dyn));
  
    //
    // 2. Locate the relocation section from the dynamic section.
    //
    RelaAddress    = MAX_UINT64;
    RelaSize      = 0;
    RelaCount     = 0;
    RelaEntrySize = 0;
    RelaType      = 0;
    for ( Index = 0, Dyn = (Elf64_Dyn *) (ElfCt->FileBase + DynShdr->sh_offset)
        ; Index < DivU64x64Remainder (DynShdr->sh_size, DynShdr->sh_entsize, NULL)
        ; Index++, Dyn = ELF_NEXT_ENTRY (Elf64_Dyn, Dyn, DynShdr->sh_entsize)
        ) {
      switch (Dyn->d_tag) {
        case DT_RELA:
        case DT_REL:
          //
          // DT_REL represent program virtual addresses.
          // A file's virtual addresses might not match the memory virtual addresses during execution.
          // When interpreting addresses contained in the dynamic structure, the dynamic linker computes actual addresses,
          // based on the original file value and the memory base address.
          // For consistency, files do not contain relocation entries to ``correct'' addresses in the dynamic structure.
          //
          RelaAddress = Dyn->d_un.d_ptr;
          RelaType    = (Dyn->d_tag == DT_RELA) ? SHT_RELA: SHT_REL;
          break;
        case DT_RELACOUNT:
        case DT_RELCOUNT:
          RelaCount = Dyn->d_un.d_val;
          break;
        case DT_RELENT:
        case DT_RELAENT:
          RelaEntrySize = Dyn->d_un.d_val;
          break;
        case DT_RELSZ:
        case DT_RELASZ:
          RelaSize = Dyn->d_un.d_val;
          break;
        default:
          break;
      }
    }
  
    if (RelaAddress == MAX_UINT64) {
      ASSERT (RelaCount     == 0);
      ASSERT (RelaEntrySize == 0);
      ASSERT (RelaSize      == 0);
      //
      // It's fine that a DYN ELF doesn't contain relocation section.
      //
      return EFI_SUCCESS;
    }
  
    //
    // Verify the existence of the relocation section.
    //
    RelShdr = NULL;
    for (Index = 0; Index < ElfCt->ShNum; Index++) {
      RelShdr = GetElf64SectionByIndex (ElfCt->FileBase, Index);
      ASSERT (RelShdr != NULL);
      if ((RelShdr->sh_addr == RelaAddress) && (RelShdr->sh_size == RelaSize)) {
        break;
      }
      RelShdr = NULL;
    }
  
    if (RelShdr == NULL) {
      return EFI_UNSUPPORTED;
    }
    ASSERT (RelShdr->sh_type == RelaType);
    ASSERT (RelShdr->sh_entsize == RelaEntrySize);
  
    //
    // 3. Process the relocation section.
    //
    ProcessRelocation64 (
      (Elf64_Rela *) (ElfCt->FileBase + RelShdr->sh_offset),
      RelShdr->sh_size, RelShdr->sh_entsize, RelShdr->sh_type,
      (UINTN) ElfCt->ImageAddress - (UINTN) ElfCt->PreferredImageAddress,
      TRUE
      );
    return EFI_SUCCESS;
  }
  
  /**
    Relocate all sections in a ELF image.
  
    @param[in]  ElfCt               ELF image context pointer.
  
    @retval EFI_UNSUPPORTED         Relocation is not supported.
    @retval EFI_SUCCESS             ELF image was relocated successfully.
  **/
  EFI_STATUS
  RelocateElf64Sections  (
    IN    ELF_IMAGE_CONTEXT      *ElfCt
    )
  {
    EFI_STATUS       Status;
    Elf64_Ehdr       *Ehdr;
    Elf64_Shdr       *RelShdr;
    Elf64_Shdr       *Shdr;
    UINT32           Index;
    UINTN            Delta;
  
    Ehdr  = (Elf64_Ehdr *)ElfCt->FileBase;
    if (Ehdr->e_machine != EM_X86_64) {
      return EFI_UNSUPPORTED;
    }
  
    Delta = (UINTN) ElfCt->ImageAddress - (UINTN) ElfCt->PreferredImageAddress;
    ElfCt->EntryPoint = (UINTN)(Ehdr->e_entry + Delta);
  
    //
    // 1. Relocate dynamic ELF using the relocation section pointed by dynamic section
    //
    if (Ehdr->e_type == ET_DYN) {
      DEBUG ((DEBUG_INFO, "DYN ELF: Relocate using dynamic sections...\n"));
      Status = RelocateElf64Dynamic (ElfCt);
      ASSERT_EFI_ERROR (Status);
      return Status;
    }
  
    //
    // 2. Executable ELF: Fix up the delta between actual image address and preferred image address.
    //
    //  Linker already fixed up EXEC ELF based on the preferred image address.
    //  A ELF loader in modern OS only loads it into the preferred image address.
    //  The below relocation is unneeded in that case.
    //  But the ELF loader in firmware supports to load the image to a different address.
    //  The below relocation is needed in this case.
    //
    DEBUG ((DEBUG_INFO, "EXEC ELF: Fix actual/preferred base address delta ...\n"));
    for ( Index = 0, RelShdr = (Elf64_Shdr *) (ElfCt->FileBase + Ehdr->e_shoff)
        ; Index < Ehdr->e_shnum
        ; Index++,   RelShdr = ELF_NEXT_ENTRY (Elf64_Shdr, RelShdr, Ehdr->e_shentsize)
        ) {
      if ((RelShdr->sh_type != SHT_REL) && (RelShdr->sh_type != SHT_RELA)) {
        continue;
      }
      Shdr = GetElf64SectionByIndex (ElfCt->FileBase, RelShdr->sh_info);
      if ((Shdr->sh_flags & SHF_ALLOC) == SHF_ALLOC) {
        //
        // Only fix up sections that occupy memory during process execution.
        //
        ProcessRelocation64 (
          (Elf64_Rela *)((UINT8*)Ehdr + RelShdr->sh_offset),
          RelShdr->sh_size, RelShdr->sh_entsize, RelShdr->sh_type,
          Delta, FALSE
          );
      }
    }
  
    return EFI_SUCCESS;
  }
  
  /**
    Load ELF image which has 64-bit architecture.
  
    Caller should set Context.ImageAddress to a proper value, either pointing to
    a new allocated memory whose size equal to Context.ImageSize, or pointing
    to Context.PreferredImageAddress.
  
    @param[in]  ElfCt               ELF image context pointer.
  
    @retval EFI_SUCCESS         ELF binary is loaded successfully.
    @retval Others              Loading ELF binary fails.
  
  **/
  EFI_STATUS
  LoadElf64Image (
    IN    ELF_IMAGE_CONTEXT    *ElfCt
    )
  {
    Elf64_Ehdr    *Ehdr;
    Elf64_Phdr    *Phdr;
    UINT16        Index;
    UINTN         Delta;
  
    ASSERT (ElfCt != NULL);
  
    //
    // Per the sprit of ELF, loading to memory only consumes info from program headers.
    //
    Ehdr = (Elf64_Ehdr *)ElfCt->FileBase;
  
    for ( Index = 0, Phdr = (Elf64_Phdr *)(ElfCt->FileBase + Ehdr->e_phoff)
        ; Index < Ehdr->e_phnum
        ; Index++, Phdr = ELF_NEXT_ENTRY (Elf64_Phdr, Phdr, Ehdr->e_phentsize)
        ) {
      //
      // Skip segments that don't require load (type tells, or size is 0)
      //
      if ((Phdr->p_type != PT_LOAD) ||
          (Phdr->p_memsz == 0)) {
        continue;
      }
  
      //
      // The memory offset of segment relative to the image base
      // Note: CopyMem() does nothing when the dst equals to src.
      //
      Delta = (UINTN) Phdr->p_paddr - (UINTN) ElfCt->PreferredImageAddress;
      CopyMem (ElfCt->ImageAddress + Delta, ElfCt->FileBase + (UINTN) Phdr->p_offset, (UINTN) Phdr->p_filesz);
      ZeroMem (ElfCt->ImageAddress + Delta + (UINTN) Phdr->p_filesz, (UINTN) (Phdr->p_memsz - Phdr->p_filesz));
    }
  
    //
    // Relocate when new new image base is not the preferred image base.
    //
    if (ElfCt->ImageAddress != ElfCt->PreferredImageAddress) {
      RelocateElf64Sections (ElfCt);
    }
  
    return EFI_SUCCESS;
  }
  