/* ----------------------------------------------------------------------------- 
 * stab.c
 *
 *     This file reads stabs data and looks for various properties of a 
 *     given symbol.
 * 
 * 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/stab.c,v 1.22 2001/06/20 15:12:53 beazley Exp $";

/* stabs data structure.   This appears to be somewhat universal. */
typedef struct Stab {
  unsigned        n_strx;         /* index into file string table */
  unsigned char   n_type;         /* type flag (N_TEXT,..)  */
  char            n_other;        /* used by N_SLINE stab */
  unsigned short  n_desc;         /* see stabs documentation */
  unsigned        n_value;        /* value of symbol (or sdb offset) */ 
} Stab;

/* stabs data types used by this module */

#define N_UNDF      0x0           /* undefined         */
#define N_FUN       0x24          /* function          */
#define N_OBJ       0x38          /* object file path  */
#define N_RSYM      0x40          /* Register symbol   */
#define N_SLINE     0x44          /* Source line       */
#define N_SO        0x64          /* Source file name  */
#define N_LSYM      0x80          /* Local symbol      */
#define N_PSYM      0xa0          /* Parameter         */
#define N_LBRAC     0xc0          /* Left brace        */
#define N_RBRAC     0xe0          /* Right brace       */

/* -----------------------------------------------------------------------------
 * stabs type handler
 *
 * Type names are defined as N_LSYM types.   We need to keep a hash table of
 * logical type names and stabs type names.
 *
 * We also need to keep a hash table of stabs types.
 * ----------------------------------------------------------------------------- */

typedef struct stabtype {
  char             *name;
  char             *value;
  struct stabtype  *next;
  int               visit;
} stabtype;

#define HASH_SIZE    113

static int       stab_type_init = 0;
static stabtype *lnames[HASH_SIZE];      /* Hash of local names */
static stabtype  *deadnames[HASH_SIZE];  /* Hash of dead names */

/* Initialize the hash table */

static void init_hash() {
  int i;
  stabtype *s, *sp = 0;

  for (i = 0; i < HASH_SIZE; i++) {
    if (stab_type_init) {
      /* Add stabs to dead list */
      s = lnames[i];
      sp = 0;
      while (s) {
	sp = s;
	s = s->next;
      }
      if (sp) {
	sp->next = deadnames[i];
	deadnames[i] = lnames[i];
      }
    }
    lnames[i] = 0;
  }
  stab_type_init = 1;
}

static int thash(char *name) {
  unsigned int h = 0;
  int i;
  for (i = 0; i < 8 && (*name); i++, name++) {
    h = ((h << 7) + *name);
  }
  return (h % HASH_SIZE);
}

/* Add a symbol to the hash */

static void type_add(char *name, char *value) {
  int h;
  stabtype *s;
  char      sc =0;
  char     *v;
  char     *vr;
  char     *split;

  if (!stab_type_init) {
    init_hash();
    stab_type_init = 1;
  }

  /* Split the "value" up into a type name and a value */
  
  split = strchr(value,'=');
  if (value[0] != '(') split = 0;
  if (split) {
    sc = *split;
    v = value;
    *split = 0;
    vr = split+1;
  } else {
    v = value;
    sc = 0;
    vr = 0;
  }

  h = thash(name);
  s = lnames[h];
  while (s) {
    if (strcmp(s->name,name) == 0) {
      if (strcmp(s->value,v)) {
	s->value = wad_string_lookup(v);
      }
      goto add_more;
    }
    s = s->next;
  }
  s = deadnames[h];
  if (!s) {
    s = (stabtype *) wad_malloc(sizeof(stabtype));
  } else {
    deadnames[h] = s->next; 
  }
  s->name = wad_string_lookup(name);
  s->value = wad_string_lookup(v);
  s->next = lnames[h];
  s->visit = 0;
  lnames[h] = s;

  /* Now take a look at the value.   If it is contains other types, we might be able to define more stuff */
 add_more:
  if (vr) {
    /* There is a mapping to another type */
    type_add(v,vr);
  }
}

static
char *type_resolve(char *name) {
  int h;
  stabtype *s;
  h = thash(name);
  s = lnames[h];
  while(s) {
    if (strcmp(s->name,name) == 0) {
      if (!s->visit) {
	char *c;
	/* The visit flag is set so that we don't get in infinite loops */
	s->visit = 1;
	c = type_resolve(s->value);
	s->visit = 0;
	return c;
      } else {
	return name;
      }
    }
    s = s->next;
  }
  return name;
}  

/* This function tries to resolve base stabs types into a machine equivalent */
static
int type_typecode(char *name) {
  char *range;

  if (name[0] == '*') {
    return WAD_TYPE_POINTER;
  }

  range = strchr(name,';');
  if (!range) return WAD_TYPE_UNKNOWN;
  range++;

  if (name[0] == 'r') {
    /* GNU-style range specifiers */
    if (
	(strcmp(range,"0000000000000;0037777777777;") == 0)
	) {
      return WAD_TYPE_UINT32;
    }
    if (
	(strcmp(range,"0020000000000;0017777777777;") == 0)
	) {
      return WAD_TYPE_INT32;
    }
    if (
	(strcmp(range,"-32768;32767;") == 0)
	) {
      return WAD_TYPE_INT16;
    }
    if (
	(strcmp(range,"0;65535;") == 0) 
	) {
      return WAD_TYPE_UINT16;
    }
    if (
	(strcmp(range,"0;127;") == 0)
	) {
      return WAD_TYPE_CHAR;
    }
    if (
	(strcmp(range,"-128;127;") == 0)
	) {
      return WAD_TYPE_INT8;
    }
    if (
	(strcmp(range,"0;255;") == 0) 
	) {
      return WAD_TYPE_UINT8;
    }
    if (
	(strcmp(range,"4;0;") == 0)
	) {
      return WAD_TYPE_FLOAT;
    }
    if (
	(strcmp(range,"8;0;") == 0)
	) {
      return WAD_TYPE_DOUBLE;
    }
  }
  /* Traditional built-in types */
  if (strcmp(name,"bs4;0;32;") == 0) {
    return WAD_TYPE_INT32;
  } 
  if (strcmp(name,"bs2;0;16;") == 0) {
    return WAD_TYPE_INT16;
  }
  if (strcmp(name,"bs1;0;8;") == 0) {
    return WAD_TYPE_INT8;
  }
  if (strcmp(name,"bsc1;0;8;") == 0) {
    return WAD_TYPE_CHAR;
  }
  if (strcmp(name,"bu4;0;32;") == 0) {
    return WAD_TYPE_UINT32;
  }
  if (strcmp(name,"bu2;0;16;") == 0) {
    return WAD_TYPE_UINT16;
  }
  if (strcmp(name,"bu1;0;8;") == 0) {
    return WAD_TYPE_UINT8;
  }
  if (strcmp(name,"R1;4;") == 0) {
    return WAD_TYPE_FLOAT;
  }
  if (strcmp(name,"R2;8;") == 0) {
    return WAD_TYPE_DOUBLE;
  }
  return WAD_TYPE_UNKNOWN;
}

static void types_print() {
  stabtype *s;
  int i;
  for (i = 0; i < HASH_SIZE; i++) {
    s = lnames[i];
    while (s) {
      wad_printf("%20s  %s\n", s->name, s->value);
      s = s->next;
    }
  }
}

void wad_stab_debug() {
  /*  types_print();*/
}

/* -----------------------------------------------------------------------------
 * match_stab_symbol()
 *
 * Match a stabs symbol name against a stab string.  The stab string may contain
 * extra information delimited by a colon which is not used in the comparsion.
 * Returns 1 on match, 0 on mismatch.
 * ----------------------------------------------------------------------------- */

static int
match_stab_symbol(char *symbol, char *stabtext, int slen) {
  if (strcmp(symbol,stabtext) == 0) {
    return 1;
  }
  if ((strncmp(symbol, stabtext, slen) == 0) && (*(stabtext+slen) == ':')) return 1;
  return 0;
}

static char *
stab_string_parm(char *str) {
  return strchr(str,':');
}

/* -----------------------------------------------------------------------------
 * stab_symbol(Stab *s, char *stabstr)
 *
 * Process stab symbol specifier N_LSYM
 * ----------------------------------------------------------------------------- */

static void
stab_symbol(Stab *s, char *stabstr) {
  char *str;
  char *pstr;
  char name[1024]; 
  char value[65536];

  str = stabstr+s->n_strx;
  pstr = stab_string_parm(str);
  if (!pstr) return;
  
  strncpy(name,str, pstr-str);
  name[(int)(pstr-str)] = 0;
  if ((pstr[1] == 't') || (pstr[1] == 'p') || (pstr[1] == 'r')) {
    /* A stabs type definition */
    /*    printf("stab lsym:  other=%d, desc=%d, value=%d, str='%s'\n", s->n_other,s->n_desc,s->n_value,
	  stabstr+s->n_strx); */
    /*    wad_printf("name = '%s', pstr='%s'\n", name, pstr+2); */
    wad_strcpy(value,pstr+2);
    type_add(name,value);
  }
}


/* -----------------------------------------------------------------------------
 * scan_function()
 *
 * Collect stabs data for a function definition.
 * ----------------------------------------------------------------------------- */

static int
scan_function(Stab *s, char *stabstr, int ns, WadFrame *f) {
  int i;
  unsigned long offset;
  int      get_parms = 1;
  int      nbrace = 0;

  offset = f->pc - f->sym_base;
  if (wad_debug_mode & DEBUG_STABS) {
    wad_printf("---[ %s ] --------------\n", f->sym_name);
  }
  
  for (i = 0; i < ns; i++,s++) {
    if (wad_debug_mode & DEBUG_STABS) {
      wad_printf("   %10d %10x %10d %10d %10d: '%s'\n", s->n_strx, s->n_type, s->n_other, s->n_desc, s->n_value, 
	     stabstr+s->n_strx);
      
    }
    
    if ((s->n_type == N_UNDF) || (s->n_type == N_SO) || /* (s->n_type == N_FUN) || */
	(s->n_type == N_OBJ)) return i;

    if ((s->n_type == N_FUN) && !(strlen(stabstr+s->n_strx))) return 1;

    if (s->n_type == N_LBRAC) {
      nbrace++;
      get_parms = 0;
    }
    if (s->n_type == N_RBRAC) {
      nbrace--;
      if (nbrace <= 0) return i;
    }
    /* Local variable declaration */

    if (s->n_type == N_LSYM) {
      /* This might be a local variable definition */
      /*      wad_printf("local: n_value = %d, offset = %d\n", s->n_value, offset);*/
      if (s->n_desc <= f->loc_line)
      {
	/* Okay. We can pay attention to it */
	char *pname;
	char *c;
	int   len;
	WadLocal *arg, *a;
	pname = stabstr+s->n_strx;
	c = strchr(pname,':');
	if (*(c+1) != '(') continue;
	if (c) {
	  len = (c-pname);
	} else {
	  len = strlen(pname);
	}
	/*	printf("local\n"); */
	stab_symbol(s,stabstr);
	a = f->debug_locals;
	while (a) {
	  if ((strncmp(a->name,pname,len) == 0) && (strlen(a->name) == len)) {
	    /* We already saw this argument.  Given a choice between a register and a stack
	       argument.  We will choose the stack version */
	    a->loc = PARM_STACK;
	    a->stack = s->n_value;
	    break;
	  }
	  a = a->next;
	}
	if (a) continue; /* We got an argument match.  Just skip to the next stab */
	arg = (WadLocal *) wad_malloc(sizeof(WadLocal));
	{
	  char t = pname[len];
	  pname[len] = 0;
	  arg->name = wad_string_lookup(pname);
	  pname[len] = t;
	}
	arg->loc = PARM_STACK;
	arg->line = s->n_desc;
	arg->stack = s->n_value;
	arg->type = 0;
	arg->next = 0;
	{
	  char tname[128];
	  char *t = tname;
	  
	  c+=1;
	  while ((*c) && (*c != '=')) {
	    *t++ = *c++;
	  }
	  *t = 0;
	  t = type_resolve(tname);
	  arg->type = type_typecode(t);
	  if (wad_debug_mode & DEBUG_STABS) {
	    wad_printf("type_resolve '%s' -> '%s' (%d)\n", tname, t, arg->type);
	  }
	}
	if (f->debug_locals) {
	  f->debug_lastlocal->next = arg;
	  f->debug_lastlocal = arg;
	} else {
	  f->debug_locals = arg;
	  f->debug_lastlocal = arg;
	  f->debug_nlocals= 0;
	}
	f->debug_nlocals++;
      }
    }

    if (s->n_type == N_SLINE) {
      get_parms = 0;
      if (s->n_value <= offset) {
	f->loc_line = s->n_desc;
      }
    } else if (((s->n_type == N_PSYM) || (s->n_type == N_RSYM)) && get_parms) {
      /* Parameter counting */
      char *pname;
      char *c;
      int   len;
      WadLocal *arg;
      pname = stabstr+s->n_strx;
      c = strchr(pname,':');
      if (c) {
	len = (c-pname);
      } else {
	len = strlen(pname);
      }
      /* Get type information */
      
      stab_symbol(s,stabstr);
      
      /* Check if the argument was already used */
      /* In this case, the first stab simply identifies an argument.  The second
	 one identifies its location for the debugger */
      
      {
	/* Need to do some fix up for linux here */
	WadLocal *a = f->debug_args;
	while (a) {
	  if ((strncmp(a->name,pname,len) == 0) && (strlen(a->name) == len)) {
	    /* We already saw this argument.  Given a choice between a register and a stack
	       argument.  We will choose the stack version */
	    
	    if (a->loc == PARM_STACK) {
	      break;
	    }
	    /* Go ahead and use the new argument */
	    if (s->n_type == N_RSYM) {
	      a->loc = PARM_REGISTER;
	      a->reg = s->n_value;
	    } else {
	      a->loc = PARM_STACK;
	      a->stack = s->n_value;
	    }
	    break;
	  }
	  a = a->next;
	}
	if (a) continue; /* We got an argument match.  Just skip to the next stab */
      }
	
      arg = (WadLocal *) wad_malloc(sizeof(WadLocal));
      {
	char t = pname[len];
	pname[len] = 0;
	arg->name = wad_string_lookup(pname);
	pname[len] = t;
      }
      if (s->n_type == N_RSYM) {
	arg->loc = PARM_REGISTER;
	arg->reg = s->n_value;
	arg->stack = 0;
      } else {
	arg->loc = PARM_STACK;
	arg->line = s->n_desc;
	arg->stack = s->n_value;
      }
      arg->type = 0;
      arg->next = 0;
      {
	char tname[128];
	char *t = tname;
	
	c+=2;
	while ((*c) && (*c != '=')) {
	  *t++ = *c++;
	}
	*t = 0;
	t = type_resolve(tname);
	arg->type = type_typecode(t);
	if (wad_debug_mode & DEBUG_STABS) {
	  wad_printf("type_resolve '%s' -> '%s' (%d)\n", tname, t, arg->type);
	}
      }
      if (f->debug_args) {
	f->debug_lastarg->next = arg;
	f->debug_lastarg = arg;
      } else {
	f->debug_args = arg;
	f->debug_lastarg = arg;
	f->debug_nargs= 0;
      }
      f->debug_nargs++;
    }
  }
  return i;
}

/* Given a stabs data segment (obtained somehow), this function tries to
   collect as much information as it can about a given symbol. 

   s points to the stab data.  stabstr points to the stab string section,
   ns is the size of the stab section, symbol is the item of interest,
   and offset is the offset in the object file of the symbol

   Note: this function may recurse upon itself if there are multiple
   stabs sections.

   Note: If a symbol corresponds to a local symbol, it's entirely possible
   that the only stabs data we will find is a file specifier. In this case,
 */

int
wad_search_stab(void *sp, int size, char *stabstr, WadFrame *f) {
  Stab *s;
  int   ns;
  int   i;
  int   found = 0;

  char  *file, *lastfile = 0;

  char   srcfile[MAX_PATH];
  char   objfile[MAX_PATH];

  /* It appears to be necessary to clear the types table on each new stabs section */

  init_hash();

  if (!f->sym_name) return 0;

  s = (Stab *) sp;            /* Stabs data      */
  ns = size/sizeof(Stab);     /* number of stabs */

  srcfile[0] = 0;
  objfile[0] = 0;

  for (i = 0; i < ns; i++, s++) {
    if (wad_debug_mode & DEBUG_STABS) {
      /*      wad_printf("   %10d %10x %10d %10d %10d: '%s'\n", s->n_strx, s->n_type, s->n_other, s->n_desc, s->n_value, 
	      stabstr+s->n_strx); */
      
	     }
    if (s->n_type == N_LSYM) {
      stab_symbol(s,stabstr);
      continue;
    }
    if ((s->n_type == N_UNDF)) { /* && (s->n_desc >= 0)) { */
      /* New stabs section.  We need to be a little careful here. Do a recursive 
	 search of the subsection. */

      if (wad_search_stab(s+1,s->n_desc*sizeof(Stab), stabstr, f)) {
	return 1;
      }

      /* On solaris, each stabs section seems to increment the stab string pointer.  On Linux,
         the linker seems to do a certain amount of optimization that results in a single
         string table. */

#ifdef WAD_SOLARIS
      stabstr += s->n_value;     /* Update the string table location*/
#endif
      i += s->n_desc;
      s += s->n_desc;
      objfile[0] = 0;
      srcfile[0] = 0;
      continue;
    } else if (s->n_type == N_SO) {
      /* Source file specification */
      /* Look for directory */
      file = stabstr+s->n_strx;
      if (strlen(file) && (file[strlen(file)-1] == '/')) {
	wad_strcpy(srcfile,file);
      } else {
	wad_strcat(srcfile,file);
      }
      objfile[0] = 0;
      /* If we have a file match, we might be looking for a local symbol.   If so,
         we'll go ahead and set the srcfile field of the frame */

      /* We're going to check for a file match. Maybe we're looking for a local symbol */
      if (f->sym_file && strcmp(f->sym_file,file) == 0) {
	found = 1;
      }
      lastfile = file;
    } else if (s->n_type == N_OBJ) {
      /* Object file specifier */
      if (objfile[0]) {
	wad_strcat(objfile,"/");
      }
      wad_strcat(objfile,stabstr+s->n_strx);
    } else if (s->n_type == N_FUN) {
      if (match_stab_symbol(f->sym_name, stabstr+s->n_strx, f->sym_nlen)) {
	  if (!f->sym_file || (strcmp(f->sym_file,lastfile) == 0)) {
	    int n;
	    /* Go find debugging information for the function */
	    n = scan_function(s+1, stabstr, ns -i - 1, f);
	    f->loc_srcfile = wad_string_lookup(srcfile);
	    f->loc_objfile = wad_string_lookup(objfile);
	    return 1;
	  }
      }
    }
  }
  /* If found, but no other debugging information was filled in, go ahead and copy the
     source and objfile information */
  
  if ((found) && (!f->debug_check)) {
    f->loc_srcfile = wad_string_lookup(srcfile);
    f->loc_objfile = wad_string_lookup(objfile);
  }
  return found;
}




