/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 *
 * License: GPL (version 3 or any later version).
 * See LICENSE for details. 
 * END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif


#include  <fcntl.h>
#include  <stdio.h>
#ifdef linux
#include <elf.h>
#include <libelf/libelf.h>
#else
#if defined(__osf__)
#include <elf_abi.h>
#else
#include  <libelf.h>
#endif
#endif
#include  <stdlib.h>
#include  <string.h>
#include <ctype.h>

#if defined(sparc) || defined(__sparc)
#include <sys/elf_SPARC.h>
#else
#if !defined(linux) && !defined(__osf__)
#include <sys/elf_386.h>
#endif
#endif


char *reldate = "23-APR-2002";

char *ofname = NULL;
FILE *of = NULL;

static void failure(char *s);

struct segment {
  char *vaddr;
  int len;
  char *physaddr;
};

struct segment segments[65536];
int segment_max;

char *modes[8] = { "---", "--X", "-W-", "-WX", "R--", "R-X", "RW-", "RWX" };

char *erf;
char *erf2;

char *find_string(char *addr,int l)
{
  int i,off,j;
  char *np;
  
  for (i = 0; i < segment_max;i++) {
    if (addr >= segments[i].vaddr && 
	addr <= (segments[i].vaddr + segments[i].len)) {
      off = addr - segments[i].vaddr;
      
      np = segments[i].physaddr + off;
      
      
      for (j = 0; j < 256; j++) {
	if (np[j] == '\0') {
	  return np;
	}
	if (!isascii(np[j])) {
	  break;
	}
      }
      if (l) {
	return("<bad pointer>");
      }
      return NULL;
    }
  }
  if (l) {
    return "<out of segment>";
  }
  return NULL;
}

struct iii_msgarray {
  unsigned int magic1;
  unsigned int magic2;
  unsigned int pos;
  unsigned int magic4;
  void *pointers[128][5];
} iii_msgarray;


void view_debug(char *cp)
{
  int i;
  char *s;
  char *s2;
  struct iii_msgarray *p = (struct iii_msgarray *)cp;

  for (i = 0; i < 128; i++) {
    int dl;
    int j,ap = 1;

    if (p->pointers[i][1] == NULL) continue;

    if (i == p->pos) {
      fprintf(of,"*");
    } else {
      fprintf(of," ");
    }

    dl = (int)p->pointers[i][0];
    switch(dl) {
    case 0:
      fprintf(of,"E ");
      break;
    case 1:
      fprintf(of,"  ");
      break;
    case 4:
      fprintf(of,"A ");
      break;
    case 8:
      fprintf(of,"C ");
      break;
    case 64:
      fprintf(of,"G ");
      break;
    case 4096:
      fprintf(of,"R ");
      break;
    case 0xffff:
      fprintf(of,"A ");
      break;
    default:
      fprintf(of," %5d ",p->pointers[i][0]);
    }
    
    s = find_string(p->pointers[i][1],1);
    
    for (j = 0; s[j] != '\0'; j++) {
      if (s[j] == '\n') break;
      if (s[j] == '%') {
	if (s[j+1] == 'l') j++;

	switch(s[j+1]) {
	case '%':
	default:
	  fprintf(of,"%c",s[j+1]);
	  break;
	case 'x':
	  ap++;
	  if (ap >= 5) {
	    fprintf(of,"(? (%%x)");
	  } else {
	    fprintf(of,"%x", p->pointers[i][ap]);
	  }
	  break;
	case 'd':
	  ap++;
	  if (ap >= 5) {
	    fprintf(of,"(? (%%d)");	    
	  } else {
	    fprintf(of,"%d", p->pointers[i][ap]);	    
	  }
	  break;
	case 's':
	  ap++;
	  if (ap >= 5) {
	    fprintf(of,"(? (%%s)");	    
	  } else {
	    s2 = find_string(p->pointers[i][ap],0);
	    if (s2) {
	      fprintf(of,"%s", s2);
	    } else {
	      fprintf(of,"(0x%x)", p->pointers[i][ap]);
	    }
	  }
	  break;
	}
	j++;
      } else {
	fprintf(of,"%c",s[j]);
      }
    }
    fprintf(of,"\n");
  }
}

int seek_debug(char *erf,int start, int mx)
{
  int i;
  unsigned int m1 = 0x0abbccdd;
  unsigned int m2 = 0xdeadbeef;
  
  for (i=start; i < mx; i+= sizeof(m1)) {
    if (memcmp(&erf[i],&m1,sizeof(m1)) == 0 && 
	memcmp(&erf[i+4],&m2,sizeof(m2)) == 0) {
      view_debug(&erf[i]);
      return 1;
    }
  }
  
  return 0;
}

#if !defined(__osf__)
void add_segment(Elf32_Phdr *phdr,char *base,int which)
{
  int i;

  i = segment_max;

  if (i >= 65536) {
    fprintf(of,"too many segments\n");
    exit(1);
  }
  i++;
  segment_max = i;
  
  segments[i].vaddr = (char *)phdr->p_vaddr;
  segments[i].len = phdr->p_filesz;
  segments[i].physaddr = base + phdr->p_offset;
}

void load_segments(Elf *elf,int phnum,char *base,int flen, int which)
{
  Elf32_Phdr *phdr;
  int i;

    
  phdr = elf32_getphdr(elf);
  
  if (phdr == NULL) {
    failure("getphdr");
  }
  
  /* printf("headers at %d\n",ehdr->e_phoff); */
  
  for (i = 0; i < phnum; i++) {
    if (phdr[i].p_type == PT_NOTE) {
      if (which) {
	fprintf(of,"%d: NOTE offset=0x%x filesz=0x%x flags=%d align=%d\n",
	       i, phdr[i].p_offset, 
	       phdr[i].p_filesz, 
	       phdr[i].p_flags, phdr[i].p_align);
      }
      
    } else if (phdr[i].p_type == PT_LOAD) {
      if (which) {
	fprintf(of,"%d: LOAD vaddr=0x%x filesz=0x%x memsz=0x%x mode=%s\n",
	       i, phdr[i].p_vaddr,
	       phdr[i].p_filesz, phdr[i].p_memsz,
	       modes[phdr[i].p_flags & 0x7]);
      }

      if (phdr[i].p_offset && phdr[i].p_filesz) {
	if (phdr[i].p_offset + phdr[i].p_filesz > flen) {
	  fprintf(of,"%d: segment out of range - core file is incomplete.\n",i);
	  continue;
	}
      }
      
      if ((phdr[i].p_flags == 7 || phdr[i].p_flags == 5) && phdr[i].p_filesz) {
	add_segment(&phdr[i],base,which);
      }
    } else {
      
      if (which) {
	fprintf(of,"%d: type=%d offset=0x%x vaddr=0x%x p=0x%x filesz=%d memsz=%d flags=%d align=%d\n",
	       i, phdr[i].p_type,phdr[i].p_offset, phdr[i].p_vaddr,
	       phdr[i].p_paddr, phdr[i].p_filesz, phdr[i].p_memsz,
	       phdr[i].p_flags, phdr[i].p_align);
      }
    }
  }
}

#endif

void try_adb(char *pf,char *cf)
{
  char buf[2048];
  FILE *p;

  if (ofname != NULL) {
    sprintf(buf,"/usr/bin/adb %s %s >>%s",pf,cf,ofname);
  } else {
    sprintf(buf,"/usr/bin/adb %s %s",pf,cf);
  }

  p = popen(buf,"w");

  if (p == NULL) {
    perror(buf);
    return;
  }

  fprintf(p,"$L\n");
  fprintf(p,"$m\n");
  fprintf(p,"$?\n");
  fprintf(p,"$C\n");
#ifdef notanymore
  fprintf(p,"sls_release_date /S\n");
  fprintf(p,"lash_release_date /S\n");
#endif

  fprintf(p,"$q\n");
  pclose(p);
}

void
main(int argc, char ** argv)
{
#if !defined(__osf__)
  Elf32_Ehdr *   ehdr;
  Elf32_Phdr *   phdr;
  Elf *          elf;
  Elf *          elf2;
  int       fd,fd2;
  int exf = 0;
  int i;
  size_t flen = 0;
  size_t flen2 = 0;
  time_t t;

  if (of == NULL) {
    of = stdout;
  }

  time(&t);

  if (argc != 4) {
    fprintf(of,"Core analysis %s needs three arguments: executable, core file, dest file\n",
	    reldate);
    exit(1);
  }
  
  ofname = argv[3];
  of = fopen(ofname,"a");
  
  fprintf(of,"Core analysis %s. Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.\nCopyright (C) 2005 Red Hat, Inc.\nAll rights reserved.\nCurrently %sOpening %s %s\n",reldate,ctime(&t),argv[1],argv[2]);

  if ((fd2 = open(argv[1], O_RDONLY)) == -1) {
    perror(argv[1]);
    exit(1);
  }
  (void) elf_version(EV_CURRENT);  
  /* Obtain the ELF descriptor */
  if ((elf2 = elf_begin(fd2, ELF_C_READ, NULL)) == NULL)
    failure("beginining");

  if ((erf2 = elf_rawfile(elf2,&flen2)) == NULL) {
    failure("elf_rawfile");
  }

  /* Obtain the .shstrtab data buffer */
  if ((ehdr = elf32_getehdr(elf2)) == NULL) {
    failure("reading header");
  }
  
  if (ehdr->e_type == ET_CORE) {
    fprintf(of,"Executable file should be given as the first argument.\n");
    exit(1);
  }
  
  if (ehdr->e_machine == EM_SPARC ||
      ehdr->e_machine == EM_SPARC32PLUS ||
      ehdr->e_machine == EM_SPARCV9) {
    fprintf(of,"architecture is SPARC\n");
    exf = EM_SPARC;
  } else if (ehdr->e_machine == EM_386) {
    fprintf(of,"architecture is x86\n");
    exf = ehdr->e_machine;
  } else {
    fprintf(of,"unknown architecture %d\n",ehdr->e_machine);
    exit(1);
  }
  

  if (ehdr->e_phnum == 0) {
    fprintf(of,"no program header in program file\n");
    exit(1);
  }

  load_segments(elf2,ehdr->e_phnum,erf2,flen2,0);

  /* Open the input file */
  if ((fd = open(argv[2], O_RDONLY)) == -1) {
    perror(argv[2]);
    exit(1);
  }
  
  /* Obtain the ELF descriptor */
  if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL)
    failure("beginining");

  if ((erf = elf_rawfile(elf,&flen)) == NULL) {
    failure("elf_rawfile");
  }

  /* Obtain the .shstrtab data buffer */
  if ((ehdr = elf32_getehdr(elf)) == NULL) {
    failure("reading header");
  }
  
  if (ehdr->e_type != ET_CORE) {
    fprintf(of,"second argument is ELF but not a core file\n");
    exit(1);
  }
  
  if (ehdr->e_machine != exf) {
    fprintf(of,"Architecture mismatch between executable and core file.\n");
    exit(1);
  }
  
  if (ehdr->e_phnum == 0) {
    fprintf(of,"no program header in core file\n");
    exit(1);
  }
  
  load_segments(elf,ehdr->e_phnum,erf,flen,1);
  

#ifdef notanymore
  fprintf(of,"Seeking debug information in core file.\n");

  phdr = elf32_getphdr(elf);
  
  for (i = 0; i < ehdr->e_phnum; i++) {
    if (phdr[i].p_type == PT_LOAD) {
      
      if (phdr[i].p_offset && phdr[i].p_filesz) {
	if (phdr[i].p_offset + phdr[i].p_filesz > flen) {
	  continue;
	}
	if (seek_debug(erf,phdr[i].p_offset,phdr[i].p_filesz)) {
	  break;
	}
      }
      
    }
  }
#endif

  if (of != stdout) {
    fclose(of);
  }

  try_adb(argv[1],argv[2]);

#endif  
}  

static void
failure(char *msg)
{
  fprintf(of, "%s: %s\n", msg, elf_errmsg(elf_errno()));
  exit(1);
}
