blob: c0ff0f224f4e208ba5d6ae9997ea868ea195ace2 [file] [log] [blame]
/*
* Copyright (C) 2002 Richard Henderson
* Copyright (C) 2001 Rusty Russell, 2002 Rusty Russell IBM.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <common.h>
#include <errno.h>
#include <module.h>
#include <elf.h>
#include <malloc.h>
#include <xfuncs.h>
#include <command.h>
#include <fs.h>
#include <kallsyms.h>
#include <linux/list.h>
static unsigned int find_sec(Elf_Ehdr *hdr,
Elf_Shdr *sechdrs,
const char *secstrings,
const char *name)
{
unsigned int i;
for (i = 1; i < hdr->e_shnum; i++) {
debug("find_sec: %s %s\n", name, secstrings+sechdrs[i].sh_name);
/* Alloc bit cleared means "ignore it." */
if ((sechdrs[i].sh_flags & SHF_ALLOC) &&
!strcmp(secstrings+sechdrs[i].sh_name, name))
return i;
}
return 0;
}
/* Provided by the linker */
extern const struct kernel_symbol __usymtab_start[];
extern const struct kernel_symbol __usymtab_end[];
/* lookup symbol in given range of kernel_symbols */
static const struct kernel_symbol *lookup_symbol(const char *name,
const struct kernel_symbol *start,
const struct kernel_symbol *stop)
{
const struct kernel_symbol *ks = start;
for (; ks < stop; ks++) {
if (strcmp(ks->name, name) == 0)
return ks;
}
return NULL;
}
static unsigned long resolve_symbol(Elf32_Shdr *sechdrs,
const char *name)
{
const struct kernel_symbol *ks;
debug("%s: %s\n", __FUNCTION__, name);
ks = lookup_symbol(name, __usymtab_start,
__usymtab_end);
if (ks)
return ks->value;
return 0;
}
/* Change all symbols so that sh_value encodes the pointer directly. */
static int simplify_symbols(Elf32_Shdr *sechdrs,
unsigned int symindex,
const char *strtab)
{
Elf32_Sym *sym = (void *)sechdrs[symindex].sh_addr;
unsigned long secbase;
unsigned int i, n = sechdrs[symindex].sh_size / sizeof(Elf32_Sym);
int ret = 0;
for (i = 1; i < n; i++) {
switch (sym[i].st_shndx) {
case SHN_COMMON:
/* We compiled with -fno-common. These are not
supposed to happen. */
printf("Common symbol: %s\n", strtab + sym[i].st_name);
printf("please compile with -fno-common\n");
ret = -1;
break;
case SHN_ABS:
/* Don't need to do anything */
debug("Absolute symbol: 0x%08lx\n",
(long)sym[i].st_value);
break;
case SHN_UNDEF:
sym[i].st_value
= resolve_symbol(sechdrs,
strtab + sym[i].st_name);
debug("undef : %20s 0x%08x 0x%08lx\n", strtab + sym[i].st_name, sym[i].st_value);
/* Ok if resolved. */
if (sym[i].st_value != 0)
break;
/* Ok if weak. */
if (ELF32_ST_BIND(sym[i].st_info) == STB_WEAK)
break;
printf("Unknown symbol %s\n",
strtab + sym[i].st_name);
ret = -1;
break;
default:
secbase = sechdrs[sym[i].st_shndx].sh_addr;
debug("default: %20s 0x%08x 0x%08lx\n", strtab + sym[i].st_name, sym[i].st_value, secbase);
sym[i].st_value += secbase;
break;
}
}
return ret;
}
#define ALIGN(x,a) __ALIGN_MASK(x,(typeof(x))(a)-1)
#define __ALIGN_MASK(x,mask) (((x)+(mask))&~(mask))
/* Update size with this section: return offset. */
static long get_offset(unsigned long *size, Elf32_Shdr *sechdr)
{
long ret;
ret = ALIGN(*size, sechdr->sh_addralign ?: 1);
*size = ret + sechdr->sh_size;
return ret;
}
static void layout_sections( struct module *mod,
const Elf32_Ehdr *hdr,
Elf32_Shdr *sechdrs,
const char *secstrings)
{
static unsigned long const masks[][2] = {
/* NOTE: all executable code must be the first section
* in this array; otherwise modify the text_size
* finder in the two loops below */
{ SHF_EXECINSTR | SHF_ALLOC },
{ SHF_ALLOC, SHF_WRITE },
{ SHF_WRITE | SHF_ALLOC },
{ SHF_ALLOC, 0 }
};
unsigned int m, i;
for (i = 0; i < hdr->e_shnum; i++)
sechdrs[i].sh_entsize = ~0UL;
debug("Core section allocation order:\n");
for (m = 0; m < ARRAY_SIZE(masks); ++m) {
for (i = 0; i < hdr->e_shnum; ++i) {
Elf32_Shdr *s = &sechdrs[i];
if ((s->sh_flags & masks[m][0]) != masks[m][0]
|| (s->sh_flags & masks[m][1])
|| s->sh_entsize != ~0UL
|| strncmp(secstrings + s->sh_name,
".init", 5) == 0)
continue;
s->sh_entsize = get_offset(&mod->core_size, s);
debug("\t%s 0x%08x\n", secstrings + s->sh_name, s->sh_entsize);
}
}
debug("core_size: %ld\n", mod->core_size);
}
LIST_HEAD(module_list);
struct module * load_module(void *mod_image, unsigned long len)
{
struct module *module = NULL;
Elf32_Ehdr *ehdr; /* Elf header structure pointer */
Elf32_Shdr *sechdrs; /* Section header structure pointer */
Elf32_Sym *sym;
unsigned int numsyms;
char *strtab = 0; /* String table pointer */
int i; /* Loop counter */
unsigned int strindex = 0;
unsigned int symindex = 0;
unsigned int modindex;
char *secstrings;
void *ptr = NULL;
int err;
int cmdindex;
if (len < sizeof(*ehdr))
return NULL;
ehdr = (Elf32_Ehdr *)mod_image;
if (len < ehdr->e_shoff + ehdr->e_shnum * sizeof(Elf_Shdr))
return NULL;
/* Find the section header string table for output info */
sechdrs = (Elf32_Shdr *) (mod_image + ehdr->e_shoff +
(ehdr->e_shstrndx * sizeof (Elf32_Shdr)));
if (sechdrs->sh_type == SHT_SYMTAB)
strtab = (char *) (mod_image + sechdrs->sh_offset);
/* Convenience variables */
sechdrs = (void *)ehdr + ehdr->e_shoff;
secstrings = (void *)ehdr + sechdrs[ehdr->e_shstrndx].sh_offset;
sechdrs[0].sh_addr = 0;
for (i = 0; i < ehdr->e_shnum; i++) {
debug("%d addr: 0x%08x size: 0x%08x ofs: 0x%08x\n",
i, sechdrs[i].sh_addr, sechdrs[i].sh_size, sechdrs[i].sh_offset);
/* Mark all sections sh_addr with their address in the
temporary image. */
sechdrs[i].sh_addr = (size_t)ehdr + sechdrs[i].sh_offset;
if (sechdrs[i].sh_type == SHT_SYMTAB) {
symindex = i;
strindex = sechdrs[i].sh_link;
strtab = mod_image + sechdrs[strindex].sh_offset;
}
}
modindex = find_sec(ehdr, sechdrs, secstrings,
".gnu.linkonce.this_module");
if (!modindex) {
printf("No module found in object\n");
err = -ENOEXEC;
goto cleanup;
}
module = (void *)sechdrs[modindex].sh_addr;
if (symindex == 0) {
printf("module has no symbols (stripped?)\n");
err = -ENOEXEC;
goto cleanup;
}
/* Determine total sizes, and put offsets in sh_entsize. For now
this is done generically; there doesn't appear to be any
special cases for the architectures. */
layout_sections(module, ehdr, sechdrs, secstrings);
ptr = xzalloc(module->core_size);
module->module_core = ptr;
/* Transfer each section which specifies SHF_ALLOC */
debug("final section addresses:\n");
for (i = 0; i < ehdr->e_shnum; i++) {
void *dest;
if (!(sechdrs[i].sh_flags & SHF_ALLOC))
continue;
dest = module->module_core + sechdrs[i].sh_entsize;
debug("0x%08x dest 0x%p\n", sechdrs[i].sh_addr, dest);
if (sechdrs[i].sh_type != SHT_NOBITS)
memcpy(dest, (void *)sechdrs[i].sh_addr,
sechdrs[i].sh_size);
/* Update sh_addr to point to copy in image. */
sechdrs[i].sh_addr = (unsigned long)dest;
}
/* Fix up syms, so that st_value is a pointer to location. */
err = simplify_symbols(sechdrs, symindex, strtab);
if (err < 0)
goto cleanup;
for (i = 0; i < ehdr->e_shnum; i++) {
if (sechdrs[i].sh_type == SHT_REL) {
apply_relocate(sechdrs, strtab, symindex, i, module);
}
if (sechdrs[i].sh_type == SHT_RELA)
apply_relocate_add(sechdrs, strtab, symindex, i, module);
}
numsyms = sechdrs[symindex].sh_size / sizeof(Elf32_Sym);
sym = (void *)sechdrs[symindex].sh_addr;
cmdindex = find_sec(ehdr, sechdrs, secstrings, ".barebox_cmd");
if (cmdindex) {
struct command *cmd =(struct command *)sechdrs[cmdindex].sh_addr;
for (i = 0; i < sechdrs[cmdindex].sh_size / sizeof(struct command); i++) {
register_command(cmd);
cmd++;
}
}
for (i = 0; i < numsyms; i++) {
if (!strcmp(strtab + sym[i].st_name, MODULE_SYMBOL_PREFIX "init_module")) {
printf("found init_module() at 0x%08x\n", sym[i].st_value);
module->init = (void *)sym[i].st_value;
}
}
list_add_tail(&module->list, &module_list);
return module;
cleanup:
if (ptr)
free(ptr);
if (module)
free(module);
return NULL;
}