/* Run an ELF binary on a linux system.

   Copyright (C) 1993, Eric Youngdale.

   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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.  */



/* Various symbol table handling functions, including symbol lookup */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ibcs/unistd.h>
#include "hash.h"
#include "linuxelf.h"
#include "syscall.h"
#include "string.h"

/*
 * This is the start of the linked list that describes all of the files present
 * in the system with pointers to all of the symbol, string, and hash tables, 
 * as well as all of the other good stuff in the binary.
 */
struct elf_resolve * _dl_symbol_tables = NULL;

/*
 * This is the hash function that is used by the ELF linker (and as, I s'pose)
 * to generate the hash table that each executable and library is required to
 * have.
 */

unsigned long _dl_elf_hash(const char * name){
  unsigned long hash = 0;
  unsigned long tmp;

  while (*name){
    hash = (hash << 4) + *name++;
    if((tmp = hash & 0xf0000000)) hash ^= tmp >> 24;
    hash &= ~tmp;
  };
  return hash;
}

static int __dl_strcmp(const char * cs,const char * ct)
{
register int __res __asm__("ax");
__asm__("cld\n"
	".LL1:\tlodsb\n\t"
	"scasb\n\t"
	"jne .LL2\n\t"
	"testb %%al,%%al\n\t"
	"jne .LL1\n\t"
	"xorl %%eax,%%eax\n\t"
	"jmp .LL3\n"
	".LL2:\tmovl $1,%%eax\n\t"
	"jb .LL3\n\t"
	"negl %%eax\n"
	".LL3:"
	:"=a" (__res):"D" (cs),"S" (ct):"si","di");
return __res;
}

/*
 * Check to see if a library has already been added to the hash chain.
 */
int _dl_check_hashed_files(char * libname){
  struct elf_resolve * tpnt;
  tpnt = _dl_symbol_tables;
  while(tpnt){
    if(__dl_strcmp(tpnt->libname, libname) == 0) return 1;
    tpnt = tpnt->next;
  };
  return 0;
}

/*
 * We call this function when we have just read an ELF library or executable.
 * We add the relevant info to the symbol chain, so that we can resolve all
 * externals properly.
 */

struct elf_resolve * _dl_add_elf_hash_table(char * libname,
					char * loadaddr,
					unsigned int * dynamic_info,
					unsigned int dynamic_addr,
					unsigned int dynamic_size){
  unsigned int *  hash_addr;
  struct elf_resolve * tpnt;
  int i;

  if (!_dl_symbol_tables)
    tpnt = _dl_symbol_tables = 
      (struct elf_resolve *) _dl_malloc(sizeof(struct elf_resolve));
  else {
    tpnt = _dl_symbol_tables;
    while(tpnt->next) tpnt = tpnt->next;
    tpnt->next = (struct elf_resolve *) _dl_malloc(sizeof(struct elf_resolve));
    tpnt = tpnt->next;
  };
  
  hash_addr = (unsigned int *) (dynamic_info[DT_HASH] + loadaddr);
  tpnt->next = NULL;
  tpnt->nbucket = *hash_addr++;
  tpnt->nchain = *hash_addr++;
  tpnt->libname = _dl_strdup(libname);
  tpnt->elf_buckets = hash_addr;
  hash_addr += tpnt->nbucket;
  tpnt->dynamic_addr = dynamic_addr;
  tpnt->dynamic_size = dynamic_size;
  tpnt->libtype = elf_lib;

  tpnt->chains = hash_addr;
  tpnt->loadaddr = loadaddr;
  for(i=0; i<24; i++) tpnt->dynamic_info[i] = dynamic_info[i];
  return tpnt;
}


/*
 * This function resolves externals, and this is either called when we process
 * relocations or when we call an entry in the PLT table for the first time.
 */

char * _dl_find_hash(char * name, struct elf_resolve * tpnt1, unsigned int instr_addr){
  struct elf_resolve * tpnt;
  int si;
  char * pnt;
  char * strtab;
  struct Elf32_Sym * symtab;
  unsigned int elf_hash_number, linux_hash_number, hn;
  struct library_symbol * lpnt;
  char linux_name[256];
  char * weak_result;


  weak_result = 0;
  elf_hash_number = _dl_elf_hash(name);

  for(tpnt = _dl_symbol_tables; tpnt; tpnt = tpnt->next) {
    hn = elf_hash_number % tpnt->nbucket;
    symtab = (struct Elf32_Sym *) (tpnt->dynamic_info[DT_SYMTAB] + 
				   tpnt->loadaddr);
    strtab = (char *) (tpnt->dynamic_info[DT_STRTAB] + tpnt->loadaddr);
    for(si = tpnt->elf_buckets[hn]; si; si = tpnt->chains[si]){
      pnt = strtab + symtab[si].st_name;

      if(__dl_strcmp(pnt, name) == 0 && 
	 (ELF32_ST_TYPE(symtab[si].st_info) == STT_FUNC ||
	  ELF32_ST_TYPE(symtab[si].st_info) == STT_OBJECT) &&
	 (instr_addr && symtab[si].st_value != instr_addr) &&
	 symtab[si].st_value != 0 && symtab[si].st_shndx != 0)
	switch(ELF32_ST_BIND(symtab[si].st_info)){
	case STB_GLOBAL:
	  return tpnt->loadaddr + symtab[si].st_value;
	case STB_WEAK:
	  if (!weak_result) weak_result = tpnt->loadaddr + symtab[si].st_value;
	  break;
	default:  /* Do local symbols need to be examined? */
	  break;
	};
    };
  };
  return weak_result;
}


