/* 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 the stack: some space is allocated by alloca.
   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
   beginn of the programm, because it use a lot of spaces.
   chkr_use_symtab is called each time an function want to use the symbols.
    Its mere argument is another function(...) which can access to the loaded
    symbol table. 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.
 */   
    
#ifndef	_MALLOC_INTERNAL
#define _MALLOC_INTERNAL
#include "malloc.h"
#endif

#include <nlist.h>
#include <fcntl.h>
#include "chkrlib.h"

#ifdef CHKR_SAVESTACK
/* variable to access to the symbol table */
static char symtabfileproto[]="/tmp/Chkr.XXXXXX";
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 unsigned long int string_table_size;	/* size of the string table */
static struct exec header;		/* header structure */

/* 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 )

/* 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()
{
  if (symtabfilesize > 2)
  {
    unlink(symtabfile);
    symtabfilesize = 0;		/* don't try to load it */
  }
}

/* load the symbol table and call func to use the symbol table
   At the end of chkr_use_symtab, the symbol table is unusuable.
   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))
{
 int fd;		/* file decriptor */
 int errno_ori;		/* errno is saved */
 int status = 0;	/* bad status */
 struct nlist *sym, *sym1;
 char *string_table1;	/* second string table */
 int  str_index=0;

 errno_ori = errno;	/* save errno, so that chkr_use_symtab should be used
 			   at anytime */
 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 */
   while (fd != -1)		/* used only to use break */
   {
     /* Load the header of the file */
     if (read(fd, (__ptr_t)&header, sizeof(header)) != sizeof(header))
       break;	/* 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(header) || header.a_trsize || header.a_drsize )	
       break;
     
     /* jump to the symbol table */
     if (lseek(fd, N_SYMOFF(header), 0) < 0)
       break;
     
     /* 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)
       break;
  
     /* load the size of all the strings */
     if (read(fd, (__ptr_t) &string_table_size, sizeof(string_table_size)) 
   				!= sizeof(string_table_size))
       break;
     
     /* 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)
       break;
     
     /* prepare the second string table */
#define BAD_STR "<bad string table index>"
     string_table1[str_index++] = '\0';	
     strcpy(&string_table1[str_index], BAD_STR);
     str_index += strlen(BAD_STR) + 1;	/* +1 is the '\0' */
   
     /* sift 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
         {
           /* copy the string */
           strcpy(&string_table1[str_index], &string_table[ sym1->n_un.n_strx 
     			     - sizeof(string_table_size) ]);
     	   /* Forget some unuseful symbols */
           if( sym1->n_type == N_TEXT && 
         	!( strcmp(&string_table1[str_index],"gcc2_compiled.") &&
            	strcmp(&string_table1[str_index],"___gnu_compiled_c") ) )
             continue;
           sym1->n_un.n_strx = str_index;
           str_index += strlen(&string_table1[str_index]) + 1;
         } 
         sym1++;
       }
     nsymbols = sym1 - symbols;
     /* sort the symbols table, according to the value */
     qsort(symbols, nsymbols, sizeof(symbols[0]), symcomp);
     
     /* normalize the symbol table */
     string_table = (char*)&symbols[nsymbols];
     memcpy(string_table, string_table1, str_index);
     string_table_size = str_index;
     
     status = 1; /* all is right */
     break;
     /* NOTREACHED */
   }
   if (fd != -1)
     close(fd);		/* all is right */
   else
   {
     /* in case of error */
     nsymbols = 0;	/* avoid errors */
     symtabfilesize = 2;	/* don't try again to load symbol table */
   }
 
   if (status == 1)
   {
     /* write the symtab to the temp file */
     fd = open(symtabfile, O_WRONLY|O_CREAT, 0600);
     if (fd != -1)
     {
       do
       {
         /* write the symbol table */
         if (write(fd, symbols, nsymbols * sizeof(symbols[0])) !=
             nsymbols * sizeof(symbols[0]))
           break;
         /* write the string table */
         if (write(fd, string_table, string_table_size) != string_table_size)
           break;
         if (close(fd) == -1)
           break;
         symtabfilesize = nsymbols * sizeof(symbols[0]) + str_index;
#if 0         
         /* This file will be removed at the end */
         atexit(chkr_remove_symtabfile);
#endif         
         status = 2;
       }
       while(0);
       if (status != 2)
       {
         unlink(symtabfile);	/* error after creating: remove the file */
         symtabfilesize = 1;	/* try again next time */
       }
     }
     if ( status != 2)
       symtabfilesize = 1;	 /* symtabfile is not written. */
   }
 }
 else if ( symtabfilesize > 2)
 {
   /* load into memory the symbol table */
   do
   {
     fd = open(symtabfile, O_RDONLY);
     if (fd == -1)
       break;
     /* alloc memory to store the symtab */
     symbols = (struct nlist *) __alloca(symtabfilesize);
     /* read it in one read syscall */
     if (read(fd, symbols, symtabfilesize) != symtabfilesize)
       break;
     string_table = (char*)&symbols[nsymbols];
     status = 1;
     close(fd);
     break;
   }
   while(0);
   if (status == 0)
     symtabfilesize = 2;	/* don't try again */
 }

 /* normalize the symbol table: Convert index to C-string (relocation) */
 if (status)
   for(str_index=0; str_index < nsymbols; str_index++)
     symbols[str_index].n_un.n_name = 
     			&string_table[symbols[str_index].n_un.n_strx];

 /* call the function which use the symbol table 
    status is 1 if the symbols are correctly loaded, otherwise 0 */
 func (status);
 
 symbols = (struct nlist*)0;
 errno = errno_ori;	/* errno is secure */
}

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

/* show all the information about an address (of a function).
   chkr_show_addr must be called (indirectely) by chkr_use_symtab */
void chkr_show_addr(__ptr_t *ptr)
{
  size_t i;
  struct nlist *function, *file_name, *line;
  
  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]; 
       		break;
    }
  } 		  
  (*__chkr_trap)(0," pc=0x%08x in %s() at %s:%d",
  	*ptr, /* pc or ip */
  	function ? (*demangler)(function->n_un.n_name) 
  			: "Unknown", /* function name */ 
    	file_name ? file_name->n_un.n_name : "Unknown", /* file name */
    	line ? (int)line->n_desc : 0);
}

/* show all the symbol table.
   chkr_show_addr must be called (indirectely) by chkr_use_symtab */
void chkr_dump_symtab()
{
  int i;
  struct nlist *sp;

  /* From objdump.c (binutils 1.9) */
  (*__chkr_trap)(0,"%3s: %4s %5s %4s %8s",
	  "#", "type", "other", "desc", "val");
  for (i = 0, sp = symbols; i < nsymbols; i++, sp++)
    {
      (*__chkr_trap)(0,"%3d: %4x %5x %4x %8x %s",
	      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 */