/* gnu.a.out.c Functions to handle the symbol table of a a.out exec file.
   from nlist.c and objdump.c Modifications by T.Gingold */

/* Copyright (C) 1991 Free Software Foundation, Inc.
This file is part of the GNU C Library.

The GNU C Library 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.

The GNU C 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with the GNU C Library; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* show_stack "dump" the stack using the debug informations.
   So, it must know the name of the programm (argv[0]), as soon as possible 
   because it can be changed.
   From the name, the programm searchs the complete path.  This is made by
   find_exec.c, taken from dld 2.3.2 (thanks)
   This two operations are made the first time the programm call malloc.
   An other (and better) way is changing crt0.s.  But this is not always 
   possible: we must have the sources or guess how crt0.s works.
   From these information, show_stack load the symbols informations only when
   necessary.  They are stored in memory: some space is allocated by mmap.
   I don't really want to use malloc, because show_stack could be used when
   the state of malloc is strange.  I don'y want store this information at the
   beginning of the programm, because it use a lot of spaces.
   chkr_load_symtab is called each time an function want to use the symbols.
   Yes, alloca make the system a little tortuous.  For memory, alloca
   allocates mem in the stack, so because of the frame pointor, the memory is
   automatically free when the function return.  See auto variable.
 */   

#define NEED_MM    
#include <nlist.h>
#include <stab.h>
#include <fcntl.h>
#include "checker.h"
#include "machine.h"
#include "message.h"

extern int end, etext, edata;

#ifdef CHKR_SAVESTACK
/* variable to access to the symbol table */
static char symtabfileproto[] = TMP_FILE;
static char *symtabfile;		/* file, where symtab is written */
/* size of the temp file. null if symtab has been never loaded.
   1 if not saved, 2 if error when loading */
static unsigned int symtabfilesize;	/* size of the temp file */
static size_t nsymbols;			/* number of symbols */
static struct nlist *symbols;		/* symbol table */
static char *string_table;		/* string table */
static struct exec header;		/* header structure */
int symtab_available = 0;		/* 0: not loaded; >=1: loaded */
extern int nosymtab;

/* return true if the symbol is useful
   usefule symbols are:
   0x04 N_TEXT		
   0x05 N_TEXT | N_EXT	symbol defined in text segment
   0x44 N_SLINE		line number in text segment
   0x64 N_SO		name of main source file
   Not really portable ...
 */
/*#define GOODSYM(x) ( ((x)& ~N_EXT) == N_TEXT || ((x)& ~0x20) == 0x44 )*/
#define GOODSYM(x) ( (x) == N_SO || (x) == N_SOL || (x) == N_SLINE || (x) == N_FUN)

/* You can use any demangler. But, It must not use malloc ... */
static char *simple_demangler(char *name);
extern char *cplus_demangle (char *type); /* from binutils */

/* demangler: translate symbol to function name 
   simple for C name: just remove the '_'
   rather difficult for C++ */
char *(*demangler)(char *name) = simple_demangler;

/* Compare two symbols.
   It is necessary to sort. */
static int symcomp(const __ptr_t ptr1, const __ptr_t ptr2)
{
  return ( (struct nlist*)ptr1 )->n_value -
         ( (struct nlist*)ptr2 )->n_value;
}

/* function called at exit, to remove the temp file */
void
chkr_remove_symtabfile(void)
{
  if (symtabfilesize > 2)
  {
    unlink(symtabfile);
    symtabfilesize = 0;		/* don't try to load it */
  }
}

/* Test if file is the binary fd of this processus
 * Be careful: lseek(2) is used
 */
static int
is_good_image(int fd)
{
  struct exec eheader;
  
  if(lseek(fd, 0, 0) != 0)
    return 0;
    
  /* Load the header of the file */
  if (read(fd, (__ptr_t)&eheader, sizeof(eheader)) != sizeof(eheader))
    return 0;	/* in case of error */

  /* check that this is really an executable:
     The magic number must be valid and
     there must be no relocation infos (on linux) */
  if( N_BADMAG(eheader) || eheader.a_trsize || eheader.a_drsize )
    return 0;
     
#define PAGE_ROUND(a) (((unsigned)(a) + PAGESIZE -1) & ~(PAGE_SIZE - 1))     

  /* check that this is not a bad file */
  switch(N_MAGIC(eheader))
    {
      case ZMAGIC:
      case NMAGIC:
        if (eheader.a_text != (unsigned)&etext
     	    || eheader.a_text + eheader.a_data != PAGE_ROUND(&edata)
     	    || eheader.a_text + eheader.a_data + eheader.a_bss != (unsigned)&end)
     	  return 0;
     	break;
      case OMAGIC:
        if (eheader.a_text != (unsigned)&etext
            || eheader.a_text + eheader.a_data != (unsigned)&edata
            || eheader.a_text + eheader.a_data + eheader.a_bss != (unsigned)&end)
          return 0;
        break;
      default:
        return 0;
    }
  /* seems OK */
  return 1;
}

/* Prepare the symtab file. */
static void
prepare_symtab_file(void)
{
 int fd;		/* file decriptor */
 struct nlist *sym, *sym1;
 char *string_table1;	/* second string table */
 unsigned long int string_table_size;	/* size of the string table */
 int  str_index=0;
 int i;

 if (symtabfilesize < 2)
 {
   /* symtab never loaded or symtab temp file not saved */
   symtabfile = mktemp(symtabfileproto);	/* make the temp file name */
   fd = open(chkr_prog_path, O_RDONLY);		/* open the binary file */
   if (fd == -1)
     goto error;
   /* Load the header of the file */
   if (read(fd, (__ptr_t)&header, sizeof(header)) != sizeof(header))
     goto error;	/* in case of error */

   if (!is_good_image(fd))
     {
       chkr_header(M_CANT_F_GOOD_INFO);
       goto error;
     }

#ifdef CHKR_USE_BITMAP 
   if (N_MAGIC(header) == OMAGIC)
     is_text_writable = 1;	/* can write in the text segment */
#endif         
       
   /* jump to the symbol table */
   if (lseek(fd, N_SYMOFF(header), 0) < 0)
     goto error;
     
   /* allocate (stack)mem for loading the symbols */
   symbols = (struct nlist *) __alloca(header.a_syms);
   nsymbols = header.a_syms / sizeof(symbols[0]);
   
   /* load the symbols table */
   if (read(fd, (__ptr_t) symbols, sizeof(symbols[0]) * nsymbols) != 
 		sizeof(symbols[0])*nsymbols)
     goto error;
  
   /* load the size of all the strings */
   if (read(fd, (__ptr_t) &string_table_size, sizeof(string_table_size)) 
   				!= sizeof(string_table_size))
     goto error;
     
   /* allocate space for the strings */
   string_table_size -= sizeof(string_table_size);
   string_table = (char *) __alloca(string_table_size);
   /* This second string table will contain a sifted and shorter copy 
        of the first string table */
   string_table1 = (char*) __alloca(string_table_size + 30); /* security */
   
   /* load the strings */ 
   if (read(fd, (__ptr_t) string_table, string_table_size)
        != string_table_size)
      goto error;
     
   /* prepare the second string table */
#define BAD_STR M_BAD_STR
   string_table1[str_index++] = '\0';	
   strcpy(&string_table1[str_index], BAD_STR);
   str_index += strlen(BAD_STR) + 1;	/* +1 is the '\0' */
   
   /* shift the symtab: forget unuseful symbols. The second string table
        is initialised */
   for (sym = symbols, sym1 = symbols; sym < &symbols[nsymbols]; sym++ )
     if (GOODSYM(sym->n_type))
       {
         *sym1 = *sym;
         /* Taken from objdump.c (binutils 1.9) */
         if (sym1->n_un.n_strx < 0 || sym1->n_un.n_strx > string_table_size) 
       	   sym1->n_un.n_strx = 1;		/* 1 is BAD_STR */
         else
           {
             char *str = &string_table1[str_index];
             /* copy the string */
             strcpy(str, &string_table[ sym1->n_un.n_strx 
           		- sizeof(string_table_size) ]);
     	     /* Forget some unuseful symbols */
             if (sym1->n_type == N_TEXT
                 && !( strcmp(str, "gcc2_compiled.") && 
         	       strcmp(str, "___gnu_compiled_c") ) )
               continue;
#if 0
             /* forget pathes given as names */
             if (sym1->n_type == 0x64 && strlen(str) > 0 && 
           		str[strlen(str)-1] == '/' )
               continue;
#endif
             sym1->n_un.n_strx = str_index;
             str_index += strlen(&string_table1[str_index]) + 1;
           } 
         sym1++;
       }
   nsymbols = sym1 - symbols;
#if 0
   /* sort the symbols table, according to the value */
   qsort(symbols, nsymbols, sizeof(symbols[0]), symcomp);
#endif
     
   chkr_close(fd);		/* all is right */
 
   /* relocation */
   for(i=0; i < nsymbols; i++)
     symbols[i].n_un.n_name = (char*)(MM_SYM + nsymbols * sizeof(symbols[0]) 
       			        + symbols[i].n_un.n_strx);
   /* write the symtab to the temp file */
   fd = open(symtabfile, O_WRONLY|O_CREAT, 0600);
   if (fd != -1)
     {
       /* write the symbol table */
       if (write(fd, symbols, nsymbols * sizeof(symbols[0])) !=
           nsymbols * sizeof(symbols[0]))
         goto error_bis;
       /* write the string table */
       if (write(fd, string_table1, str_index) != str_index)
         goto error_bis;
       /* write symtabfilesize */
       if (write(fd, &symtabfilesize, sizeof(int)) != sizeof(int))
         goto error_bis;
       /* write nsymbols */
       if (write(fd, &nsymbols, sizeof(int)) != sizeof(int))
         goto error_bis;
       if (chkr_close(fd) == -1)
         goto error_bis;
       symtabfilesize = nsymbols * sizeof(symbols[0]) + str_index;
#if 0         
       /* This file will be removed at the end */
       atexit(chkr_remove_symtabfile);
#endif         
     }
   else
     goto error;
 }
 symbols = (struct nlist*)0;
 return;
   error:	/* in case of error */
 symtabfilesize = 2;	/* don't try again to load symbol table */
 symbols = (struct nlist*)0;
 return;
   error_bis:
 unlink(symtabfile);	/* error after creating: remove the file */
 symtabfilesize = 2;	/* try again next time */
 symbols = (struct nlist*)0;
 return;
}

/* Dup the symtab file.
 * Used at fork time
 */
void
fork_symtab_file(void)
{
 char tmp_file[] = TMP_FILE;
 char *tmpf;
 
 if (symtabfilesize < 2)
   return;	/* nothing to do */
 tmpf = mktemp(tmp_file);
 
 link(symtabfile, tmpf);
 strcpy(symtabfileproto, tmpf);
}

/* prepare exec(2).
 * Must be OK to recreate the file.
 */
void
exec_symtab_file(void)
{
  symtabfilesize = 0;
  symtab_available = 0;
}

/* Load the symbols in memory */
void
chkr_load_symtab(void)
{
 int errno_ori;
 int fd;
 
 if (nosymtab || (++symtab_available) > 1) /* no symtab or already loaded */
   return;
   
 errno_ori = errno;
 if (symtabfilesize < 2)
   prepare_symtab_file();
 if ( symtabfilesize > 2)
   {
     /* load into memory the symbol table */
     fd = open(symtabfile, O_RDONLY);
     if (fd == -1)
       goto error;
     symbols = (struct nlist*)(mmap((char*)MM_SYM, symtabfilesize, PROT_READ,
               MAP_FIXED | MAP_FILE | MAP_PRIVATE, fd, 0));
     string_table = (char*)&symbols[nsymbols];
     if (symbols == (struct nlist*)MM_SYM)
       symtab_available = 1;
     else
       symtab_available = 0;
     chkr_close(fd);
     error:
     if (symtab_available == 0)
       symtabfilesize = 2;	/* don't try again */
   }
 else
   symtab_available = 0;
 errno = errno_ori;
 return;
}

/* Unload the symbols */
void
chkr_unload_symtab(void)
{
 int errno_ori;
 if (symtab_available)
   {
     errno_ori = errno;
     symbols = (struct nlist*)0;
     munmap((char*)MM_SYM, symtabfilesize);
     errno = errno_ori;
     symtab_available--;
   }
}
  
/* Load the symbol table and call func to use the symbol table.
   The symbol table in memory keep only 'useful' symbols, and it is sorted.
   So, to be faster, the symtab is written to a temp file, and all next 
   calls to this function will only load the temp file.  Really faster */
void
chkr_use_symtab(void (*func)(int status))
{
 chkr_load_symtab(); 			   

 /* call the function which use the symbol table 
    status is 1 if the symbols are correctly loaded, otherwise 0 */
 func (symtab_available);
 
 chkr_unload_symtab();
}

/* Test if f is a filename with an .o extention
   This function recognize *.o file. */
static int is_obj_filename(char *f)
{
  while (*f)
    f++;
  if(f[-1]=='o' && f[-2]=='.')
    return 1; 
  else
    return 0;
}

#if 0
/* Show all the information about an address (of a function).  */
void
chkr_show_addr(__ptr_t *ptr)
{
  size_t i;
  struct nlist *function, *file_name, *line;

  if (symtab_available == 0)
    return;  
  function = file_name = line = (struct nlist*)0;
  for (i = 0; symbols[i].n_value <= (unsigned long)*ptr && i < nsymbols; ++i)
  {
    switch(symbols[i].n_type)
    {
    /* Not really portable. I don't known all n_type name */
    /* N_TEXT is a symbol declared in text segment. It can be:
        a static function,
        a label ( GCC declares gcc2_compiled. and ___gnu_compiled_c )
        a file name such as "show_stack.o". No path.
       N_TEXT | N_EXT is an global (exported) symbol. So it is only a non
       static function or a const variable.
       There is no way to know which is the good one...
    */
     case N_TEXT | N_EXT:	/* function */
     		if ( function == (struct nlist*)0 || /* no initialized */
       		      symbols[i].n_value >= function->n_value )  /* better */ 
       		  function = &symbols[i]; 
       		break;
     case 0x64:			/* file */
       		if ( file_name == (struct nlist*)0 || /* no initialized */
       		      symbols[i].n_value >= file_name->n_value)  /* better */
       		  file_name = &symbols[i]; 
       		break;
     case N_TEXT:
     		/* Object file name */
       		if ( file_name == (struct nlist*)0 || /* no initialized */
       		      (symbols[i].n_value > file_name->n_value && /* better */
       		        is_obj_filename(symbols[i].n_un.n_name) ))
       		  file_name = &symbols[i];
       		/* Function name */
       		if ( function == (struct nlist*)0 || /* no initialized */
       		      (symbols[i].n_value >= function->n_value && /* better */
       		        (symbols[i].n_un.n_name)[0]=='_') )
       		  function = &symbols[i];
       		break;
     case 0x44:			/* line number */
       		if ( line == (struct nlist*)0 || /* no initialized */
       		      symbols[i].n_value > line->n_value)  /* better */ 
       		  line = &symbols[i]; 
       		if (symbols[i].n_value == line->n_value &&
       		    symbols[i].n_desc > line->n_desc)
       		  line = &symbols[i];
       		break;
    }
  } 		  
  chkr_printf(M_PC___IN___AT_,
  	*ptr, /* pc or ip */
  	function ? (*demangler)(function->n_un.n_name) 
  			: M_UNKNOWN, /* function name */ 
    	file_name ? file_name->n_un.n_name : M_UNKNOWN, /* file name */
    	line ? (int)line->n_desc : 0);
}
#endif

/* NOTE: This come from bfd/aoutx.h */
void
chkr_show_addr(__ptr_t *ptr)
{
  /* Run down the file looking for the filename, function and linenumber */
  char buffer[100];
  char filename_buffer[200];
  uint line = 0;
  char *functionname = NULL;
  char *directory_name = NULL;
  char *main_file_name = NULL;
  char *current_file_name = NULL;
  char *line_file_name = NULL; /* Value of current_file_name at line number. */
  ulong high_line_vma = ~0;
  ulong low_func_vma = 0;
  struct nlist *func = 0;
  struct nlist *p;
  
  if (symtab_available == 0)
    return;
  for (p = symbols; p < &symbols[nsymbols]; p++)
    {
    next:
      switch (p->n_type)
      {
      case N_SO:
	main_file_name = current_file_name = p->n_un.n_name;
	/* Look ahead to next symbol to check if that too is an N_SO. */
	p++;
	if (p == &symbols[nsymbols])
	  break;
	if (p->n_type != (int)N_SO)
	  goto next;

	/* Found a second N_SO  First is directory; second is filename. */
	directory_name = current_file_name;
	main_file_name = current_file_name = p->n_un.n_name;
	break;
      case N_SOL:
	current_file_name = p->n_un.n_name;
	break;

      case N_SLINE:
	/* We'll keep this if it resolves nearer than the one we have already */
	if (p->n_value >= (ulong)*ptr && p->n_value < high_line_vma)
	  {
	    line = p->n_desc;
	    high_line_vma = p->n_value;
	    line_file_name = current_file_name;
	  }
	break;
      case N_FUN:
	{
	  /* We'll keep this if it is nearer than the one we have already */
	  if (p->n_value >= low_func_vma && p->n_value <= (ulong)*ptr)
	    {
	      low_func_vma = p->n_value;
	      func = p;
	    }
	  if (line && func)
	    {
	      char *function = func->n_un.n_name;
	      char *p;
	      strncpy(buffer, function, sizeof(buffer)-1);
	      buffer[sizeof(buffer)-1] = 0;
	      /* Have to remove : stuff */
	      p = strchr(buffer, ':');
	      if (p != NULL) 
	        *p = '\0';
	      functionname = buffer;
	      goto done;
	    }
	}
	break;
      }
  }

 done:
  if (line)
    main_file_name = line_file_name;
#if 0
  if (main_file_name)
    {
      if (main_file_name[0] == '/' || directory_name == NULL)
        strncpy(filename_buffer, main_file_name, 150);
      else
        {
          strncpy(filename_buffer, directory_name, 140);
          strncat(filename_buffer, main_file_name, 50);
        }
    }
#else
  strncpy(filename_buffer, main_file_name, 150);
#endif

  chkr_printf(M_PC___IN___AT_,
  	*ptr, /* pc or ip */
  	functionname ? functionname : M_UNKNOWN, /* function name */ 
    	filename_buffer ? filename_buffer : M_UNKNOWN, /* file name */
    	line);
}


/* Show all the symbol table.  */
void chkr_dump_symtab(void)
{
  int i;
  struct nlist *sp;

  if (symtab_available == 0)
    return;
  /* From objdump.c (binutils 1.9) */
  chkr_printf("%3s: %4s %5s %4s %8s",
	  "#", "type", "other", "desc", "val");
  for (i = 0, sp = symbols; i < nsymbols; i++, sp++)
    {
      chkr_printf("%3d: %4x %5x %4x %8x %s\n",
	      i,
	      sp->n_type & 0xff,
	      sp->n_other & 0xff,
	      sp->n_desc & 0xffff,
	      sp->n_value,
	      sp->n_un.n_name);
    }
}

/* Very simple demangler for C name */
static char *simple_demangler(char *name)
{
 if(name[0] == '_')
   return &name[1];
 else
   return name;
}
#endif /* CHKR_SAVESTACK */