| /** @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; | |
| } |