/* ----------------------------------------------------------------------------- 
 * elf.c
 *
 *     ELF file management. This file contains functions for accessing ELF
 *     file data from a raw memory mapped ELF file (as performed by the
 *     functions in object.c).
 * 
 * Author(s) : David Beazley (beazley@cs.uchicago.edu)
 *
 * Copyright (C) 2000.  The University of Chicago. 
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * See the file COPYING for a complete copy of the LGPL.
 * ----------------------------------------------------------------------------- */

#include "wad.h"

static char cvs[] = "$Header: /cvs/projects/SWIG/Tools/WAD/Wad/elf.c,v 1.14 2001/06/20 15:12:53 beazley Exp $";

#ifdef WAD_SOLARIS
#include <sys/elf.h>
#endif
#ifdef WAD_LINUX
#include <elf.h>
#endif

/* --- What's needed here (high level interface) :
     - Mapping of addresses to symbols
     - Mapping of symbols to file+line
*/


/* -----------------------------------------------------------------------------
 * wad_elf_check()
 *
 * Checks to see if an object file is an ELF file. Returns 1 on success and 
 * changes the type flag of wo to indicate the type of ELF file.
 * ----------------------------------------------------------------------------- */

int
wad_elf_check(WadObjectFile *wo) {
  if (strncmp((char *)wo->ptr,ELFMAG, SELFMAG) != 0)
    return 0;
  
  /* Probably need to put some kind of 32/64 bit check here */
  return 1;
}

/* -----------------------------------------------------------------------------
 * wad_elf_phdrcnt()
 * 
 * Return number of entries in the ELF program header section
 * ----------------------------------------------------------------------------- */

int
wad_elf_phdrcnt(WadObjectFile *wo) {
  Elf32_Ehdr *eh;
  
  eh = (Elf32_Ehdr *) wo->ptr;
  return eh->e_phnum;
}

/* -----------------------------------------------------------------------------
 * wad_elf_phdrpos()
 *
 * Return the location of the ELF program header.
 * ----------------------------------------------------------------------------- */

void *
wad_elf_phdrpos(WadObjectFile *wo) {
  Elf32_Ehdr *eh;
  char *c;
  eh = (Elf32_Ehdr *) wo->ptr;
  c = (char *) wo->ptr;
  return (void *) (c+eh->e_phoff);
}

/* -----------------------------------------------------------------------------
 * wad_elf_shdrcnt()
 * 
 * Return number of entries in the ELF section header
 * ----------------------------------------------------------------------------- */

int
wad_elf_shdrcnt(WadObjectFile *wo) {
  Elf32_Ehdr *eh;
  
  eh = (Elf32_Ehdr *) wo->ptr;
  return eh->e_shnum;
}


/* -----------------------------------------------------------------------------
 * wad_elf_shdrpos()
 *
 * Return the location of the section headers
 * ----------------------------------------------------------------------------- */

void *
wad_elf_shdrpos(WadObjectFile *wo) {
  Elf32_Ehdr *eh;
  char *c;
  eh = (Elf32_Ehdr *) wo->ptr;
  c = (char *) wo->ptr;
  return (void *) (c+eh->e_shoff);
}

/* -----------------------------------------------------------------------------
 * wad_elf_section_header()
 *
 * Get a specific section number
 * ----------------------------------------------------------------------------- */

void *wad_elf_section_header(WadObjectFile *wo, int sn) {
  Elf32_Ehdr *eh;
  char  *r;

  eh = (Elf32_Ehdr *) wo->ptr;
  if ((sn < 0) || (sn >= eh->e_shnum)) return 0;
  
  r = (char *) wad_elf_shdrpos(wo) + (sn*eh->e_shentsize);
  return (void *) r;
}

/* -----------------------------------------------------------------------------
 * wad_elf_section_data()
 *
 * Get section data
 * ----------------------------------------------------------------------------- */

void *wad_elf_section_data(WadObjectFile *wo, int sn) {
  Elf32_Shdr *sh;
  char *r;

  sh = (Elf32_Shdr *) wad_elf_section_header(wo,sn);
  if (!sh) return 0;
  
  r = ((char *) wo->ptr) + sh->sh_offset;
  return r;
}

/* -----------------------------------------------------------------------------
 * wad_elf_section_size()
 * Return section size
 * ----------------------------------------------------------------------------- */

int wad_elf_section_size(WadObjectFile *wo, int sn) {
  Elf32_Shdr *sh;

  sh = (Elf32_Shdr *) wad_elf_section_header(wo,sn);
  if (!sh) return -1;
  return sh->sh_size;
}

/* -----------------------------------------------------------------------------
 * wad_elf_section_name()
 *
 * Returns the name of an ELF section 
 * ----------------------------------------------------------------------------- */

char *wad_elf_section_name(WadObjectFile *wo, int sn) {
  Elf32_Ehdr *eh;
  Elf32_Shdr *sh;
  char    *sectionstr;

  eh = (Elf32_Ehdr *) wo->ptr;

  /* Get the string table */
  sectionstr = (char *) wad_elf_section_data(wo,eh->e_shstrndx);
  if (!sectionstr) {
    return 0;
  }

  /* Get the section header for the section */
  sh = (Elf32_Shdr *) wad_elf_section_header(wo,sn);
  if (!sh) return 0;
  return sectionstr + sh->sh_name;
}

/* -----------------------------------------------------------------------------
 * wad_elf_section_byname()
 *
 * Get section data given a section name
 * ----------------------------------------------------------------------------- */

int
wad_elf_section_byname(WadObjectFile *wo, char *name) {
  int i;
  char *sn;
  int n;

  n = wad_elf_shdrcnt(wo);
  for (i = 0; i <n; i++) {
    sn = wad_elf_section_name(wo,i);
    if (strcmp(sn,name) == 0) {
      return i;
    }
  }
  return -1;
}

/* -----------------------------------------------------------------------------
 * wad_find_symbol()
 *
 * Tries to find the symbol associated with a given address. base is the
 * base address of the object file.
 * ----------------------------------------------------------------------------- */

static
int elf_search_section_sym(WadFrame *f, char *secname, char *strname) {
  int         nsymtab;
  int         nstrtab;
  int         symtab_size;
  Elf32_Sym  *sym;
  int         nsym;
  char       *str;
  int         i;
  unsigned long vaddr, base;
  char        *name;
  char        *localfile = 0;

  vaddr = (unsigned long) f->pc;
  base  = (unsigned long) f->segment->base;

  nsymtab = wad_elf_section_byname(f->object,secname);
  if (nsymtab < 0) return 0;
  nstrtab = wad_elf_section_byname(f->object,strname);
  if (nstrtab < 0) return 0;
  
  symtab_size = wad_elf_section_size(f->object,nsymtab);
  sym = (Elf32_Sym *) wad_elf_section_data(f->object,nsymtab);
  str = (char *) wad_elf_section_data(f->object,nstrtab);

  nsym = (symtab_size/sizeof(Elf32_Sym));
  for (i = 0; i < nsym; i++) {
    name = str + sym[i].st_name;
    /* Look for filename in case the symbol maps to a local symbol */
    if (ELF32_ST_TYPE(sym[i].st_info) == STT_FILE) {
      localfile = name;
    }
    if (wad_debug_mode & DEBUG_SYMBOL_SEARCH) {
      wad_printf("%x(%x): %s   %x + %x, %x, %x\n", base, vaddr, name, sym[i].st_value, sym[i].st_size, sym[i].st_info, sym[i].st_shndx);
    }
    if (((base + sym[i].st_value) <= vaddr) && (vaddr <= (base+sym[i].st_value + sym[i].st_size))) {
#ifdef WAD_LINUX
      /* If the section index is 0, the symbol is undefined */
      if (sym[i].st_shndx == 0) continue;
#endif
      f->sym_name = name;
      f->sym_nlen = strlen(name);
      f->sym_base = base + sym[i].st_value;
      f->sym_size = sym[i].st_size;
      if (ELF32_ST_BIND(sym[i].st_info) == STB_LOCAL) {
	f->sym_file = localfile;
	f->sym_bind = SYM_LOCAL;
      } else {
	f->sym_bind = SYM_GLOBAL;
      }
      return 1;
    }
  }
  return 0;
}

void
wad_elf_find_symbol(WadFrame *f) {
  /* We simply try a few possible sections */
  if (elf_search_section_sym(f,".symtab",".strtab")) return;
  if (elf_search_section_sym(f,".dynsym",".dynstr")) return;

  /* Hmmm. No match found. Oh well */
  return;
}

/* -----------------------------------------------------------------------------
 * wad_elf_debug_info()
 *
 * Gather debugging information about a function (if possible)
 * ----------------------------------------------------------------------------- */

int
wad_elf_debug_info(WadFrame *f) {
  int nstab, nstabstr, nstabindex, nstabindexstr, nstabexcl, nstabexclstr;
  int ret;
  void *stab;
  char *stabstr;
  int   stabsize;

  nstab = wad_elf_section_byname(f->object,".stab");
  nstabstr = wad_elf_section_byname(f->object,".stabstr");
  nstabindex = wad_elf_section_byname(f->object,".stab.index");
  nstabindexstr = wad_elf_section_byname(f->object,".stab.indexstr");
  nstabexcl = wad_elf_section_byname(f->object,".stab.excl");
  nstabexclstr = wad_elf_section_byname(f->object,".stab.exclstr");

#ifdef DEBUG_DEBUG
  wad_printf("nstab         = %d\n", nstab);
  wad_printf("nstabstr      = %d\n", nstabstr);
  wad_printf("nstabindex    = %d\n", nstabindex);
  wad_printf("nstabindexstr = %d\n", nstabindexstr);
  wad_printf("nstabexcl     = %d\n", nstabexcl);
  wad_printf("nstabexclstr  = %d\n", nstabexclstr);
#endif 

  /* Now start searching stabs */

  /* Look in the .stab section */
  if (nstab > 0) {
    stab = wad_elf_section_data(f->object,nstab);
    stabsize = wad_elf_section_size(f->object,nstab);
    stabstr = (char *) wad_elf_section_data(f->object,nstabstr);

    
    if (wad_search_stab(stab,stabsize,stabstr, f)) return 1;
  }

  /* Look in the .stab.excl section. A solaris oddity? */
  
  if (nstabexcl > 0) {
    stab = wad_elf_section_data(f->object,nstabexcl);
    stabsize = wad_elf_section_size(f->object, nstabexcl);
    stabstr = (char *) wad_elf_section_data(f->object, nstabexclstr);

    if (wad_search_stab(stab,stabsize,stabstr, f)) return 1;
  }

  /* Look in the .stab.index section. A Solaris oddity? */
  if (nstabindex > 0) {

    stab = wad_elf_section_data(f->object,nstabindex);
    stabsize = wad_elf_section_size(f->object, nstabindex);
    stabstr = (char *) wad_elf_section_data(f->object, nstabindexstr);

    if (wad_search_stab(stab,stabsize,stabstr, f)) {
      /* Hmmm. Might be in a different file */
      WadObjectFile *wo1, *wold;
      char objfile[MAX_PATH];
      /*      printf("DEBUG %s\n", f->sym_name); */
      wad_strcpy(objfile, f->loc_objfile);
      wo1 = wad_object_load(objfile);
      if (wo1) {
	wold = f->object;
	f->object = wo1;
	wad_find_debug(f);
	f->object = wold;
	return ret;
      } else {
	/*	wad_printf("couldn't load %s\n", objfile); */
      }
      /*      if (!ret) return wad_search_stab(stab,stabsize,stabstr,f);*/
      return ret;
    }
  }
  return 0;
}

/* -----------------------------------------------------------------------------
 * wad_elf_debug()
 *
 * Print some debugging information about an object
 * ----------------------------------------------------------------------------- */

void
wad_elf_debug(WadObjectFile *wo) {
  int i;
  wad_printf("ELF Debug : obj = %x (%s)\n", wo, wo->path);
  wad_printf("   phdrcnt      = %d\n", wad_elf_phdrcnt(wo));
  wad_printf("   phdrpos      = %x\n", wad_elf_phdrpos(wo));
  wad_printf("   shdrcnt      = %d\n", wad_elf_shdrcnt(wo));
  wad_printf("   shdrpos      = %x\n", wad_elf_shdrpos(wo));
  for (i = 0; i < wad_elf_shdrcnt(wo); i++) {
    wad_printf("      section '%s': data = 0x%x, size = %d\n", 
	   wad_elf_section_name(wo,i),
	   wad_elf_section_data(wo,i),
	   wad_elf_section_size(wo,i));
  }
}

/* general purpose functions exposed to the outside world */

/* -----------------------------------------------------------------------------
 * wad_find_symbol()
 * ----------------------------------------------------------------------------- */

void
wad_find_symbol(WadFrame *f) {
  if (wad_debug_mode & DEBUG_SYMBOL) {
    wad_printf("wad: Searching for 0x%08x --> ", f->pc);
  }
  if (f->object)
    wad_elf_find_symbol(f);
  if (wad_debug_mode & DEBUG_SYMBOL) {
    if (f->sym_name) {
      wad_printf("%s", f->sym_name);
      if (f->sym_file)
	wad_printf(" in '%s'\n", f->sym_file);
      else
	wad_printf("\n");
    } else {
      wad_printf("?\n");
    }
  }
}

void
wad_find_debug(WadFrame *f) {
  /*  if (f->debug_check) return; */
  if (f->object) {
    wad_elf_debug_info(f);
  }
  /*  f->debug_check = 1; */
}



