#include #include #include #include #include #include #include "elf.h" #include #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); }