/*
 * Copyright (c) 2006, Swedish Institute of Computer Science
 * All rights reserved. 
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions 
 * are met: 
 * 1. Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright 
 *    notice, this list of conditions and the following disclaimer in the 
 *    documentation and/or other materials provided with the distribution. 
 * 3. Neither the name of the Institute nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software 
 *    without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
 * SUCH DAMAGE. 
 *
 */

/*
 * The Contiki dynamic Link Editor (CLE), ELF version.
 */

#include <stdio.h>
#include <string.h>

#include "contiki.h"

#include "loader/elf32.h"
#include "loader/cle.h"
#include "loader/sym.h"

#define NDEBUG
#include "lib/assert.h"

#ifdef NDEBUG
#define PRINTF(...) do {} while (0)
#else
#define PRINTF(...) printf(__VA_ARGS__)
#endif

#define NOLL 0

#ifdef __AVR__
/*
 * On the AVR, GNU C squeezes function addresses into 16 bits. Some of
 * this code is explicitly written to deal with this.
 */
#ifndef __GNUC__
#eror "You lose!!!"
#endif
#endif

/*
 * Parse object file located at offset hdr reading data using function
 * pread. Save what is useful in info.
 */
int
cle_read_info(struct cle_info *info,
	      int (*pread)(void *, int, off_t),
	      off_t hdr)
{
  /*
   * Save stackspace by using a union!
   *
   * Beware that the contents of ehdr is gone when shdr is written!!!
   */
  union {
    struct elf32_ehdr ehdr;
    struct elf32_shdr shdr;
  } huge;
#define ehdr huge.ehdr
#define shdr huge.shdr

  off_t shoff; 
  cle_off strs;
  cle_half shnum;		/* number shdrs */
  cle_half shentsize;		/* sizeof shdr */
  cle_word strtabsize = 0;
  int i, ret;

  memset(info, 0x0, sizeof(*info));

  ret = pread(&ehdr, sizeof(ehdr), hdr);
  assert(ret > 0);

  /* Make sure that we have a correct and compatible ELF header. */
  if(memcmp(ehdr.e_ident, ELF_MAGIC_HEADER, ELF_MAGIC_HEADER_SIZE) != 0) {
    return CLE_BAD_HEADER;
  }

  shoff = hdr + ehdr.e_shoff;
  shentsize = ehdr.e_shentsize;
  shnum = ehdr.e_shnum;

  /* The string table section: holds the names of the sections. */
  ret = pread(&shdr, sizeof(shdr), shoff + shentsize*ehdr.e_shstrndx);
  assert(ret > 0);

  /* BEWARE THAT ehdr IS NOW OVERWRITTEN!!! */

  /*
   * Get a pointer to the actual table of strings. This table holds
   * the names of the sections, not the names of other symbols in the
   * file (these are in the symtab section).
   */
  strs = shdr.sh_offset;

  /*
   * The ".text" segment holds the actual code from the ELF file, the
   * ".data" segment contains initialized data, the ".bss" segment
   * holds the size of the unitialized data segment. The ".rela.text"
   * and ".rela.data" segments contains relocation information for the
   * contents of the ".text" and ".data" segments, respectively. The
   * ".symtab" segment contains the symbol table for this file. The
   * ".strtab" segment points to the actual string names used by the
   * symbol table.
   *
   * In addition to grabbing pointers to the relevant sections, we
   * also save the section index for resolving addresses in the
   * relocator code.
   */
  for(i = 0; i < shnum; ++i) {
    ret = pread(&shdr, sizeof(shdr), shoff);
    assert(ret > 0);
    
    /* The name of the section is contained in the strings table. */
    ret = pread(info->name, sizeof(info->name), hdr + strs + shdr.sh_name);
    assert(ret > 0);

    if(strncmp(info->name, ".text", 5) == 0) {
      info->textoff = shdr.sh_offset;
      info->textsize = shdr.sh_size;
      info->text_shndx = i;
    } else if(strncmp(info->name, ".rela.text", 10) == 0) {
      info->textrelaoff = shdr.sh_offset;
      info->textrelasize = shdr.sh_size;
    } else if(strncmp(info->name, ".data", 5) == 0) {
      info->dataoff = shdr.sh_offset;
      info->datasize = shdr.sh_size;
      info->data_shndx = i;
    } else if(strncmp(info->name, ".rela.data", 10) == 0) {
      info->datarelaoff = shdr.sh_offset;
      info->datarelasize = shdr.sh_size;
    } else if(strncmp(info->name, ".symtab", 7) == 0) {
      info->symtaboff = shdr.sh_offset;
      info->symtabsize = shdr.sh_size;
    } else if(strncmp(info->name, ".strtab", 7) == 0) {
      info->strtaboff = shdr.sh_offset;
      strtabsize = shdr.sh_size;
    } else if(strncmp(info->name, ".bss", 4) == 0) {
      info->bsssize = shdr.sh_size;
      info->bss_shndx = i;
    } else {
      info->name[sizeof(info->name) - 1] = 0;
      PRINTF("cle: unknown section %.12s\n", info->name);
    }

    /* Move on to the next section header. */
    shoff += shentsize;
  }

  if(info->symtabsize == 0) {
    return CLE_NO_SYMTAB;
  }
  if(strtabsize == 0) {
    return CLE_NO_STRTAB;
  }
  if(info->textsize == 0) {
    return CLE_NO_TEXT;
  }

  return CLE_OK;
}

/*
 * Relocate one segment that has been copied to the location pointed
 * to by segmem.
 *
 * Relocation info is read from offset reloff to (reloff + relsize)
 * and the start of the object file is at hdr. Data is read using
 * function pread.
 */
int
cle_relocate(struct cle_info *info,
	     int (*pread)(void *, int, off_t),
	     off_t hdr,		/* Offset to start of file. */
	     void *segmem,      /* Where segment is stored in memory. */
	     cle_off reloff,	/* .rela.<segment> start */
	     cle_word relsize)	/* .rela.<segment> size */
{
  struct elf32_rela rela;
  struct elf32_sym s;
  off_t off;
  cle_addr addr;
  int ret;
  
  for(off = hdr + reloff;
      off < hdr + reloff + relsize;
      off += sizeof(struct elf32_rela)) {
    ret = pread(&rela, sizeof(rela), off);
    assert(ret > 0);
    ret = pread(&s, sizeof(s),
	       hdr + info->symtaboff
	       + sizeof(struct elf32_sym)*ELF32_R_SYM(rela.r_info));
    assert(ret > 0);

    if(s.st_shndx == info->bss_shndx) {
      addr = (cle_addr)(uintptr_t)info->bss;
    } else if(s.st_shndx == info->data_shndx) {
      addr = (cle_addr)(uintptr_t)info->data;
    } else if(s.st_shndx == info->text_shndx) {
      addr = info->text;
    } else {
      addr = NOLL;
    }

    if(s.st_name == 0) {	/* No name, local symbol? */
      if(addr == NOLL) {
	return CLE_UNKNOWN_SEGMENT;
      }
    } else {
      ret = pread(info->name, sizeof(info->name),
		  hdr + info->strtaboff + s.st_name);
      assert(ret > 0);
      cle_addr sym = (cle_addr)(uintptr_t)sym_function(info->name);
#ifdef __AVR__
      if(sym != NOLL)
	sym = sym << 1;
#endif
      if(sym == NOLL)
	sym = (cle_addr)(uintptr_t)sym_object(info->name);

      if(addr == NOLL && sym != NOLL) { /* Imported symbol. */
	addr = sym;
      } else if(addr != NOLL && sym == NOLL) { /* Exported symbol. */
	addr = addr + s.st_value;
      } else if(addr == NOLL && sym == NOLL) {
	PRINTF("cle: undefined reference to %.32s (%d)\n",
	       info->name, s.st_info);
	return CLE_UNDEFINED;	/* Or COMMON symbol. */
      } else if(addr != NOLL && sym != NOLL) {
	PRINTF("cle: multiple definitions of %.32s (%d)\n",
	       info->name, s.st_info);
	return CLE_MULTIPLY_DEFINED;
      }
    }

    addr += rela.r_addend;

    ret = cle_write_reloc(segmem + rela.r_offset, &rela, addr, info);
    if(ret != CLE_OK) {
      return ret;
    }
  }
  return CLE_OK;
}

/*
 * Search object file located at offset hdr using function
 * pread. Search for symbol named symbol and return its address after
 * relocation or NULL on failure.
 */
void *
cle_lookup(struct cle_info *info,
	   int (*pread)(void *, int, off_t),
	   off_t hdr,		/* Offset to start of file. */
	   const char *symbol)

{
  struct elf32_sym s;
  off_t a;
  cle_addr addr;
  int ret;

  for(a = hdr + info->symtaboff;
      a < hdr + info->symtaboff + info->symtabsize;
      a += sizeof(s)) {
    ret = pread(&s, sizeof(s), a);
    assert(ret > 0);

    if(s.st_name != 0) {
      ret = pread(info->name, sizeof(info->name),
		 hdr + info->strtaboff + s.st_name);
      assert(ret > 0);

      if(strcmp(info->name, symbol) == 0) { /* Exported symbol found. */
	if(s.st_shndx == info->bss_shndx) {
	  addr = (cle_addr)(uintptr_t)info->bss;
	} else if(s.st_shndx == info->data_shndx) {
	  addr = (cle_addr)(uintptr_t)info->data;
	} else if(s.st_shndx == info->text_shndx) {
	  addr = info->text;
#ifdef __AVR__
	  return (void *)(uintptr_t)((addr + s.st_value) >> 1);
#endif
	} else {
	  return NULL;		/* Really an error! */
	}

	return (void *)(uintptr_t)(addr + s.st_value);
      }
    }
  }
  return NULL;
}