REF: https://bugzilla.tianocore.org/show_bug.cgi?id=3818 It will have some potential issue when memory larger than 2G because the high memory address will be fill with 0xFFFFFFFF when do the operation of INTN + INT64 but it is 32 bit normal data in fact. Should use same data type INT64 + INT64. V3: 1. Use INT64 as input parameter because all date type is 64 bit V2: 1. Force the data type to UINTN to avoid high dword be filled with 0xFFFFFFFF 2. Keep INTN because the offset may postive or negative. Reviewed-by: Guo Dong <guo.dong@intel.com> Reviewed-by: Ray Ni <ray.ni@intel.com> Signed-off-by: Guomin Jiang <guomin.jiang@intel.com>
		
			
				
	
	
		
			487 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			487 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /** @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  INT64       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;
 | |
| }
 |