diff options
Diffstat (limited to 'kexec/kexec-elf-exec.c')
-rw-r--r-- | kexec/kexec-elf-exec.c | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/kexec/kexec-elf-exec.c b/kexec/kexec-elf-exec.c new file mode 100644 index 0000000..bea7b3e --- /dev/null +++ b/kexec/kexec-elf-exec.c @@ -0,0 +1,228 @@ +#include <limits.h> +#include <stdint.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include "elf.h" +#include <boot/elf_boot.h> +#include "kexec.h" +#include "kexec-elf.h" + +static const int probe_debug = 0; + +static void load_elf_segments(struct mem_ehdr *ehdr, struct kexec_info *info, unsigned long base) +{ + size_t i; + + /* Read in the PT_LOAD segments */ + for(i = 0; i < ehdr->e_phnum; i++) { + struct mem_phdr *phdr; + size_t size; + phdr = &ehdr->e_phdr[i]; + if (phdr->p_type != PT_LOAD) { + continue; + } + size = phdr->p_filesz; + if (size > phdr->p_memsz) { + size = phdr->p_memsz; + } + add_segment(info, phdr->p_data, size, + phdr->p_paddr + base, phdr->p_memsz); + } +} + +static int get_elf_exec_load_base(struct mem_ehdr *ehdr, struct kexec_info *info, + unsigned long min, unsigned long max, + unsigned long align, unsigned long *base) +{ + unsigned long first, last; + size_t i; + + /* Note on arm64: + * arm64's vmlinux has virtual address in physical address + * field of PT_LOAD segments. So the following validity check + * and relocation makes no sense on arm64. + */ + if (ehdr->e_machine == EM_AARCH64) + return 0; + + first = ULONG_MAX; + last = 0; + for(i = 0; i < ehdr->e_phnum; i++) { + unsigned long start, stop; + struct mem_phdr *phdr; + phdr = &ehdr->e_phdr[i]; + if ((phdr->p_type != PT_LOAD) || + (phdr->p_memsz == 0)) + { + continue; + } + start = phdr->p_paddr; + stop = start + phdr->p_memsz; + if (first > start) { + first = start; + } + if (last < stop) { + last = stop; + } + if (align < phdr->p_align) { + align = phdr->p_align; + } + } + + if ((max - min) < (last - first)) + return -1; + + if (!valid_memory_range(info, min > first ? min : first, max < last ? max : last)) { + unsigned long hole; + hole = locate_hole(info, last - first + 1, align, min, max, 1); + if (hole == ULONG_MAX) + return -1; + + /* Base is the value that when added + * to any virtual address in the file + * yields it's load virtual address. + */ + *base = hole - first; + } + return 0; +} + +int build_elf_exec_info(const char *buf, off_t len, struct mem_ehdr *ehdr, + uint32_t flags) +{ + struct mem_phdr *phdr, *end_phdr; + int result; + result = build_elf_info(buf, len, ehdr, flags); + if (result < 0) { + return result; + } + if ((ehdr->e_type != ET_EXEC) && (ehdr->e_type != ET_DYN) && + (ehdr->e_type != ET_CORE)) { + /* not an ELF executable */ + if (probe_debug) { + fprintf(stderr, "Not ELF type ET_EXEC or ET_DYN\n"); + } + return -1; + } + if (!ehdr->e_phdr) { + /* No program header */ + fprintf(stderr, "No ELF program header\n"); + return -1; + } + end_phdr = &ehdr->e_phdr[ehdr->e_phnum]; + for(phdr = ehdr->e_phdr; phdr != end_phdr; phdr++) { + /* Kexec does not support loading interpreters. + * In addition this check keeps us from attempting + * to kexec ordinay executables. + */ + if (phdr->p_type == PT_INTERP) { + fprintf(stderr, "Requires an ELF interpreter\n"); + return -1; + } + } + + return 0; +} + + +int elf_exec_load(struct mem_ehdr *ehdr, struct kexec_info *info) +{ + unsigned long base; + int result; + + if (!ehdr->e_phdr) { + fprintf(stderr, "No program header?\n"); + result = -1; + goto out; + } + + /* If I have a dynamic executable find it's size + * and then find a location for it in memory. + */ + base = 0; + if (ehdr->e_type == ET_DYN) { + result = get_elf_exec_load_base(ehdr, info, 0, elf_max_addr(ehdr), 0 /* align */, &base); + if (result < 0) + goto out; + } + + load_elf_segments(ehdr, info, base); + + /* Update entry point to reflect new load address*/ + ehdr->e_entry += base; + + result = 0; + out: + return result; +} + +int elf_exec_load_relocatable(struct mem_ehdr *ehdr, struct kexec_info *info, + unsigned long reloc_min, unsigned long reloc_max, + unsigned long align) +{ + unsigned long base; + int result; + + if (reloc_min > reloc_max) { + fprintf(stderr, "Bad relocation range, start=%lux > end=%lux.\n", reloc_min, reloc_max); + result = -1; + goto out; + } + if (!ehdr->e_phdr) { + fprintf(stderr, "No program header?\n"); + result = -1; + goto out; + } + + base = 0; + result = get_elf_exec_load_base(ehdr, info, reloc_min, reloc_max, align, &base); + if (result < 0) + goto out; + + load_elf_segments(ehdr, info, base); + + /* Update entry point to reflect new load address*/ + ehdr->e_entry += base; + + result = 0; + out: + return result; +} + +void elf_exec_build_load(struct kexec_info *info, struct mem_ehdr *ehdr, + const char *buf, off_t len, uint32_t flags) +{ + int result; + /* Parse the Elf file */ + result = build_elf_exec_info(buf, len, ehdr, flags); + if (result < 0) { + die("ELF exec parse failed\n"); + } + + /* Load the Elf data */ + result = elf_exec_load(ehdr, info); + if (result < 0) { + die("ELF exec load failed\n"); + } +} + +void elf_exec_build_load_relocatable(struct kexec_info *info, struct mem_ehdr *ehdr, + const char *buf, off_t len, uint32_t flags, + unsigned long reloc_min, unsigned long reloc_max, + unsigned long align) +{ + int result; + /* Parse the Elf file */ + result = build_elf_exec_info(buf, len, ehdr, flags); + if (result < 0) { + die("%s: ELF exec parse failed\n", __func__); + } + + /* Load the Elf data */ + result = elf_exec_load_relocatable(ehdr, info, reloc_min, reloc_max, align); + if (result < 0) { + die("%s: ELF exec load failed\n", __func__); + } +}
\ No newline at end of file |