text: /******************************************************************************
   * 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;
  	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);
  		}
  	}
  }
  
  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 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;
  	long elf_size = -1;
  	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 elf_size;
  }
  