/*
** symbol.c for libelfsh
**
** Started on  Mon Feb 26 04:11:46 2001 mayhem
** Last update Fri Aug 15 23:46:54 2003 jv
*/
#include "libelfsh.h"




/*
** Return the symbol name giving its index in the symbol string table
** No special case for SECTION symbol because the section names strings
** have been duplicated in the symbol table.
*/
char		*elfsh_get_symbol_name(elfshobj_t *file, Elf32_Sym *s)
{
  char		*str;

  /* Sanity checks */
  if (file == NULL || s == NULL)
    ELFSH_SETERROR("[libelfsh:get_symbol_name] Invalid NULL parameter\n", NULL);

  /* Load symtab if needed */
  if (file->secthash[ELFSH_SECTION_SYMTAB] == NULL &&
      elfsh_get_symtab(file, NULL) == NULL)
    ELFSH_SETERROR("[libelfsh:get_symbol_name] Cannot retreive symbol table\n",
		   NULL);

  /* Else use the symbol string table */
  if ((NULL == file->secthash[ELFSH_SECTION_STRTAB] ||
       NULL == file->secthash[ELFSH_SECTION_STRTAB]->data) &&
      NULL == elfsh_get_strtab(file, 0))
    return (NULL);
  str = file->secthash[ELFSH_SECTION_STRTAB]->data;
  return (str + s->st_name);
}




/* Return the used offset in .strtab or -1 if failed */
int			elfsh_set_symbol_name(elfshobj_t	*file,
					      Elf32_Sym		*s,
					      char		*name)
{
  elfshsect_t		*sct;
  char			*str;
  u_int			len;
  u_int			new_len;

  /* Sanity checks */
  if (file == NULL || s == NULL || name == NULL)
    ELFSH_SETERROR("[libelfsh:set_symbol_name] Invalid NULL parameter", -1);
  if (file->secthash[ELFSH_SECTION_SYMTAB] == NULL &&
      elfsh_get_symtab(file, NULL) == NULL)
    ELFSH_SETERROR("[libelfsh:get_symbol_name] Cannot retreive symbol table",
		   -1);

  /* Also change the section name in .shstrtab if symbol is STT_SECTION */
  if (elfsh_get_symbol_type(s) == STT_SECTION)
    {
      sct = elfsh_get_section_from_sym(file, s);
      if (sct != NULL && elfsh_set_section_name(file, sct, name) < 0)
	return (-1);
    }


  /* Else use the symbol string table */
  if (file->secthash[ELFSH_SECTION_STRTAB] == NULL ||
      file->secthash[ELFSH_SECTION_STRTAB]->data == NULL)
    return (NULL);

  /* Change the name */
  str = file->secthash[ELFSH_SECTION_STRTAB]->data;
  str += s->st_name;
  len = strlen(str);
  new_len = strlen(name);

  /* Do not allocate new place if possible */
  if (len >= new_len)
    strcpy(str, name);
  /* Append the name to .strtab */
  else
    s->st_name = elfsh_insert_in_strtab(file, name);

  return (s->st_name);
}



/* Return a ptr on the symbol table */
void		*elfsh_get_symtab(elfshobj_t *file, int *num)
{
  elfshsect_t	*s;
  int		strindex;
  int		index;
  int		nbr;

  /* Sanity checks */
  if (file == NULL)
    ELFSH_SETERROR("[libelfsh:get_symtab] Invalid NULL parameter\n", NULL);
  else if (NULL == file->sht && NULL == elfsh_get_sht(file, NULL))
    return (NULL);

  if (file->secthash[ELFSH_SECTION_SYMTAB] == NULL)
    {

      /* If symtab is already loaded, return it */
      s = elfsh_get_section_by_type(file, SHT_SYMTAB,
				    0, &index,
				    &strindex, &nbr);
      if (s != NULL)
	{
	  file->secthash[ELFSH_SECTION_SYMTAB] = s;
	  s->data = elfsh_load_section(file, s->shdr);
	  if (s->data == NULL)
	    return (NULL);

	  /* Now load the string table */
	  s = elfsh_get_strtab(file, s->shdr->sh_link);
	}

      /*
      ** Fix 0 lenght syms and STT_SECTION syms
      ** Create a minimal .symtab if unexistant
      */
      elfsh_fixup_symtab(file, &strindex);
    }

  if (num != NULL)
    *num =
      file->secthash[ELFSH_SECTION_SYMTAB]->shdr->sh_size / sizeof(Elf32_Sym);

  return (file->secthash[ELFSH_SECTION_SYMTAB]->data);
}






/*
** Return the dynamic symbol name giving its value,
** Fill 'offset' with the difference between sym->st_value and 'value'
*/
char		*elfsh_reverse_symbol(elfshobj_t	*file,
				      u_int		value,
				      int		*offset)
{
  elfshsect_t	*sect;
  Elf32_Sym	*sorted;
  int		num;
  int		index;
  char		*str;

  /* Sanity checks */
  if (!value || value == 0xFFFFFFFF)
    return (NULL);
  if (file == NULL)
    ELFSH_SETERROR("[libelfsh:reverse_symbol] Invalid NULL parameter\n", NULL);

  /* If there is no symtab, resolve using SHT */
  if (elfsh_get_symtab(file, &num) == NULL)
    {
      sect = elfsh_get_parent_section(file, value, offset);
      if (sect == NULL)
	ELFSH_SETERROR("[libelfsh:reverse_symbol] No parent section\n", NULL);
      *offset = (int) sect->shdr->sh_addr - value;
      return (elfsh_get_section_name(file, sect));
    }

  /* Else use the sorted-by-address symbol table to match what we want */
  if (file->secthash[ELFSH_SECTION_SYMTAB]->altdata == NULL)
    elfsh_sync_sorted_symtab(file->secthash[ELFSH_SECTION_SYMTAB]);
  sorted = file->secthash[ELFSH_SECTION_SYMTAB]->altdata;

  for (index = 0; index < num; index++)
    if (sorted[index].st_value <= value && DUMPABLE(sorted + index) &&
	(index + 1 >= num || sorted[index + 1].st_value > value))
      {
	*offset = (int) (value - sorted[index].st_value);
	str = elfsh_get_symbol_name(file, sorted + index);
	if (!*str)
	  str = NULL;
	return (str);
      }

  ELFSH_SETERROR("[libelfsh:reverse_symbol] No valid symbol interval\n", NULL);
}





/* Return the symbol entry giving its name */
Elf32_Sym	*elfsh_get_symbol_by_name(elfshobj_t *file, char *name)
{
  Elf32_Sym	*sym;
  int		idx;
  int		size;
  char		*actual;

  if (file == NULL || name == NULL)
    ELFSH_SETERROR("[libelfsh:get_symbol_by_name] Invalid NULL parameter\n",
		   NULL);
  if (elfsh_get_symtab(file, &size) == NULL)
    return (NULL);
  sym = file->secthash[ELFSH_SECTION_SYMTAB]->data;
  for (idx = 0; idx < size; idx++)
    {
      actual = elfsh_get_symbol_name(file, sym + idx);
      if (actual != NULL && !strcmp(actual, name))
	return (sym + idx);
    }
  ELFSH_SETERROR("[libelfsh:get_symbol_by_name] Symbol not found\n",
		 NULL);
}



/* Shift usual symbols (mandatory on solaris) */
void		elfsh_shift_usualsyms(elfshsect_t *sect, Elf32_Sym *sym)
{
  Elf32_Sym	*end;

  /* Change _end if necessary (solaris) */
  end = elfsh_get_dynsymbol_by_name(sect->parent, "_end");
  if (end != NULL && sym->st_value + sym->st_size > end->st_value)
    end->st_value = sym->st_value + sym->st_size;

  /* Change _END_ if necessary (solaris) */
    end = elfsh_get_dynsymbol_by_name(sect->parent, "_END_");
  if (end != NULL && sym->st_value + sym->st_size > end->st_value)
    end->st_value = sym->st_value + sym->st_size;

  /* Change _edata if necessary (solaris) */
  end = elfsh_get_dynsymbol_by_name(sect->parent, "_edata");
  if (end != NULL && sym->st_value + sym->st_size > end->st_value)
    end->st_value = sym->st_value + sym->st_size;

  /* Change _end if necessary (solaris) */
  end = elfsh_get_symbol_by_name(sect->parent, "_end");
  if (end != NULL && sym->st_value + sym->st_size > end->st_value)
    end->st_value = sym->st_value + sym->st_size;

  /* Change _END_ if necessary (solaris) */
    end = elfsh_get_symbol_by_name(sect->parent, "_END_");
  if (end != NULL && sym->st_value + sym->st_size > end->st_value)
    end->st_value = sym->st_value + sym->st_size;

  /* Change _edata if necessary (solaris) */
  end = elfsh_get_symbol_by_name(sect->parent, "_edata");
  if (end != NULL && sym->st_value + sym->st_size > end->st_value)
    end->st_value = sym->st_value + sym->st_size;
}


/* Insert a symbol in the given symbol table */
int		elfsh_insert_symbol(elfshsect_t *sect,
				    Elf32_Sym	*sym,
				    char	*name)
{
  Elf32_Sym	*orig;
  int		index;

  /* Sanity checks */
  if (sect == NULL || sect->shdr == NULL ||
      (sect->shdr->sh_type != SHT_SYMTAB &&
       sect->shdr->sh_type != SHT_DYNSYM))
    ELFSH_SETERROR("[libelfsh:insert_symbol] Input section is not SYMTAB\n",
		   -1);
  if (name == NULL)
    name = ELFSH_NULL_STRING;

  /* Check if symbol already exists */
  orig = elfsh_get_symbol_by_name(sect->parent, name);
  if (orig != NULL && sym->st_value == orig->st_value)
    return (((uintptr_t) orig - (uintptr_t) sect->data) / ELFSH_SYMTAB_ENTRY_SIZE);

  /* Shift some special symbols */
  elfsh_shift_usualsyms(sect, sym);

  /* Insert symbol name in .shstrtab */
  index = elfsh_insert_in_strtab(sect->parent, name);
  if (index < 0)
    return (-1);

#if __DEBUG_RELADD__
  printf("[DEBUG_RELADD] Injected symbol %-20s [%08X] \n",
	 name, (u_int) sym->st_value);
#endif

  /* Insert symbol in .symtab */
  sym->st_name = index;
  index = elfsh_append_data_to_section(sect, sym, sizeof(Elf32_Sym));
  return (index);
}





/* Retreive the file offset giving the virtual address */
int		elfsh_get_symbol_foffset(elfshobj_t *file, Elf32_Sym *sym)
{
  elfshsect_t	*sect;
  char		*name;

  /* If the symbol is a section, then look at the sht instead */
  if (elfsh_get_symbol_type(sym) == STT_SECTION)
    {
      name = elfsh_get_symbol_name(file, sym);
      sect = elfsh_get_section_by_name(file, name, NULL, NULL, NULL);
      return (sect ? elfsh_get_section_foffset(sect->shdr) : 0);
    }

  /* get our parent section and compute the file offset */
  if (sym == NULL || file == NULL || NULL == sym->st_value)
    return (0);
  sect = elfsh_get_parent_section(file, sym->st_value, NULL);
  if (sect == NULL)
    return (0);
  return (sect->shdr->sh_offset + (sym->st_value - sect->shdr->sh_addr));
}



/* Get symtab entry by vaddr */
Elf32_Sym	  *elfsh_get_symbol_by_value(elfshobj_t	*file,
					     u_int	vaddr,
					     int	*off,
					     int	mode)
{
  int		  num;

  if (NULL == elfsh_get_symtab(file, &num))
    ELFSH_SETERROR("[libelfsh:get_symbol_by_value] Cannot retreive SYMTAB\n",
		   NULL);

  return (elfsh_get_sym_by_value(file->secthash[ELFSH_SECTION_SYMTAB]->data,
				 num, vaddr, off, mode));
}



/* Shift the symbol table */
int		elfsh_shift_symtab(elfshobj_t *file, u_long limit, int inc)
{
  elfshsect_t	*actual;
  u_int		nbr;
  u_int		idx;
  Elf32_Sym	*sym;

#if __DEBUG_RELADD__
  static u_int	totshift = 0;
  totshift += inc;
  printf("[DEBUG_RELADD] Shifting symtab [LIM %08X .::. INC %08u .::. TOT %08u] \n",
	 (u_int) limit, inc, totshift);
#endif

  actual = elfsh_get_section_by_type(file, SHT_SYMTAB, 0, NULL, NULL, &nbr);
  if (actual == NULL || actual->data == NULL || !nbr)
    return (-1);
  sym = actual->data;

  for (idx = 0; idx < (nbr / ELFSH_SYMTAB_ENTRY_SIZE); idx++)
    if (sym[idx].st_value >= limit && limit != 0)
      {

#if __DEBUG_RELADD__
	printf("[DEBUG_RELADD] Shifted symbol *%-20s* [%08X -> %08X]\n",
	       elfsh_get_symbol_name(file, sym),
	       (u_int) sym[idx].st_value,
	       (u_int) sym[idx].st_value + inc);
#endif

	sym[idx].st_value += inc;
      }

  elfsh_sync_sorted_symtab(actual);
  return (0);
}



/* Insert STT_SECTION symbol */
int		elfsh_insert_sectsym(elfshobj_t *file, elfshsect_t *sect)
{
  Elf32_Sym	new;
  int		ret;

  new = elfsh_create_symbol(sect->shdr->sh_addr, sect->shdr->sh_size, STT_SECTION,
			    STB_LOCAL, 0, sect->index);
  ret = elfsh_insert_symbol(file->secthash[ELFSH_SECTION_SYMTAB],
			    &new, sect->name);
  elfsh_sync_sorted_symtab(file->secthash[ELFSH_SECTION_SYMTAB]);

#if __DEBUG_RELADD__
  printf("[DEBUG_RELADD] Inserted STT_SECT symbol %s [%08X] \n",
	 sect->name, (u_int) sect->shdr->sh_addr);
#endif

  return (ret);
}
