blob: 3bc40402fb8a9552a434c9c3d1e5e9d09643e5bc [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2004, 2011 IBM Corporation
* All rights reserved.
* This program and the accompanying materials
* are made available under the terms of the BSD License
* which accompanies this distribution, and is available at
* http://www.opensource.org/licenses/bsd-license.php
*
* Contributors:
* IBM Corporation - initial implementation
*****************************************************************************/
/*
* 64-bit ELF loader for PowerPC.
* See the "64-bit PowerPC ELF Application Binary Interface Supplement" and
* the "ELF-64 Object File Format" documentation for details.
*/
#include <string.h>
#include <stdio.h>
#include <libelf.h>
#include <byteorder.h>
#include <helpers.h>
struct ehdr64
{
uint32_t ei_ident;
uint8_t ei_class;
uint8_t ei_data;
uint8_t ei_version;
uint8_t ei_pad[9];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
uint64_t e_entry;
uint64_t e_phoff;
uint64_t e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
};
struct phdr64
{
uint32_t p_type;
uint32_t p_flags;
uint64_t p_offset;
uint64_t p_vaddr;
uint64_t p_paddr;
uint64_t p_filesz;
uint64_t p_memsz;
uint64_t p_align;
};
struct shdr64
{
uint32_t sh_name; /* Section name */
uint32_t sh_type; /* Section type */
uint64_t sh_flags; /* Section attributes */
uint64_t sh_addr; /* Virtual address in memory */
uint64_t sh_offset; /* Offset in file */
uint64_t sh_size; /* Size of section */
uint32_t sh_link; /* Link to other section */
uint32_t sh_info; /* Miscellaneous information */
uint64_t sh_addralign; /* Address alignment boundary */
uint64_t sh_entsize; /* Size of entries, if section has table */
};
struct rela /* RelA relocation table entry */
{
uint64_t r_offset; /* Address of reference */
uint64_t r_info; /* Symbol index and type of relocation */
int64_t r_addend; /* Constant part of expression */
};
struct sym64
{
uint32_t st_name; /* Symbol name */
uint8_t st_info; /* Type and Binding attributes */
uint8_t st_other; /* Reserved */
uint16_t st_shndx; /* Section table index */
uint64_t st_value; /* Symbol value */
uint64_t st_size; /* Size of object (e.g., common) */
};
/* For relocations */
#define ELF_R_SYM(i) ((i)>>32)
#define ELF_R_TYPE(i) ((uint32_t)(i) & 0xFFFFFFFF)
#define ELF_R_INFO(s,t) ((((uint64_t) (s)) << 32) + (t))
/*
* Relocation types for PowerPC64.
*/
#define R_PPC64_NONE 0
#define R_PPC64_ADDR32 1
#define R_PPC64_ADDR24 2
#define R_PPC64_ADDR16 3
#define R_PPC64_ADDR16_LO 4
#define R_PPC64_ADDR16_HI 5
#define R_PPC64_ADDR16_HA 6
#define R_PPC64_ADDR14 7
#define R_PPC64_ADDR14_BRTAKEN 8
#define R_PPC64_ADDR14_BRNTAKEN 9
#define R_PPC64_REL24 10
#define R_PPC64_REL14 11
#define R_PPC64_REL14_BRTAKEN 12
#define R_PPC64_REL14_BRNTAKEN 13
#define R_PPC64_GOT16 14
#define R_PPC64_GOT16_LO 15
#define R_PPC64_GOT16_HI 16
#define R_PPC64_GOT16_HA 17
#define R_PPC64_COPY 19
#define R_PPC64_GLOB_DAT 20
#define R_PPC64_JMP_SLOT 21
#define R_PPC64_RELATIVE 22
#define R_PPC64_UADDR32 24
#define R_PPC64_UADDR16 25
#define R_PPC64_REL32 26
#define R_PPC64_PLT32 27
#define R_PPC64_PLTREL32 28
#define R_PPC64_PLT16_LO 29
#define R_PPC64_PLT16_HI 30
#define R_PPC64_PLT16_HA 31
#define R_PPC64_SECTOFF 33
#define R_PPC64_SECTOFF_LO 34
#define R_PPC64_SECTOFF_HI 35
#define R_PPC64_SECTOFF_HA 36
#define R_PPC64_ADDR30 37
#define R_PPC64_ADDR64 38
#define R_PPC64_ADDR16_HIGHER 39
#define R_PPC64_ADDR16_HIGHERA 40
#define R_PPC64_ADDR16_HIGHEST 41
#define R_PPC64_ADDR16_HIGHESTA 42
#define R_PPC64_UADDR64 43
#define R_PPC64_REL64 44
#define R_PPC64_PLT64 45
#define R_PPC64_PLTREL64 46
#define R_PPC64_TOC16 47
#define R_PPC64_TOC16_LO 48
#define R_PPC64_TOC16_HI 49
#define R_PPC64_TOC16_HA 50
#define R_PPC64_TOC 51
#define R_PPC64_PLTGOT16 52
#define R_PPC64_PLTGOT16_LO 53
#define R_PPC64_PLTGOT16_HI 54
#define R_PPC64_PLTGOT16_HA 55
#define R_PPC64_ADDR16_DS 56
#define R_PPC64_ADDR16_LO_DS 57
#define R_PPC64_GOT16_DS 58
#define R_PPC64_GOT16_LO_DS 59
#define R_PPC64_PLT16_LO_DS 60
#define R_PPC64_SECTOFF_DS 61
#define R_PPC64_SECTOFF_LO_DS 62
#define R_PPC64_TOC16_DS 63
#define R_PPC64_TOC16_LO_DS 64
#define R_PPC64_PLTGOT16_DS 65
#define R_PPC64_PLTGOT16_LO_DS 66
#define R_PPC64_TLS 67
#define R_PPC64_DTPMOD64 68
#define R_PPC64_TPREL16 69
#define R_PPC64_TPREL16_LO 60
#define R_PPC64_TPREL16_HI 71
#define R_PPC64_TPREL16_HA 72
#define R_PPC64_TPREL64 73
#define R_PPC64_DTPREL16 74
#define R_PPC64_DTPREL16_LO 75
#define R_PPC64_DTPREL16_HI 76
#define R_PPC64_DTPREL16_HA 77
#define R_PPC64_DTPREL64 78
#define R_PPC64_GOT_TLSGD16 79
#define R_PPC64_GOT_TLSGD16_LO 80
#define R_PPC64_GOT_TLSGD16_HI 81
#define R_PPC64_GOT_TLSGD16_HA 82
#define R_PPC64_GOT_TLSLD16 83
#define R_PPC64_GOT_TLSLD16_LO 84
#define R_PPC64_GOT_TLSLD16_HI 85
#define R_PPC64_GOT_TLSLD16_HA 86
#define R_PPC64_GOT_TPREL16_DS 87
#define R_PPC64_GOT_TPREL16_LO_ DS 88
#define R_PPC64_GOT_TPREL16_HI 89
#define R_PPC64_GOT_TPREL16_HA 90
#define R_PPC64_GOT_DTPREL16_DS 91
#define R_PPC64_GOT_DTPREL16_LO_DS 92
#define R_PPC64_GOT_DTPREL16_HI 93
#define R_PPC64_GOT_DTPREL16_HA 94
#define R_PPC64_TPREL16_DS 95
#define R_PPC64_TPREL16_LO_DS 96
#define R_PPC64_TPREL16_HIGHER 97
#define R_PPC64_TPREL16_HIGHERA 98
#define R_PPC64_TPREL16_HIGHEST 99
#define R_PPC64_TPREL16_HIGHESTA 100
#define R_PPC64_DTPREL16_DS 101
#define R_PPC64_DTPREL16_LO_DS 102
#define R_PPC64_DTPREL16_HIGHER 103
#define R_PPC64_DTPREL16_HIGHERA 104
#define R_PPC64_DTPREL16_HIGHEST 105
#define R_PPC64_DTPREL16_HIGHESTA 106
static struct phdr64*
get_phdr64(unsigned long *file_addr)
{
return (struct phdr64 *) (((unsigned char *) file_addr)
+ ((struct ehdr64 *)file_addr)->e_phoff);
}
static void
load_segment64(unsigned long *file_addr, struct phdr64 *phdr, signed long offset,
int (*pre_load)(void*, long),
void (*post_load)(void*, long))
{
unsigned long src = phdr->p_offset + (unsigned long) file_addr;
unsigned long destaddr;
destaddr = phdr->p_paddr + offset;
/* check if we're allowed to copy */
if (pre_load != NULL) {
if (pre_load((void*)destaddr, phdr->p_memsz) != 0)
return;
}
/* copy into storage */
memmove((void*)destaddr, (void*)src, phdr->p_filesz);
/* clear bss */
memset((void*)(destaddr + phdr->p_filesz), 0,
phdr->p_memsz - phdr->p_filesz);
if (phdr->p_memsz && post_load != NULL) {
post_load((void*)destaddr, phdr->p_memsz);
}
}
unsigned long
elf_load_segments64(void *file_addr, signed long offset,
int (*pre_load)(void*, long),
void (*post_load)(void*, long))
{
struct ehdr64 *ehdr = (struct ehdr64 *) file_addr;
/* Calculate program header address */
struct phdr64 *phdr = get_phdr64(file_addr);
int i;
/* loop e_phnum times */
for (i = 0; i <= ehdr->e_phnum; i++) {
/* PT_LOAD ? */
if (phdr->p_type == PT_LOAD) {
if (phdr->p_paddr != phdr->p_vaddr) {
printf("ELF64: VirtAddr(%lx) != PhysAddr(%lx) not supported, aborting\n",
(long)phdr->p_vaddr, (long)phdr->p_paddr);
return 0;
}
/* copy segment */
load_segment64(file_addr, phdr, offset, pre_load, post_load);
}
/* step to next header */
phdr = (struct phdr64 *)(((uint8_t *)phdr) + ehdr->e_phentsize);
}
/* Entry point is always a virtual address, so translate it
* to physical before returning it */
return ehdr->e_entry;
}
/**
* Return the base address for loading (i.e. the address of the first PT_LOAD
* segment)
* @param file_addr pointer to the ELF file in memory
* @return the base address
*/
long
elf_get_base_addr64(void *file_addr)
{
struct ehdr64 *ehdr = (struct ehdr64 *) file_addr;
/* Calculate program header address */
struct phdr64 *phdr = get_phdr64(file_addr);
int i;
/* loop e_phnum times */
for (i = 0; i <= ehdr->e_phnum; i++) {
/* PT_LOAD ? */
if (phdr->p_type == PT_LOAD) {
/* Return base address */
return phdr->p_paddr;
}
/* step to next header */
phdr = (struct phdr64 *)(((uint8_t *)phdr) + ehdr->e_phentsize);
}
return 0;
}
/**
* Apply one relocation entry.
*/
static void
elf_apply_rela64(void *file_addr, signed long offset, struct rela *relaentry,
struct sym64 *symtabentry)
{
void *addr;
unsigned long s_a;
unsigned long base_addr;
base_addr = elf_get_base_addr64(file_addr);
/* Sanity check */
if (relaentry->r_offset < base_addr) {
printf("\nELF relocation out of bounds!\n");
return;
}
base_addr += offset;
/* Actual address where the relocation will be applied at. */
addr = (void*)(relaentry->r_offset + offset);
/* Symbol value (S) + Addend (A) */
s_a = symtabentry->st_value + offset + relaentry->r_addend;
switch (ELF_R_TYPE(relaentry->r_info)) {
case R_PPC64_ADDR32: /* S + A */
*(uint32_t *)addr = (uint32_t) s_a;
break;
case R_PPC64_ADDR64: /* S + A */
*(uint64_t *)addr = (uint64_t) s_a;
break;
case R_PPC64_TOC: /* .TOC */
*(uint64_t *)addr += offset;
break;
case R_PPC64_ADDR16_HIGHEST: /* #highest(S + A) */
*(uint16_t *)addr = ((s_a >> 48) & 0xffff);
break;
case R_PPC64_ADDR16_HIGHER: /* #higher(S + A) */
*(uint16_t *)addr = ((s_a >> 32) & 0xffff);
break;
case R_PPC64_ADDR16_HI: /* #hi(S + A) */
*(uint16_t *)addr = ((s_a >> 16) & 0xffff);
break;
case R_PPC64_ADDR16_LO: /* #lo(S + A) */
*(uint16_t *)addr = s_a & 0xffff;
break;
case R_PPC64_ADDR16_LO_DS:
*(uint16_t *)addr = (s_a & 0xfffc);
break;
case R_PPC64_ADDR16_HA: /* #ha(S + A) */
*(uint16_t *)addr = (((s_a >> 16) + ((s_a & 0x8000) ? 1 : 0))
& 0xffff);
break;
case R_PPC64_TOC16: /* half16* S + A - .TOC. */
case R_PPC64_TOC16_LO_DS:
case R_PPC64_TOC16_LO: /* #lo(S + A - .TOC.) */
case R_PPC64_TOC16_HI: /* #hi(S + A - .TOC.) */
case R_PPC64_TOC16_HA:
case R_PPC64_TOC16_DS: /* (S + A - .TOC) >> 2 */
case R_PPC64_REL14:
case R_PPC64_REL24: /* (S + A - P) >> 2 */
case R_PPC64_REL32: /* S + A - P */
case R_PPC64_REL64: /* S + A - P */
case R_PPC64_GOT16_DS:
case R_PPC64_GOT16_LO_DS:
// printf("\t\tignoring relocation type %i\n",
// ELF_R_TYPE(relaentry->r_info));
break;
default:
printf("ERROR: Unhandled relocation (A) type %i\n",
ELF_R_TYPE(relaentry->r_info));
}
}
/**
* Step through all relocation entries and apply them one by one.
*/
static void
elf_apply_all_rela64(void *file_addr, signed long offset, struct shdr64 *shdrs, int idx)
{
struct shdr64 *rela_shdr = &shdrs[idx];
struct shdr64 *dst_shdr = &shdrs[rela_shdr->sh_info];
struct shdr64 *sym_shdr = &shdrs[rela_shdr->sh_link];
struct rela *relaentry;
struct sym64 *symtabentry;
uint32_t symbolidx;
unsigned i;
/* If the referenced section has not been allocated, then it has
* not been loaded and thus does not need to be relocated. */
if ((dst_shdr->sh_flags & SHF_ALLOC) != SHF_ALLOC)
return;
for (i = 0; i < rela_shdr->sh_size; i += rela_shdr->sh_entsize) {
relaentry = (struct rela *)(file_addr + rela_shdr->sh_offset + i);
symbolidx = ELF_R_SYM(relaentry->r_info);
symtabentry = (struct sym64*)(file_addr + sym_shdr->sh_offset) + symbolidx;
elf_apply_rela64(file_addr, offset, relaentry, symtabentry);
}
}
/**
* Apply ELF relocations
*/
void
elf_relocate64(void *file_addr, signed long offset)
{
struct ehdr64 *ehdr = (struct ehdr64 *) file_addr;
/* Calculate section header address */
struct shdr64 *shdrs = (struct shdr64 *)
(((unsigned char *) file_addr) + ehdr->e_shoff);
int i;
/* loop over all segments */
for (i = 0; i <= ehdr->e_shnum; i++) {
/* Skip if it is not a relocation segment */
if (shdrs[i].sh_type == SHT_RELA) {
elf_apply_all_rela64(file_addr, offset, shdrs, i);
}
}
}
void
elf_byteswap_header64(void *file_addr)
{
struct ehdr64 *ehdr = (struct ehdr64 *) file_addr;
struct phdr64 *phdr;
int i;
bswap_16p(&ehdr->e_type);
bswap_16p(&ehdr->e_machine);
bswap_32p(&ehdr->e_version);
bswap_64p(&ehdr->e_entry);
bswap_64p(&ehdr->e_phoff);
bswap_64p(&ehdr->e_shoff);
bswap_32p(&ehdr->e_flags);
bswap_16p(&ehdr->e_ehsize);
bswap_16p(&ehdr->e_phentsize);
bswap_16p(&ehdr->e_phnum);
bswap_16p(&ehdr->e_shentsize);
bswap_16p(&ehdr->e_shnum);
bswap_16p(&ehdr->e_shstrndx);
phdr = get_phdr64(file_addr);
/* loop e_phnum times */
for (i = 0; i <= ehdr->e_phnum; i++) {
bswap_32p(&phdr->p_type);
bswap_32p(&phdr->p_flags);
bswap_64p(&phdr->p_offset);
bswap_64p(&phdr->p_vaddr);
bswap_64p(&phdr->p_paddr);
bswap_64p(&phdr->p_filesz);
bswap_64p(&phdr->p_memsz);
bswap_64p(&phdr->p_align);
/* step to next header */
phdr = (struct phdr64 *)(((uint8_t *)phdr) + ehdr->e_phentsize);
}
}
uint32_t elf_get_eflags_64(void *file_addr)
{
struct ehdr64 *ehdr = (struct ehdr64 *) file_addr;
return ehdr->e_flags;
}
/*
* Determine the size of an ELF image that has been loaded into
* a buffer larger than its size. We search all program headers
* and sections for the one that shows the farthest extent of the
* file.
* @return Return -1 on error, size of file otherwise.
*/
long elf_get_file_size64(const void *buffer, const unsigned long buffer_size)
{
const struct ehdr64 *ehdr = (const struct ehdr64 *) buffer;
const uint8_t *buffer_end = buffer + buffer_size;
const struct phdr64 *phdr;
const struct shdr64 *shdr;
unsigned long elf_size = 0;
uint16_t entsize;
unsigned i;
if (buffer_size < sizeof(struct ehdr) ||
ehdr->e_ehsize != sizeof(struct ehdr64))
return -1;
phdr = buffer + elf64_to_cpu(ehdr->e_phoff, ehdr);
entsize = elf16_to_cpu(ehdr->e_phentsize, ehdr);
for (i = 0; i < elf16_to_cpu(ehdr->e_phnum, ehdr); i++) {
if (((uint8_t *)phdr) + entsize > buffer_end)
return -1;
elf_size = MAX(elf64_to_cpu(phdr->p_offset, ehdr) +
elf64_to_cpu(phdr->p_filesz, ehdr),
elf_size);
/* step to next header */
phdr = (struct phdr64 *)(((uint8_t *)phdr) + entsize);
}
shdr = buffer + elf64_to_cpu(ehdr->e_shoff, ehdr);
entsize = elf16_to_cpu(ehdr->e_shentsize, ehdr);
for (i = 0; i < elf16_to_cpu(ehdr->e_shnum, ehdr); i++) {
if (((uint8_t *)shdr) + entsize > buffer_end)
return -1;
elf_size = MAX(elf64_to_cpu(shdr->sh_offset, ehdr) +
elf64_to_cpu(shdr->sh_size, ehdr),
elf_size);
/* step to next header */
shdr = (struct shdr64 *)(((uint8_t *)shdr) + entsize);
}
elf_size = ROUNDUP(elf_size, 4);
if (elf_size > buffer_size)
return -1;
return (long) elf_size;
}