#ifdef linux
#include <getopt.h>
#else
#include <stdlib.h>
#endif

#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include "linuxelf.h"

#define ELF32_R_SYM(x) ((x) >> 8)
#define ELF32_R_TYPE(x) ((x) & 0xff)
char *header=NULL;
int header_size=0;
unsigned int dynamic_addr;
unsigned int dynamic_size;
char * pint = "";

int dynamic_info[24];

unsigned int rel_size;
int loadaddr = -1;

unsigned int rela_addr;
unsigned int rela_size;

struct dynamic * dpnt;
struct Elf32_Rel * rpnt;

struct Elf32_Sym * symtab;
char * strtab;
int symtab_index;

FILE * infile;

char * filetype[] = {
  "None", "Rel", "Executable", "Dynamic", "Core", "Num"};

char * machine[] = {
  "None","WE32100","Sparc","80386","MK68000","MK88000","80486","80860","MIPS"};

char * phtype[] = {
  "Null", "Load", "Dynamic", "Interp", "Note", "Shared lib", "PHDR", "Num"};

char * sectiontype[] ={
  "NULL", "PROGBITS", "SYMTAB", "STRTAB", "RELA", "HASH", "DYNAMIC",
  "NOTE", "NOBITS", "REL", "SHLIB", "DYNSYM", "NUM"};

char * dyntype[] ={
  "NULL", "NEEDED","PLTRELSZ","PLTGOT","HASH","STRTAB","SYMTAB","RELA",
  "RELASZ","RELAENT","STRSZ","SYMENT","INIT","FINI","SONAME","RPATH",
  "SYMBOLIC","REL","RELSZ","RELENT","PLTREL","DEBUG","TEXTREL","JMPREL"};

char * i386_reloctype[] = {
  "R_386_NONE","R_386_32","R_386_PC32","R_386_GOT32","R_386_PLT32",
  "R_386_COPY","R_386_GLOB_DAT","R_386_JMP_SLOT","R_386_RELATIVE",
  "R_386_GOTOFF","R_386_GOTPC"};

char * sparc_reloctype[] = {
  "R_SPARC_NONE", "R_SPARC_8", "R_SPARC_16", "R_SPARC_32", "R_SPARC_DISP8", "R_SPARC_DISP16",
  "R_SPARC_DISP32", "R_SPARC_WDISP30", "R_SPARC_WDISP22", "R_SPARC_HI22", "R_SPARC_22",
  "R_SPARC_13", "R_SPARC_LO10", "R_SPARC_GOT10", "R_SPARC_GOT13", "R_SPARC_GOT22",
  "R_SPARC_PC10", "R_SPARC_PC22", "R_SPARC_WPLT30", "R_SPARC_COPY", "R_SPARC_GLOB_DAT",
  "R_SPARC_JMP_SLOT", "R_SPARC_RELATIVE", "R_SPARC_UA32"};

char * sttinfo[] = {"NOTYPE","OBJECT","FUNC","SECTION","FILE"};
char * stbinfo[] = {"LOCAL","GLOBAL","WEAK"};

struct elf_shdr * elf_sections;

void dump_relocations(struct elfhdr * epnt, struct Elf32_Rel * rpnt, int rel_size)
{
  int i;
  struct Elf32_Rela * rapnt;
  rapnt = (struct Elf32_Rela *) rpnt;

  switch(epnt->e_machine) {
  case EM_386:
  case EM_486:
    rel_size = rel_size / sizeof(struct Elf32_Rel);
    break;
  case EM_SPARC:
    rel_size = rel_size / sizeof(struct Elf32_Rela);
    break;
  }

  for(i=0; i< rel_size; i++){
    switch(epnt->e_machine) {
    case EM_386:
    case EM_486:
      symtab_index = ELF32_R_SYM(rpnt->info);
      printf("Tag: %5.5x Value %5.5x %-15s (%x %s) \n", rpnt->offset, rpnt->info, 
	     i386_reloctype[rpnt->info & 0xff],
	     symtab[symtab_index].st_value,
	     strtab + symtab[symtab_index].st_name);
      rpnt++;
      break;
    case EM_SPARC:
      symtab_index = ELF32_R_SYM(rapnt->info);
#if 0
      fprintf(stderr, "%d %x: %x %x %x (%x)\n", i, rapnt, rapnt->offset, rapnt->info, rapnt->addend);
#endif
      printf("Tag: %5.5x Value %5.5x %-15s (%x %s) Addend:%x\n", rapnt->offset, rapnt->info, 	     
	     sparc_reloctype[rapnt->info & 0xff],
	     symtab[symtab_index].st_value,
	     strtab + symtab[symtab_index].st_name, rapnt->addend);
      rapnt++;
      break;
    default:
      printf("Don't know about relocations on this machine architecture\n");
      exit(1);
    }
  };
}

#define SECTION_NAME(X) (&header[lastmapped+(X)->sh_name])

char dump_sects[100] = {0,};

main(int argc, char * argv[]){
  int do_dynamic, do_syms, do_reloc, do_section, do_load;
  int do_using_dynamic;
  int do_header;
  int do_dump, section;
  struct elfhdr * epnt;
  struct elf_phdr * ppnt;
  struct elf_shdr * spnt, *symsec;
  int lastmapped;
  char * cp;
  int i, j;
  char c;

  if(argc < 2) {
    printf("readelf v1.3\n");
    printf("Usage: readelf [-rsahldSD] [-x secnum] [-i secnum] elfbin\n");
    exit(0);
  };

  do_section = do_syms = do_reloc = do_dynamic = do_load = 0;
  do_header = 0;
  do_using_dynamic = 0;
  do_dump = 0;

  while ((c = getopt(argc, argv, "rsahldSDx:i:v")) != EOF)
    {
      switch (c)
	{
	case 'a':
	  do_syms++;
	  do_reloc++;
	  do_dynamic++;
	  do_header++;
	  do_section++;
	  do_load++;
	  break;
	case 'D':
	  do_using_dynamic++;
	  break;
	case 'r':
	  do_reloc++;
	  break;
	case 'h':
	  do_header++;
	  break;
	case 'l':
	  do_load++;
	  break;
	case 's':
	  do_syms++;
	  break;
	case 'S':
	  do_section++;
	  break;
	case 'd':
	  do_dynamic++;
	  break;
	case 'x':
	  do_dump++;
	  section = strtoul(optarg, &cp, 0);
	  if (!*cp && section >= 0 || section < 100)
	    {
	      dump_sects[section] |= 1;
	      break;
	    }
	  goto oops;
	case 'i':
	  do_dump++;
	  section = strtoul(optarg, &cp, 0);
	  if (!*cp && section >= 0 || section < 100)
	    {
	      dump_sects[section] |= 2;
	      break;
	    }
	case 'v':
	  fprintf(stderr, "Readelf v1.3\n");
	  break;
	default:
	oops:
	  fprintf(stderr, "Invalid option '-%c'\n", c);
	  exit(1);
	};
    };


  while(optind<argc) {
    struct stat statbuf;
    infile = fopen(argv[optind++], "r");
    if (!infile){
      printf("Input file %s not found.\n",argv[optind-1]);
      exit(1);
    };
    fstat(fileno(infile),&statbuf);
    if (statbuf.st_size > header_size) {
       header = (char*)realloc(header, statbuf.st_size);
       header_size = statbuf.st_size;
    }
    memset(header,0,4096);
    fread(header, statbuf.st_size, 1, infile);
    epnt = (struct elfhdr *) header;
    if (epnt->e_ident[0] != 0x7f || strncmp(&epnt->e_ident[1], "ELF",3)){
      printf("%s is not an ELF file\n",argv[optind-1]);
      exit(1);
    };
    dynamic_addr = 0;

#if 0
  if(epnt->e_type != 2) {
    printf("%s is not an ELF executable\n",argv[optind-1]);
    exit(1);
  };
    if(epnt->e_machine != 3 && epnt->e_machine != 6){
      printf("%s is not an ELF executable for the 386/486\n",argv[optind-1]);
      exit(1);
    };
#endif

    if(do_header) {
      int ijk;
      printf("ELF magic:");
      for(ijk = 0; ijk < 16; ijk++) printf("%2.2x ", epnt->e_ident[ijk]);
      printf("\n");
      printf("Type, machine, version = %s(%d) %s(%d) %d\n", 
	     filetype[epnt->e_type], epnt->e_type,
	     machine[epnt->e_machine], epnt->e_machine, epnt->e_version);
      printf("Entry, phoff, shoff, flags = %x %d %d %x\n",
	     epnt->e_entry, epnt->e_phoff, epnt->e_shoff, epnt->e_flags);
      printf("ehsize, phentsize, phnum = %d %d %d\n", epnt->e_ehsize,
	     epnt->e_phentsize, epnt->e_phnum);
      printf("shentsize, shnum, shstrndx = %d %d %d\n", epnt->e_shentsize, 
	     epnt->e_shnum, epnt->e_shstrndx);
    }
    
    
    if(do_load) {
      printf("Elf file is %s\n",filetype[epnt->e_type]);
      printf("Entry point 0x%x\n",epnt->e_entry);
      printf("There are %d program headers, starting at offset %x:\n",
	     epnt->e_phnum, epnt->e_phoff);
    };

    ppnt = (struct elf_phdr *) &header[epnt->e_phoff];
    for(i=0;i < epnt->e_phnum; i++){
      if(loadaddr == -1 && ppnt->p_vaddr != 0) 
	loadaddr = (ppnt->p_vaddr & 0xfffff000) - (ppnt->p_offset & 0xfffff000);
      if(do_load){
	printf("%-10s ",phtype[ppnt->p_type]);
	printf("0x%5.5x ",ppnt->p_offset);
	printf("0x%8.8x ",ppnt->p_vaddr);
	printf("0x%5.5x 0x%5.5x ",ppnt->p_filesz, ppnt->p_memsz);
	printf("%c%c%c ",
	       (ppnt->p_flags & 4 ? 'R' : ' '),
	       (ppnt->p_flags & 2 ? 'W' : ' '),
	       (ppnt->p_flags & 1 ? 'E' : ' '));
      };
      
      if(ppnt->p_type == 2) {
	if(dynamic_addr) fprintf(stderr,"Error - more than one dynamic section");
	dynamic_addr = ppnt->p_offset;
	dynamic_size = ppnt->p_filesz;
      };
      if(ppnt->p_type == 3) {
	if (do_load) printf("\nRequesting program interpreter [%s]",&header[ppnt->p_offset]);
	pint = strdup(&header[ppnt->p_offset]);
      }
      if(do_load) printf("\n");
      ppnt++;
    };
    
    if(loadaddr == -1)
      {
	/* Very strange. */
	loadaddr = 0;
      }

    elf_sections = spnt = (struct elf_shdr *) &header[epnt->e_shoff];
    spnt += epnt->e_shstrndx;
    lastmapped = spnt->sh_offset;
    if(do_section) {
      printf("There are %d section headers, starting at offset %x:\n",
	     epnt->e_shnum, epnt->e_shoff);
      spnt = (struct elf_shdr *) &header[epnt->e_shoff];
      for(i=0;i < epnt->e_shnum; i++){
	printf("[%2x] %-10s", i, &header[lastmapped+spnt->sh_name]);
	if(spnt->sh_type < 12)
	  printf(" %-20s ",sectiontype[spnt->sh_type]);
	else
	  printf(" %8x ", spnt->sh_type);
	printf("%8.8x %5.5x %5.5x %2.2x",
	       spnt->sh_addr, 
	       spnt->sh_offset, 
	       spnt->sh_size, 
	       spnt->sh_entsize);
	
	printf(" %c%c%c %2x %2x %x ", 
	       (spnt->sh_flags & 1 ? 'W' : ' '),
	       (spnt->sh_flags & 2 ? 'A' : ' '),
	       (spnt->sh_flags & 4 ? 'X' : ' '),
	       spnt->sh_link, 
	       spnt->sh_info, 
	       spnt->sh_addralign);
	printf("\n");
	spnt++;
      };
    }
  
    /* Now parse the dynamic section */
    if(do_dynamic)
      printf("\n Dynamic section data:%x %x\n",dynamic_addr, dynamic_size);
    dpnt = (struct dynamic *) &header[dynamic_addr];
    dynamic_size = dynamic_size / sizeof(struct dynamic);
    for(i=0; i< dynamic_size; i++){
      if(dpnt->d_tag < 0 || dpnt->d_tag > 24) {
	      fprintf(stderr, "Invalid dynamic tag 0x%x 0x%x\n", dpnt->d_tag, dpnt->d_un.d_ptr);
      } else {
	      if(do_dynamic) printf("Tag: %d (%s) Value %x\n", dpnt->d_tag, dyntype[dpnt->d_tag],
				    dpnt->d_un.d_ptr);
	      dynamic_info[dpnt->d_tag] = dpnt->d_un.d_val;
      }
      dpnt++;
    };
    
    if(dynamic_info[DT_SYMTAB]) {
      symtab =  (struct Elf32_Sym *) (header - loadaddr + dynamic_info[DT_SYMTAB]);
      strtab = (char *) (header - loadaddr + dynamic_info[DT_STRTAB]);
      
      dpnt = (struct dynamic *) &header[dynamic_addr];
      for(i=0; i< dynamic_size; i++){
	if(dpnt->d_tag == 1) {
	  printf("Shared library: [%s] %d\n", (dpnt->d_un.d_val + strtab),
		 strcmp(dpnt->d_un.d_val + strtab, pint));
	}
	dpnt++;
      };
    }
    
    if(do_reloc) {
      if(do_using_dynamic && dynamic_info[DT_REL]) {
	rpnt = (struct Elf32_Rel *) (header + dynamic_info[DT_REL] - loadaddr);
	rel_size = dynamic_info[DT_RELSZ];
	printf("\n Relocation section data:%x %x\n",dynamic_info[DT_REL], rel_size);
	dump_relocations(epnt, rpnt, rel_size);
      }
      if(do_using_dynamic && dynamic_info[DT_JMPREL]) {
	rpnt = (struct Elf32_Rel *) (header + dynamic_info[DT_JMPREL] - loadaddr);
	rel_size = dynamic_info[DT_PLTRELSZ];
	printf("\n Jumptable Relocation section data:%x %x\n",dynamic_info[DT_JMPREL], rel_size);
	dump_relocations(epnt, rpnt, rel_size);
      }
      if(!do_using_dynamic) {
	spnt = elf_sections;
	for(i=0;i < epnt->e_shnum; i++, spnt++){
	  if(spnt->sh_type != 4 && spnt->sh_type != 9) continue;
	  rpnt = (struct Elf32_Rel *) (header + spnt->sh_offset);
	  rel_size = spnt->sh_size;
	  printf("\n Relocation section data:%s (0x%x entries)\n", SECTION_NAME(spnt),
		 rel_size);
	  symsec = &elf_sections[spnt->sh_link];
	  symtab = (struct Elf32_Sym *) (header + symsec->sh_offset);
	  strtab = (char *) (header + elf_sections[symsec->sh_link].sh_offset);
	  dump_relocations(epnt, rpnt, rel_size);
	}
      }
    }

    /* Now dump the symbol table */
    if(do_syms) {
      if (dynamic_info[DT_HASH] && do_using_dynamic)  {
	int nbucket, nchain, *elf_buckets, *chains;
	int hn, si;
	char * pnt;
	int * hash_addr = (unsigned int *) (dynamic_info[DT_HASH] + header - loadaddr);
	nbucket = *hash_addr++;
	nchain = *hash_addr++;
	elf_buckets = hash_addr;
	hash_addr += nbucket;
	chains = hash_addr;
	printf("Symbol table for image\n");
	for(hn = 0; hn < nbucket; hn++) {
	  if(!elf_buckets[hn]) continue;
	  for(si = elf_buckets[hn]; si; si = chains[si]) {
	    pnt = strtab + symtab[si].st_name;
	    printf("%3.3d %3.3d: %8x %5d %6s %6s %2.2d %3.3d %s\n", si, hn,
		   symtab[si].st_value,
		   symtab[si].st_size,
		   sttinfo[ELF32_ST_TYPE(symtab[si].st_info)],
		   stbinfo[ELF32_ST_BIND(symtab[si].st_info)],
		   symtab[si].st_other,
		   symtab[si].st_shndx,
		   pnt);
	  }
	}
      }
      if (!do_using_dynamic)  {
	int si;
	char * pnt;

	spnt = elf_sections;
	for(i=0;i < epnt->e_shnum; i++, spnt++){
	  if(spnt->sh_type != 2 && spnt->sh_type != 11) continue;
	  printf("Symbols data:%s\n", SECTION_NAME(spnt));
	  symtab = (struct Elf32_Sym *) (header + spnt->sh_offset);
	  strtab = (char *) (header + elf_sections[spnt->sh_link].sh_offset);
	  for(si=0; si< spnt->sh_size/ spnt->sh_entsize; si++) {
	    pnt = strtab + symtab[si].st_name;
	    printf("%3.3d: %8x %5d %6s %6s %2.2d %3.3d %s\n", si, 
		   symtab[si].st_value,
		   symtab[si].st_size,
		   sttinfo[ELF32_ST_TYPE(symtab[si].st_info)],
		   stbinfo[ELF32_ST_BIND(symtab[si].st_info)],
		   symtab[si].st_other,
		   symtab[si].st_shndx,
		   pnt);
	  }
	}
      }
    }
    if (do_dump) {
      int bytes, addr, lbytes, k;
      unsigned char * my_addr;

      spnt = elf_sections;
      for(i=0;i < epnt->e_shnum; i++, spnt++){
	/* See if we need an assembly dump of this section */
	if (dump_sects[i] & 2) {
	  bytes = spnt->sh_size;
	  addr = spnt->sh_addr;
	  my_addr = (unsigned char *) (header + spnt->sh_offset);
	  while(bytes > 0) {
	    fprintf(stderr,"0x%8.8x  ", addr);
	    switch(epnt->e_machine) {
	    case EM_386:
	    case EM_486:
	      lbytes =  db_disasm((unsigned int) my_addr, 0, 0) -
		((unsigned int) my_addr);
	      break;
	    case EM_SPARC:
	    default:
	      fprintf(stderr,"Unable to disassemble code for this platform\n");
	      exit(1);
	    }
	    addr += lbytes;
	    my_addr += lbytes;
	    bytes -= lbytes;
	    fprintf(stderr,"\n");
	  }
	}

	/* OK we need a hex dump of this section */
	if (dump_sects[i] & 1) {
	  bytes = spnt->sh_size;
	  addr = spnt->sh_addr;
	  my_addr = (unsigned char *) (header + spnt->sh_offset);
	  lbytes = (bytes > 15 ? 15 : bytes);
	  while(bytes) {
	    lbytes = (bytes > 16 ? 16 : bytes);
	    printf("0x%8.8x ",addr);
	    for(j=15;j>=0;j--){
	      if (j < lbytes) printf("%2.2x",my_addr[j]);
	      else printf("  ");
	      if(!(j & 0x3)) printf(" ");
	    };
	    for(j=0;j< lbytes;j++){
	      k = my_addr [j];
	      if(k >= ' ' && k < 0x80) printf("%c",k);
	      else printf(".");
	    };
	    printf("\n");
	    my_addr += lbytes;
	    addr += lbytes;
	    bytes -= lbytes;
	  }
	}
      }
    }
    fclose(infile);
  }
}

/* Needed by the i386 disassembler.  For extra credit, someone could
fix this so that we insert symbolic addresses here, esp for GOT/PLT
symbols */

void print_address(unsigned int addr, FILE * outfile){
	fprintf(outfile,"0x%8.8x", addr);
}

/* Needed by the i386 disassembler. */
void db_task_printsym(unsigned int addr){
  print_address(addr, stderr);
}
