blob: 0a8b4d2699d1017e8964c1cc0b0e9c8435264d9c [file] [log] [blame]
#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);
}