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