summaryrefslogtreecommitdiffstats
path: root/kexec/kexec-elf-rel.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--kexec/kexec-elf-rel.c553
1 files changed, 553 insertions, 0 deletions
diff --git a/kexec/kexec-elf-rel.c b/kexec/kexec-elf-rel.c
new file mode 100644
index 0000000..0a8b4d2
--- /dev/null
+++ b/kexec/kexec-elf-rel.c
@@ -0,0 +1,553 @@
+#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 size_t elf_sym_size(struct mem_ehdr *ehdr)
+{
+ size_t sym_size = 0;
+ if (ehdr->ei_class == ELFCLASS32) {
+ sym_size = sizeof(Elf32_Sym);
+ }
+ else if (ehdr->ei_class == ELFCLASS64) {
+ sym_size = sizeof(Elf64_Sym);
+ }
+ else {
+ die("Bad elf class");
+ }
+ return sym_size;
+}
+
+static size_t elf_rel_size(struct mem_ehdr *ehdr)
+{
+ size_t rel_size = 0;
+ if (ehdr->ei_class == ELFCLASS32) {
+ rel_size = sizeof(Elf32_Rel);
+ }
+ else if (ehdr->ei_class == ELFCLASS64) {
+ rel_size = sizeof(Elf64_Rel);
+ }
+ else {
+ die("Bad elf class");
+ }
+ return rel_size;
+}
+
+static size_t elf_rela_size(struct mem_ehdr *ehdr)
+{
+ size_t rel_size = 0;
+ if (ehdr->ei_class == ELFCLASS32) {
+ rel_size = sizeof(Elf32_Rela);
+ }
+ else if (ehdr->ei_class == ELFCLASS64) {
+ rel_size = sizeof(Elf64_Rela);
+ }
+ else {
+ die("Bad elf class");
+ }
+ return rel_size;
+}
+
+static struct mem_sym elf_sym(struct mem_ehdr *ehdr, const unsigned char *ptr)
+{
+ struct mem_sym sym = { 0, 0, 0, 0, 0, 0 };
+ if (ehdr->ei_class == ELFCLASS32) {
+ Elf32_Sym lsym;
+ memcpy(&lsym, ptr, sizeof(lsym));
+ sym.st_name = elf32_to_cpu(ehdr, lsym.st_name);
+ sym.st_value = elf32_to_cpu(ehdr, lsym.st_value);
+ sym.st_size = elf32_to_cpu(ehdr, lsym.st_size);
+ sym.st_info = lsym.st_info;
+ sym.st_other = lsym.st_other;
+ sym.st_shndx = elf16_to_cpu(ehdr, lsym.st_shndx);
+ }
+ else if (ehdr->ei_class == ELFCLASS64) {
+ Elf64_Sym lsym;
+ memcpy(&lsym, ptr, sizeof(lsym));
+ sym.st_name = elf32_to_cpu(ehdr, lsym.st_name);
+ sym.st_value = elf64_to_cpu(ehdr, lsym.st_value);
+ sym.st_size = elf64_to_cpu(ehdr, lsym.st_size);
+ sym.st_info = lsym.st_info;
+ sym.st_other = lsym.st_other;
+ sym.st_shndx = elf16_to_cpu(ehdr, lsym.st_shndx);
+ }
+ else {
+ die("Bad elf class");
+ }
+ return sym;
+}
+
+static struct mem_rela elf_rel(struct mem_ehdr *ehdr, const unsigned char *ptr)
+{
+ struct mem_rela rela = { 0, 0, 0, 0 };
+ if (ehdr->ei_class == ELFCLASS32) {
+ Elf32_Rel lrel;
+ memcpy(&lrel, ptr, sizeof(lrel));
+ rela.r_offset = elf32_to_cpu(ehdr, lrel.r_offset);
+ rela.r_sym = ELF32_R_SYM(elf32_to_cpu(ehdr, lrel.r_info));
+ rela.r_type = ELF32_R_TYPE(elf32_to_cpu(ehdr, lrel.r_info));
+ rela.r_addend = 0;
+ }
+ else if (ehdr->ei_class == ELFCLASS64) {
+ Elf64_Rel lrel;
+ memcpy(&lrel, ptr, sizeof(lrel));
+ rela.r_offset = elf64_to_cpu(ehdr, lrel.r_offset);
+ rela.r_sym = ELF64_R_SYM(elf64_to_cpu(ehdr, lrel.r_info));
+ rela.r_type = ELF64_R_TYPE(elf64_to_cpu(ehdr, lrel.r_info));
+ rela.r_addend = 0;
+ }
+ else {
+ die("Bad elf class");
+ }
+ return rela;
+}
+
+static struct mem_rela elf_rela(struct mem_ehdr *ehdr, const unsigned char *ptr)
+{
+ struct mem_rela rela = { 0, 0, 0, 0 };
+ if (ehdr->ei_class == ELFCLASS32) {
+ Elf32_Rela lrela;
+ memcpy(&lrela, ptr, sizeof(lrela));
+ rela.r_offset = elf32_to_cpu(ehdr, lrela.r_offset);
+ rela.r_sym = ELF32_R_SYM(elf32_to_cpu(ehdr, lrela.r_info));
+ rela.r_type = ELF32_R_TYPE(elf32_to_cpu(ehdr, lrela.r_info));
+ rela.r_addend = elf32_to_cpu(ehdr, lrela.r_addend);
+ }
+ else if (ehdr->ei_class == ELFCLASS64) {
+ Elf64_Rela lrela;
+ memcpy(&lrela, ptr, sizeof(lrela));
+ rela.r_offset = elf64_to_cpu(ehdr, lrela.r_offset);
+ rela.r_sym = ELF64_R_SYM(elf64_to_cpu(ehdr, lrela.r_info));
+ rela.r_type = ELF64_R_TYPE(elf64_to_cpu(ehdr, lrela.r_info));
+ rela.r_addend = elf64_to_cpu(ehdr, lrela.r_addend);
+ }
+ else {
+ die("Bad elf class");
+ }
+ return rela;
+}
+
+int build_elf_rel_info(const char *buf, off_t len, struct mem_ehdr *ehdr,
+ uint32_t flags)
+{
+ int result;
+ result = build_elf_info(buf, len, ehdr, flags);
+ if (result < 0) {
+ return result;
+ }
+ if (ehdr->e_type != ET_REL) {
+ /* not an ELF relocate object */
+ if (probe_debug) {
+ fprintf(stderr, "Not ELF type ET_REL\n");
+ fprintf(stderr, "ELF Type: %x\n", ehdr->e_type);
+ }
+ return -1;
+ }
+ if (!ehdr->e_shdr) {
+ /* No section headers */
+ if (probe_debug) {
+ fprintf(stderr, "No ELF section headers\n");
+ }
+ return -1;
+ }
+ if (!machine_verify_elf_rel(ehdr)) {
+ /* It does not meant the native architecture constraints */
+ if (probe_debug) {
+ fprintf(stderr, "ELF architecture constraint failure\n");
+ }
+ return -1;
+ }
+ return 0;
+}
+
+static unsigned long get_section_addralign(struct mem_shdr *shdr)
+{
+ return (shdr->sh_addralign == 0) ? 1 : shdr->sh_addralign;
+}
+
+int elf_rel_load(struct mem_ehdr *ehdr, struct kexec_info *info,
+ unsigned long min, unsigned long max, int end)
+{
+ struct mem_shdr *shdr, *shdr_end, *entry_shdr;
+ unsigned long entry;
+ int result;
+ unsigned char *buf;
+ unsigned long buf_align, bufsz, bss_align, bsssz, bss_pad;
+ unsigned long buf_addr, data_addr, bss_addr;
+
+ if (max > elf_max_addr(ehdr)) {
+ max = elf_max_addr(ehdr);
+ }
+ if (!ehdr->e_shdr) {
+ fprintf(stderr, "No section header?\n");
+ result = -1;
+ goto out;
+ }
+ shdr_end = &ehdr->e_shdr[ehdr->e_shnum];
+
+ /* Find which section entry is in */
+ entry_shdr = NULL;
+ entry = ehdr->e_entry;
+ for(shdr = ehdr->e_shdr; shdr != shdr_end; shdr++) {
+ if (!(shdr->sh_flags & SHF_ALLOC)) {
+ continue;
+ }
+ if (!(shdr->sh_flags & SHF_EXECINSTR)) {
+ continue;
+ }
+ /* Make entry section relative */
+ if ((shdr->sh_addr <= ehdr->e_entry) &&
+ ((shdr->sh_addr + shdr->sh_size) > ehdr->e_entry)) {
+ entry_shdr = shdr;
+ entry -= shdr->sh_addr;
+ break;
+ }
+ }
+
+ /* Find the memory footprint of the relocatable object */
+ buf_align = 1;
+ bss_align = 1;
+ bufsz = 0;
+ bsssz = 0;
+ for(shdr = ehdr->e_shdr; shdr != shdr_end; shdr++) {
+ if (!(shdr->sh_flags & SHF_ALLOC)) {
+ continue;
+ }
+ if (shdr->sh_type != SHT_NOBITS) {
+ unsigned long align;
+ align = get_section_addralign(shdr);
+ /* See if I need more alignment */
+ if (buf_align < align) {
+ buf_align = align;
+ }
+ /* Now align bufsz */
+ bufsz = _ALIGN(bufsz, align);
+ /* And now add our buffer */
+ bufsz += shdr->sh_size;
+ }
+ else {
+ unsigned long align;
+ align = get_section_addralign(shdr);
+ /* See if I need more alignment */
+ if (bss_align < align) {
+ bss_align = align;
+ }
+ /* Now align bsssz */
+ bsssz = _ALIGN(bsssz, align);
+ /* And now add our buffer */
+ bsssz += shdr->sh_size;
+ }
+ }
+ if (buf_align < bss_align) {
+ buf_align = bss_align;
+ }
+ bss_pad = 0;
+ if (bufsz & (bss_align - 1)) {
+ bss_pad = bss_align - (bufsz & (bss_align - 1));
+ }
+
+ /* Allocate where we will put the relocated object */
+ buf = xmalloc(bufsz);
+ buf_addr = add_buffer(info, buf, bufsz, bufsz + bss_pad + bsssz,
+ buf_align, min, max, end);
+ ehdr->rel_addr = buf_addr;
+ ehdr->rel_size = bufsz + bss_pad + bsssz;
+
+ /* Walk through and find an address for each SHF_ALLOC section */
+ data_addr = buf_addr;
+ bss_addr = buf_addr + bufsz + bss_pad;
+ for(shdr = ehdr->e_shdr; shdr != shdr_end; shdr++) {
+ unsigned long align;
+ if (!(shdr->sh_flags & SHF_ALLOC)) {
+ continue;
+ }
+ align = get_section_addralign(shdr);
+ if (shdr->sh_type != SHT_NOBITS) {
+ unsigned long off;
+ /* Adjust the address */
+ data_addr = _ALIGN(data_addr, align);
+
+ /* Update the section */
+ off = data_addr - buf_addr;
+ memcpy(buf + off, shdr->sh_data, shdr->sh_size);
+ shdr->sh_addr = data_addr;
+ shdr->sh_data = buf + off;
+
+ /* Advance to the next address */
+ data_addr += shdr->sh_size;
+ } else {
+ /* Adjust the address */
+ bss_addr = _ALIGN(bss_addr, align);
+
+ /* Update the section */
+ shdr->sh_addr = bss_addr;
+
+ /* Advance to the next address */
+ bss_addr += shdr->sh_size;
+ }
+ }
+ /* Compute the relocated value for entry, and load it */
+ if (entry_shdr) {
+ entry += entry_shdr->sh_addr;
+ ehdr->e_entry = entry;
+ }
+ info->entry = (void *)entry;
+
+ /* Now that the load address is known apply relocations */
+ for(shdr = ehdr->e_shdr; shdr != shdr_end; shdr++) {
+ struct mem_shdr *section, *symtab;
+ const unsigned char *strtab;
+ size_t rel_size;
+ const unsigned char *ptr, *rel_end;
+ if ((shdr->sh_type != SHT_RELA) && (shdr->sh_type != SHT_REL)) {
+ continue;
+ }
+ if ((shdr->sh_info > ehdr->e_shnum) ||
+ (shdr->sh_link > ehdr->e_shnum))
+ {
+ die("Invalid section number\n");
+ }
+ section = &ehdr->e_shdr[shdr->sh_info];
+ symtab = &ehdr->e_shdr[shdr->sh_link];
+
+ if (!(section->sh_flags & SHF_ALLOC)) {
+ continue;
+ }
+
+ if (symtab->sh_link > ehdr->e_shnum) {
+ /* Invalid section number? */
+ continue;
+ }
+ strtab = ehdr->e_shdr[symtab->sh_link].sh_data;
+
+ rel_size = 0;
+ if (shdr->sh_type == SHT_REL) {
+ rel_size = elf_rel_size(ehdr);
+ }
+ else if (shdr->sh_type == SHT_RELA) {
+ rel_size = elf_rela_size(ehdr);
+ }
+ else {
+ die("Cannot find elf rel size\n");
+ }
+ rel_end = shdr->sh_data + shdr->sh_size;
+ for(ptr = shdr->sh_data; ptr < rel_end; ptr += rel_size) {
+ struct mem_rela rel = {0};
+ struct mem_sym sym;
+ const void *location;
+ const unsigned char *name;
+ unsigned long address, value, sec_base;
+ if (shdr->sh_type == SHT_REL) {
+ rel = elf_rel(ehdr, ptr);
+ }
+ else if (shdr->sh_type == SHT_RELA) {
+ rel = elf_rela(ehdr, ptr);
+ }
+ /* the location to change */
+ location = section->sh_data + rel.r_offset;
+
+ /* The final address of that location */
+ address = section->sh_addr + rel.r_offset;
+
+ /* The relevant symbol */
+ sym = elf_sym(ehdr, symtab->sh_data + (rel.r_sym * elf_sym_size(ehdr)));
+
+ if (sym.st_name) {
+ name = strtab + sym.st_name;
+ }
+ else {
+ name = ehdr->e_shdr[ehdr->e_shstrndx].sh_data;
+ name += ehdr->e_shdr[sym.st_shndx].sh_name;
+ }
+
+ dbgprintf("sym: %10s info: %02x other: %02x shndx: %x value: %llx size: %llx\n",
+ name,
+ sym.st_info,
+ sym.st_other,
+ sym.st_shndx,
+ sym.st_value,
+ sym.st_size);
+
+ if (sym.st_shndx == STN_UNDEF) {
+ /*
+ * NOTE: ppc64 elf .ro shows up a UNDEF section.
+ * From Elf 1.2 Spec:
+ * Relocation Entries: If the index is STN_UNDEF,
+ * the undefined symbol index, the relocation uses 0
+ * as the "symbol value".
+ * TOC symbols appear as undefined but should be
+ * resolved as well. Their type is STT_NOTYPE so allow
+ * such symbols to be processed.
+ */
+ if (ELF32_ST_TYPE(sym.st_info) != STT_NOTYPE)
+ die("Undefined symbol: %s\n", name);
+ }
+ sec_base = 0;
+ if (sym.st_shndx == SHN_COMMON) {
+ die("symbol: '%s' in common section\n",
+ name);
+ }
+ else if (sym.st_shndx == SHN_ABS) {
+ sec_base = 0;
+ }
+ else if (sym.st_shndx > ehdr->e_shnum) {
+ die("Invalid section: %d for symbol %s\n",
+ sym.st_shndx, name);
+ }
+ else {
+ sec_base = ehdr->e_shdr[sym.st_shndx].sh_addr;
+ }
+ value = sym.st_value;
+ value += sec_base;
+ value += rel.r_addend;
+
+ dbgprintf("sym: %s value: %lx addr: %lx\n",
+ name, value, address);
+
+ machine_apply_elf_rel(ehdr, &sym, rel.r_type,
+ (void *)location, address, value);
+ }
+ }
+ result = 0;
+ out:
+ return result;
+}
+
+void elf_rel_build_load(struct kexec_info *info, struct mem_ehdr *ehdr,
+ const char *buf, off_t len, unsigned long min, unsigned long max,
+ int end, uint32_t flags)
+{
+ int result;
+
+ /* Parse the Elf file */
+ result = build_elf_rel_info(buf, len, ehdr, flags);
+ if (result < 0) {
+ die("ELF rel parse failed\n");
+ }
+ /* Load the Elf data */
+ result = elf_rel_load(ehdr, info, min, max, end);
+ if (result < 0) {
+ die("ELF rel load failed\n");
+ }
+}
+
+int elf_rel_find_symbol(struct mem_ehdr *ehdr,
+ const char *name, struct mem_sym *ret_sym)
+{
+ struct mem_shdr *shdr, *shdr_end;
+
+ if (!ehdr->e_shdr) {
+ /* "No section header? */
+ return -1;
+ }
+ /* Walk through the sections and find the symbol table */
+ shdr_end = &ehdr->e_shdr[ehdr->e_shnum];
+ for (shdr = ehdr->e_shdr; shdr != shdr_end; shdr++) {
+ const char *strtab;
+ size_t sym_size;
+ const unsigned char *ptr, *sym_end;
+ if (shdr->sh_type != SHT_SYMTAB) {
+ continue;
+ }
+ if (shdr->sh_link > ehdr->e_shnum) {
+ /* Invalid strtab section number? */
+ continue;
+ }
+ strtab = (char *)ehdr->e_shdr[shdr->sh_link].sh_data;
+ /* Walk through the symbol table and find the symbol */
+ sym_size = elf_sym_size(ehdr);
+ sym_end = shdr->sh_data + shdr->sh_size;
+ for(ptr = shdr->sh_data; ptr < sym_end; ptr += sym_size) {
+ struct mem_sym sym;
+ sym = elf_sym(ehdr, ptr);
+ if (ELF32_ST_BIND(sym.st_info) != STB_GLOBAL) {
+ continue;
+ }
+ if (strcmp(strtab + sym.st_name, name) != 0) {
+ continue;
+ }
+ if ((sym.st_shndx == STN_UNDEF) ||
+ (sym.st_shndx > ehdr->e_shnum))
+ {
+ die("Symbol: %s has Bad section index %d\n",
+ name, sym.st_shndx);
+ }
+ *ret_sym = sym;
+ return 0;
+ }
+ }
+ /* I did not find it :( */
+ return -1;
+
+}
+
+unsigned long elf_rel_get_addr(struct mem_ehdr *ehdr, const char *name)
+{
+ struct mem_shdr *shdr;
+ struct mem_sym sym;
+ int result;
+ result = elf_rel_find_symbol(ehdr, name, &sym);
+ if (result < 0) {
+ die("Symbol: %s not found cannot retrive it's address\n",
+ name);
+ }
+ shdr = &ehdr->e_shdr[sym.st_shndx];
+ return shdr->sh_addr + sym.st_value;
+}
+
+void elf_rel_set_symbol(struct mem_ehdr *ehdr,
+ const char *name, const void *buf, size_t size)
+{
+ unsigned char *sym_buf;
+ struct mem_shdr *shdr;
+ struct mem_sym sym;
+ int result;
+
+ result = elf_rel_find_symbol(ehdr, name, &sym);
+ if (result < 0) {
+ die("Symbol: %s not found cannot set\n",
+ name);
+ }
+ if (sym.st_size != size) {
+ die("Symbol: %s has size: %lld not %zd\n",
+ name, sym.st_size, size);
+ }
+ shdr = &ehdr->e_shdr[sym.st_shndx];
+ if (shdr->sh_type == SHT_NOBITS) {
+ die("Symbol: %s is in a bss section cannot set\n", name);
+ }
+ sym_buf = (unsigned char *)(shdr->sh_data + sym.st_value);
+ memcpy(sym_buf, buf, size);
+}
+
+void elf_rel_get_symbol(struct mem_ehdr *ehdr,
+ const char *name, void *buf, size_t size)
+{
+ const unsigned char *sym_buf;
+ struct mem_shdr *shdr;
+ struct mem_sym sym;
+ int result;
+
+ result = elf_rel_find_symbol(ehdr, name, &sym);
+ if (result < 0) {
+ die("Symbol: %s not found cannot get\n", name);
+ }
+ if (sym.st_size != size) {
+ die("Symbol: %s has size: %lld not %zd\n",
+ name, sym.st_size, size);
+ }
+ shdr = &ehdr->e_shdr[sym.st_shndx];
+ if (shdr->sh_type == SHT_NOBITS) {
+ die("Symbol: %s is in a bss section cannot set\n", name);
+ }
+ sym_buf = shdr->sh_data + sym.st_value;
+ memcpy(buf, sym_buf,size);
+}