| /****************************************************************************** |
| * 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 |
| *****************************************************************************/ |
| |
| /* |
| * ELF loader |
| */ |
| |
| #include <string.h> |
| #include <cache.h> |
| #include <libelf.h> |
| #include <byteorder.h> |
| |
| /** |
| * elf_check_file tests if the file at file_addr is |
| * a correct endian, ELF PPC executable |
| * @param file_addr pointer to the start of the ELF file |
| * @return the class (1 for 32 bit, 2 for 64 bit) |
| * -1 if it is not an ELF file |
| * -2 if it has the wrong endianness |
| * -3 if it is not an ELF executable |
| * -4 if it is not for PPC |
| */ |
| static int |
| elf_check_file(unsigned long *file_addr) |
| { |
| struct ehdr *ehdr = (struct ehdr *) file_addr; |
| uint8_t native_endian; |
| |
| /* check if it is an ELF image at all */ |
| if (cpu_to_be32(ehdr->ei_ident) != 0x7f454c46) |
| return -1; |
| |
| #ifdef __BIG_ENDIAN__ |
| native_endian = ELFDATA2MSB; |
| #else |
| native_endian = ELFDATA2LSB; |
| #endif |
| |
| if (native_endian != ehdr->ei_data) { |
| switch (ehdr->ei_class) { |
| case 1: |
| elf_byteswap_header32(file_addr); |
| break; |
| case 2: |
| elf_byteswap_header64(file_addr); |
| break; |
| } |
| } |
| |
| /* check if it is an ELF executable ... and also |
| * allow DYN files, since this is specified by ePAPR */ |
| if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN) |
| return -3; |
| |
| /* check if it is a PPC ELF executable */ |
| if (ehdr->e_machine != 0x14 && ehdr->e_machine != 0x15) |
| return -4; |
| |
| return ehdr->ei_class; |
| } |
| |
| /** |
| * load_elf_file tries to load the ELF file specified in file_addr |
| * |
| * it first checks if the file is a PPC ELF executable and then loads |
| * the segments depending if it is a 64bit or 32 bit ELF file |
| * |
| * @param file_addr pointer to the start of the elf file |
| * @param entry pointer where the ELF loader will store |
| * the entry point |
| * @param pre_load handler that is called before copying a segment |
| * @param post_load handler that is called after copying a segment |
| * @return 1 for a 32 bit file |
| * 2 for a 64 bit BE file |
| * 3 for a 64 bit LE ABIv1 file |
| * 4 for a 64 bit LE ABIv2 file |
| * 5 for a 32 bit LE ABIv1 file |
| * anything else means an error during load |
| */ |
| int |
| elf_load_file(void *file_addr, unsigned long *entry, |
| int (*pre_load)(void*, long), |
| void (*post_load)(void*, long)) |
| { |
| int type = elf_check_file(file_addr); |
| struct ehdr *ehdr = (struct ehdr *) file_addr; |
| |
| switch (type) { |
| case 1: |
| *entry = elf_load_segments32(file_addr, 0, pre_load, post_load); |
| if (ehdr->ei_data != ELFDATA2MSB) { |
| type = 5; /* LE32 ABIv1 */ |
| } |
| break; |
| case 2: |
| *entry = elf_load_segments64(file_addr, 0, pre_load, post_load); |
| if (ehdr->ei_data != ELFDATA2MSB) { |
| uint32_t flags = elf_get_eflags_64(file_addr); |
| if ((flags & 0x3) == 2) |
| type = 4; /* LE64 ABIv2 */ |
| else |
| type = 3; /* LE64 ABIv1 */ |
| } |
| break; |
| } |
| if (*entry == 0) |
| type = 0; |
| |
| return type; |
| } |
| |
| |
| /** |
| * load_elf_file_to_addr loads an ELF file to given address. |
| * This is useful for 64-bit vmlinux images that use the virtual entry |
| * point address in their headers, and thereby need a special treatment. |
| * |
| * @param file_addr pointer to the start of the elf file |
| * @param entry pointer where the ELF loader will store |
| * the entry point |
| * @param pre_load handler that is called before copying a segment |
| * @param post_load handler that is called after copying a segment |
| * @return 1 for a 32 bit file |
| * 2 for a 64 bit file |
| * anything else means an error during load |
| */ |
| int |
| elf_load_file_to_addr(void *file_addr, void *addr, unsigned long *entry, |
| int (*pre_load)(void*, long), |
| void (*post_load)(void*, long)) |
| { |
| int type; |
| long offset; |
| struct ehdr *ehdr = (struct ehdr *) file_addr; |
| |
| type = elf_check_file(file_addr); |
| |
| switch (type) { |
| case 1: |
| /* Parse 32-bit image */ |
| offset = (long)addr - elf_get_base_addr32(file_addr); |
| *entry = elf_load_segments32(file_addr, offset, pre_load, |
| post_load) + offset; |
| // TODO: elf_relocate32(...) |
| break; |
| case 2: |
| /* Parse 64-bit image */ |
| offset = (long)addr - elf_get_base_addr64(file_addr); |
| *entry = elf_load_segments64(file_addr, offset, pre_load, |
| post_load) + offset; |
| elf_relocate64(file_addr, offset); |
| if (ehdr->ei_data != ELFDATA2MSB) { |
| uint32_t flags = elf_get_eflags_64(file_addr); |
| if ((flags & 0x3) == 2) |
| type = 4; /* LE64 ABIv2 */ |
| else |
| type = 3; /* LE64 ABIv1 */ |
| } |
| break; |
| } |
| |
| return type; |
| } |
| |
| |
| /** |
| * Get the base load address of the ELF image |
| * @return The base address or -1 for error |
| */ |
| long |
| elf_get_base_addr(void *file_addr) |
| { |
| int type; |
| |
| type = elf_check_file(file_addr); |
| |
| switch (type) { |
| case 1: |
| /* Return 32-bit image base address */ |
| return elf_get_base_addr32(file_addr); |
| break; |
| case 2: |
| /* Return 64-bit image base address */ |
| return elf_get_base_addr64(file_addr); |
| break; |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * Get the file size of the ELF image that has been loaded into a |
| * buffer larger than the size of the file |
| * @return The size of the ELF image or < 0 for error |
| */ |
| long elf_get_file_size(const void *buffer, const unsigned long buffer_size) |
| { |
| const struct ehdr *ehdr = (const struct ehdr *)buffer; |
| |
| if (buffer_size < sizeof(struct ehdr)) |
| return -1; |
| |
| /* check if it is an ELF image at all */ |
| if (cpu_to_be32(ehdr->ei_ident) != 0x7f454c46) |
| return -1; |
| |
| switch (ehdr->ei_class) { |
| case 1: |
| return elf_get_file_size32(buffer, buffer_size); |
| case 2: |
| return elf_get_file_size64(buffer, buffer_size); |
| } |
| |
| return -1; |
| } |