| /****************************************************************************** |
| * 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> |
| |
| 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; |
| signed long virt2phys = 0; /* Offset between virtual and physical */ |
| |
| /* loop e_phnum times */ |
| for (i = 0; i <= ehdr->e_phnum; i++) { |
| /* PT_LOAD ? */ |
| if (phdr->p_type == PT_LOAD) { |
| if (!virt2phys) { |
| virt2phys = phdr->p_paddr - phdr->p_vaddr; |
| } |
| /* 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 + virt2phys; |
| } |
| |
| /** |
| * 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_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; |
| int 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); |
| } |
| } |
| } |