/** @file ELF library Copyright (c) 2019 - 2021, Intel Corporation. All rights reserved.
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; }