/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*	Copyright (c) 1988 AT&T	*/
/*	  All Rights Reserved  	*/


#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
*	File: symintLoad.c
*	Date: 12/15/88
*
*	This file provides code to build the profiling symbol array
*	(array of PROF_SYMBOL).  This array contains all of the
*	symbol table information plus selected debug information for
*	each file and each function that has a coverage array.
*	
*	The symbol table contains entries for every file, every
*	function, and every coverage array.  The debug information
*	has corresponding entries except that there are no entries
*	for the coverage arrays.  (This may change later.)
*
*	The algorithm for building the profiling symbol array
*	consists of scanning the symbol table for file, function,
*	and coverage array entries and building an entry for each.
*	The construction of an entry is constrained by the
*	following factors:
*
*		- An entry is built for every file.
*
*		- An entry is built for a function only if there
*		is a corresponding coverage array for the function.
*
*		- Entries must be ordered in the sense that each
*		non-file entry points to its owner file and each
*		file entry points to the next file (or null).
*
*		- The assembler specification (see C Issue 5 3B2
*		Assembler System Test Specification by Howe, p. 28)
*		states that all local symbols follow their file
*		symbol in the symbol table.  This allows us to relate
*		a function and its coverage array to the file that
*		contains it.
*
*		- For each symbol included in the profiling symbol
*		array, all corresponding symbol table information must
*		be present together with selected debug information.
*		Therefore, the correspondence between a symbol table
*		entry and a debug entry must be established.
*
*		- Although duplicate (static) function names may appear,
*		the names are unique within a given file.  Also, the
*		value (address) of each function is included in both
*		the symbol table information and the debug information.
*		This provides a verifable correspondence between these
*		information sets.
*
*	The algorithm used in this file is as follows:
*/



/*
* This is a discussion of the problem of multiple files with a single
* name.  (See also, the _err_exit call in the routine "add_function".)
*
* Currently, when the executable contains more than one file with
* a common name, we sometimes mix a set of functions with the wrong
* file.  Because the addresses don't match, add_profsymbol tends to
* fail (with _err_exit).  The problem is to consistently choose the
* correct file for the set of functions that are about to be processed.
* This aspect of the problem has been addressed by the code below,
* but there is another part to the story.
*
* In order to match the symbol table with the debug information, we
* have to strip the path (if any) off of the file name; that is,
* the function _CAleaf is used to find the name of the file.  This
* means that even if we make the match, we may still have trouble
* finding the file.  One solution might be to retain a pointer to
* the full name for use at the proper point (when lprof is trying
* to find the source file).  I have not traced this down completely;
* it may or may not work depending upon whether the full path is
* always included in the debug information.  If it is not possible
* to depend on the complete path, then there may be no way to completely
* solve the problem.  (Consider talking to the debugger people about
* this problem; they have to deal with it also.)
*
* Below I have included the code I used to solve the first part of
* the problem.  I have also included an explanation of what each part
* of the code does.  When the code is implemented this way, it does
* work for some cases, but I'm not sure that the assumptions it makes
* are valid.  In particular, it makes implicit assumptions about the
* ordering of names and pointers; you should check that these assumptions
* do not include that the values returned from malloc are monotone
* increasing (which I think they do).
*
* With the following change, add_profsymbol will scan to the first
* file entry of the given name that has not yet been processed.  It
* detects that a file has been processed by noting that sn_value_p
* has been set to zero; it accepts the first one whose value is not
* zero and calls dbfill_tag to "refill" the tag.  Warning: setting
* sn_value_p to zero is dangerous; in particular, you must avoid
* trying to use this value when it is zero.  Some cpus will produce
* a segmentation violation, but the 3b2 does not.
*
* add_profsymbol()
* {
* ...
* 	} else if (stchk_file(prsym_p)) {
* 		if (
* 			(sn_p + 1) < (dblist + dblist_cnt)
* 			&& strcmp(sn_p[0].sn_name_p, sn_p[1].sn_name_p) == 0
* 		) {
* 			_err_warn(
* 				"File name %s was used more than once.",
* 				sn_p->sn_name_p
* 			);
* 			while (
* 				sn_p->sn_value_p == 0
* 				&& sn_p < (dblist + dblist_cnt)
* 			) {
* 				sn_p++;
* 			}
* 			dbfill_tag(sn_p->sn_value_p, &tag);
* 		}
* 		sn_p->sn_value_p = 0;
* 		add_file(&tag);
* 	} else {
* ...
* }
*
* The change to sn_search is only to prepare for a call to sn_compare
* which has been changed to compare on both the name and the pointer
* value (instead of just the name).  (Here, we might be using the
* incorrect assumption that malloc is monotone increasing; this scheme
* should be carefully thought out.)  The change consists of setting
* the sn_value_p in the local tnode to zero; this allows sn_compare
* to ignore the value and compare only the name.
* 
* sn_search()
* {
* ...
* 	tnode.sn_name_p = name_p;
* 	tnode.sn_value_p = 0;
* ...
* }
* 
* This routine used to compare only the name; now it compares both
* the name and  the sn_value_p pointer (see note above sn_search).
* When the value pointer is zero, there is no use in comparing
* that part of the item.
*
* sn_compare()
* {
* 	register int i;
* 
* 	if (i = strcmp(a_p->sn_name_p, b_p->sn_name_p)) {
* 		return(i);
*  	} else if (a_p->sn_value_p == 0 || b_p->sn_value_p == 0) {
*  		return(i);
*  	} else if (a_p->sn_value_p < b_p->sn_value_p) {
*  		return(-1);
*  	} else {
*  		return(1);
*  	}
* }
*/


#include "string.h"
#include "symint.h"
#include "debug.h"

/* search node */
typedef struct {
	char *sn_name_p;
	char *sn_value_p;
} SEARCH_NODE;


PROF_SYMBOL * _symintLoad();

#if isLPROF
static int addcovset();
static SEARCH_NODE *build_stlist();
static int stscan();
static int stchk_focov();
static int stchk_filowog();
static int stchk_gowf();
static int stchk_func();
static int stchk_file();
static int stchk_cov();
static int stchk_match();
static void init_dblist();
static int dbscan_tag();
static void dbfill_tag();
static char * dbseek_att();
static int dbchk_stmnts();
static int dbchk_lowpc();
static int dbchk_highpc();
static int dbchk_filosub();
static PROF_SYMBOL * add_profsymbol();
static int add_function();
static void add_file();
static void check_capacity();
static char * debName();
static LEN4 bytesFor();
static SEARCH_NODE * sn_search();
static int sn_compare();
#ifdef DEBUG
static void sn_dump();
static void profsym_dump();
#endif
static LEN2 alignval2();
static LEN4 alignval4();
static void verify_match();
#endif


/* debug tag */
typedef struct {
	LEN4	tg_length;
	LEN2	tg_value;
	char	*tg_att_p;
	int	tg_attlen;
} DB_TAG;

/*
*	Debug list used to connect a symbol table entry to a debug entry.
*/
static SEARCH_NODE	*dblist;	/* array */
static int		dblist_cnt;	/* number of elements in array */

/*
*	Global symbol table list used to connect coverage array entries
*	with their (global) owner functions.  This list contains all
*	global and weak functions.
*/
static SEARCH_NODE	*gstlist;	/* array */
static int		gstlist_cnt;	/* number of elements in array */

static PROF_FILE	*profPtr;

#define ST_NAME(a)	&profPtr->pf_symstr_p[(a)->st_name]
#define PS_NAME(a)	&profPtr->pf_symstr_p[(a)->ps_sym.st_name]
#define DB_NAME(a)	(a)->ps_dbg.pd_name
#define DB_TAGLEN(ap)	alignval4(ap)
#define DB_STMNTOS(ap)	alignval4((ap) + sizeof(LEN2))
#define DB_PCVALUE(ap)	alignval4((ap) + sizeof(LEN2))

/*
*	NOTE: When you change MATCH_STR, also change pcrt1.s.
*	(See notes on "verify_match" below.)
*/
#ifdef __STDC__
#define	MATCH_NAME	_edata
#define	MATCH_STR	"_edata"
#else
#define	MATCH_NAME	edata
#define	MATCH_STR	"edata"
#endif

static PROF_SYMBOL	*prsym_list_p = 0;	/* the list to return. */
static int		prsym_cnt = 0;	/* #entries in the list */
static int		prsym_cap = 0;	/* #entries capacity allocated */

static int		prstsym_size;	/* size of a symbol table symbol */

static int		add_profsym_search_fail; /* see add_profsymbol() */

#if isLPROF
static int		prsym_size;	/* size of a PROF_SYMBOL */


/* * * * * *
 * addr of line information, and of the PROF_DEBUGE,
 * associated with the last file (symbol) seen.
 * 
 * also, the DEBUGE for the file in effect Before the current one!
 */
#define DBG_LINE_SIZE	(sizeof(LEN4) + sizeof(LEN2) + sizeof(LEN4))
static char		*curf_lp;
static LEN4		curf_lncnt;
static LEN4		curf_base;
static PROF_LINE	*curf_lns_p;

static PROF_DEBUGE	*curf_dbp;
static PROF_DEBUGE	*priorFile_dbp;

#endif

/* * * * * *
 * _symintLoad(proffilePtr)
 * proffilePtr	- PROF_FILE pointer returned by _symintOpen().
 * 
 * returns PROF_SYMBOL * - pointer to the malloc-ed array of
 * 			   symbol information entries, or
 * 			   NULL if fails.
 * 
 * 
 * This routine builds the interface data structure from the data
 * already loaded during _symintOpen().
 * 
 * There are two different incarnations of this routine:
 * one for Prof, and one for Lprof.
 * 
 * Lprof:
 * 
 * 	1. Pass through the symbol table and
 * 	   populate an extended PROF_SYMBOL array.
 * 
 * 	2. Include only certain symbols (see intro).
 * 
 * 	3. Find and include the debug information
 * 	   for each included symbol.
 * 
 * Prof:
 * 
 * 	1. Allocate a duplicate copy of the symbol table
 * 	   data.  (For Prof, a PROF_SYMBOL is just
 * 	   a structure containing an Elf32_Sym!)
 * 
 * 	2. Set internal parameters to reflect this.
 * 
 * 
 * Problems are dealt with by issuing an _err_exit().
 * 
 */
PROF_SYMBOL *
_symintLoad(proffilePtr)
PROF_FILE	*proffilePtr;
{
	Elf_Data	*symdat_p;
	PROF_SYMBOL	*ps;
	int		symcount = 0;
#if isLPROF
	Elf32_Sym	*sym_p;
	Elf32_Sym	*sym_lim_p;
	Elf32_Sym	*next_p;
	Elf32_Sym	*tsym_p;
#endif

	DEBUG_LOC("_symintLoad: top");

	profPtr = proffilePtr;

	/* * * * * *
	 * sanity checks.
	 */
	DEBUG_EXP(printf("profPtr = %x\n", profPtr));
	DEBUG_EXP(printf("profPtr->pf_symdat_p = %x\n", profPtr->pf_symdat_p));
	DEBUG_EXP(printf("profPtr->pf_nstsyms = %x\n", profPtr->pf_nstsyms));

	assert( profPtr != 0 );
	assert( profPtr->pf_symdat_p != 0 );
	assert( profPtr->pf_nstsyms != 0 );

	symdat_p = profPtr->pf_symdat_p;
	DEBUG_EXP(printf("symdat_p->d_size = %x\n", symdat_p->d_size));

	prstsym_size = (symdat_p->d_size / profPtr->pf_nstsyms);
	DEBUG_EXP(printf("_symintLoad: prstsym_size = %d\n",prstsym_size));

#if isLPROF
	prsym_size = prstsym_size + sizeof(PROF_DEBUGE);
	DEBUG_EXP(printf("_symintLoad: prsym_size = %d\n",prsym_size));

	/* * * * * *
	 * Initialize structure parameters.  Updated by add_profsymbol().
	 */
	prsym_cnt = prsym_cap = 0;

	init_dblist();

	sym_lim_p = (Elf32_Sym *)
		(((char *) (symdat_p->d_buf)) + symdat_p->d_size);

	tsym_p = NULL;
	gstlist = build_stlist(tsym_p, sym_lim_p, stchk_gowf, &gstlist_cnt);

	verify_match();

	next_p = NULL;
	(void) stscan(&next_p, sym_lim_p, stchk_file);

	priorFile_dbp = 0;

	sym_p = next_p;
	while (sym_p < sym_lim_p) {
		NO_DEBUG(printf("index for sym_p = %d\n",sym_p->st_name));
		NO_DEBUG(printf("name for sym_p = %s\n", ST_NAME(sym_p)));

		(void) stscan(&next_p, sym_lim_p, stchk_filowog);
		tsym_p = sym_p;
		if (stscan(&tsym_p, next_p, stchk_cov)) {
			symcount += addcovset(sym_p, next_p, tsym_p);
		}
		if (!stchk_file(next_p)) {
			(void) stscan(&next_p, sym_lim_p, stchk_file);
		}
		sym_p = next_p;
	}

	free(gstlist);
	profPtr->pf_nsyms = symcount;

	DEBUG_EXP(printf("number of symbols constructed = %d\n", symcount));
#ifdef DEBUG
	printf("before profsym_dump\n");
	profsym_dump(prsym_list_p, symcount);
	printf("after profsym_dump\n");
#endif

#else	/* isPROF */

	/* * * * * *
	 * alloc a new copy of the array, and
	 *  do a bit-wise copy since the structures
	 *  ARE THE SAME SIZE & (effectively) HAVE THE SAME FIELDS!
	 *  Set the descriptive `parameters' accordingly.
	 * 
	 * (We'll take a copy, to simplify the 'Drop'
	 *  logic.)
	 */

	{
	int st_size;	/* size of symbol table data */

	st_size = symdat_p->d_size;

	NO_DEBUG_LOC("_symintLoad: before malloc for symbol list (PROF)");
	prsym_list_p = (PROF_SYMBOL *) _Malloc(st_size, 1);
	NO_DEBUG_LOC("_symintLoad: after malloc for symbol list (PROF)");
	prsym_cap = prsym_cnt = profPtr->pf_nstsyms;

	NO_DEBUG_LOC("_symintLoad: before memcpy for symbol list (PROF)");
	memcpy((char *) &(prsym_list_p->ps_sym), symdat_p->d_buf, st_size);

	profPtr->pf_nsyms = profPtr->pf_nstsyms;
	}

#endif
	DEBUG_LOC("_symintLoad: bottom");
	return( prsym_list_p );
}


#ifdef  isLPROF
/*
*	addcovset: Add coverage array set to PROF_SYMBOL array.
*
*	The (local) symbols between the given file symbol and the
*	end contain at least one coverage array.  Sort all function
*	and coverage array symbols (by name) within the given bounds
*	and process each of the coverage array symbols by finding
*	its corresponding (local or global) function and adding entries
*	for both the coverage array and the function to the profile
*	symbol array.  Note that the file is also added to the profile
*	symbol array and that pointers are managed accordingly (the file
*	entries are linked and each of the non-file entries points
*	to its owner file).
*
*	- Add the file to the PROF_SYMBOL array.  If the file is not
*	found (i.e., the filename in the debug information does not
*	match the filename in the symbol table), then fail.
*	- Build (allocate) a sorted list of all function and coverage
*	array symbols within the given limits.
*	- Find the top of the coverage array subset of the pointer list.
*	- For each coverage array pointer:
*		- Find its function (look in local list, then global list).
*		- Add function and assoc coverage array to PROF_SYMBOL array.
*	- Free the sorted list.
*
*	Note: "k" is used to avoid having "cov_p" increment beyond
*	the last allocated search node and thereby (possibly) cause
*	a segmentation violation.
*/
static int
addcovset(filsym_p, end_p, cov_p)
Elf32_Sym *filsym_p;
Elf32_Sym *end_p;
Elf32_Sym *cov_p;
{
	SEARCH_NODE	*stl_p;
	SEARCH_NODE	*sncov_p;
	SEARCH_NODE	*snfunc_p;
	PROF_SYMBOL	*ps_p;
	int		k, stlcount;
	char		*fname_p;
	int		symcount = 0;

	DEBUG_LOC("addcovset: top");
	ps_p = add_profsymbol(filsym_p);
	if (add_profsym_search_fail) {
		_err_exit("Unable to locate file %s in debug information.\n",
			ST_NAME(filsym_p)
		);
	}
	ps_p->ps_dbg.pd_file_p = 0;
	symcount++;
	DEBUG_EXP(printf("debug name for ps_p = %s\n", DB_NAME(ps_p)));

	curf_dbp = &(ps_p->ps_dbg);
	if (priorFile_dbp) {
		priorFile_dbp->pd_file_p = curf_dbp;
	}
	priorFile_dbp = curf_dbp;

	stl_p = build_stlist(filsym_p, end_p, stchk_focov, &stlcount);
	sncov_p = sn_search(ST_NAME(cov_p), stl_p, stlcount);
	while (
		(sncov_p-1) >= stl_p
		&& strncmp(
			(sncov_p-1)->sn_name_p,
			COV_PREFIX,
			sizeof(COV_PREFIX)-1
		) == 0
	) {
		sncov_p--;
	}

	k = stlcount;
	while (k-- > 0 && stchk_cov((Elf32_Sym *) (sncov_p->sn_value_p))) {
		fname_p = (char *) &(sncov_p->sn_name_p[sizeof(COV_PREFIX)-1]);
		if (
			(snfunc_p = sn_search(fname_p, stl_p, stlcount))
			|| (snfunc_p = sn_search(fname_p, gstlist, gstlist_cnt))
		) {
			ps_p = add_profsymbol(snfunc_p->sn_value_p);
			ps_p->ps_dbg.pd_file_p = curf_dbp;
			symcount++;

			ps_p = add_profsymbol(sncov_p->sn_value_p);
			ps_p->ps_dbg.pd_file_p = curf_dbp;
			symcount++;
		}
		sncov_p++;
	}

	free(stl_p);
	DEBUG_LOC("addcovset: bottom");
	return(symcount);
}


/*
*	build_stlist: Build a tailored list of symbol table entries.
*/
static SEARCH_NODE *
build_stlist(begin_p, end_p, filter_p, count_p)
Elf32_Sym	*begin_p;
Elf32_Sym	*end_p;
int		(*filter_p)();
int		*count_p;
{
	Elf32_Sym	*tsym_p;
	SEARCH_NODE	*list_p;
	int		i, count;

	DEBUG_LOC("build_stlist: top");
	DEBUG_EXP(printf("begin_p = 0x%lx,  end_p = 0x%lx\n", begin_p, end_p));

	count = 0;
	tsym_p = begin_p;
	while (stscan(&tsym_p, end_p, filter_p)) {
		count++;
	}
	DEBUG_EXP(printf("count = %d\n",count));

	list_p = (SEARCH_NODE *) _Malloc(count, sizeof(*list_p));

	i = 0;
	tsym_p = begin_p;
	while (stscan(&tsym_p, end_p, filter_p)) {
		list_p[i].sn_name_p = ST_NAME(tsym_p);
		list_p[i].sn_value_p = (char *) tsym_p;
		i++;
	}
	DEBUG_EXP(sn_dump("symbol table (pre sort)", list_p, count));

	qsort(list_p, count, sizeof(*list_p), sn_compare);

	DEBUG_EXP(sn_dump("symbol table (post sort)", list_p, count));

	DEBUG_LOC("build_stlist: bottom");
	*count_p = count;
	return(list_p);
}


/*
*	stscan - symbol table scan
*
*	Scan the symbol table until the given limit is reached or
*	the filter function returns true.  Neither the starting
*	symbol (**sym_pp) nor the limit symbol (*lim_p) are legal
*	return values.  Instead, if the starting pointer is NULL,
*	then the first item in the table is a valid return value.
*	This allows the routine to be used as a generator by
*	starting from where the last call stopped.
*/
static int
stscan(sym_pp, lim_p, filter_p)
Elf32_Sym	**sym_pp;
Elf32_Sym	*lim_p;
int		(*filter_p)();
{
	if (*sym_pp == NULL) {
		*sym_pp = (Elf32_Sym *) (profPtr->pf_symdat_p->d_buf);
	} else {
		*sym_pp = (Elf32_Sym *) ((char *) (*sym_pp) + prstsym_size);
	}

	while (*sym_pp < lim_p) {
		if ((*filter_p)(*sym_pp)) {
			return(1);
		}
		*sym_pp = (Elf32_Sym *) ((char *) (*sym_pp) + prstsym_size);
	}
	return(0);
}


/*
*	These routines check the type of a symbol table entry.
*/
static int
stchk_focov(sym_p)	/* symbol is function or coverage array  */
Elf32_Sym *sym_p; {
	return(stchk_func(sym_p) || stchk_cov(sym_p));
}
static int
stchk_filowog(sym_p)	/* symbol is a file, a weak, or a global */
Elf32_Sym *sym_p; {
	return(
		stchk_file(sym_p)
		|| ELF32_ST_BIND(sym_p->st_info) == STB_GLOBAL
		|| ELF32_ST_BIND(sym_p->st_info) == STB_WEAK
	);
}
static int
stchk_gowf(sym_p)	/* symbol is global or weak function */
Elf32_Sym *sym_p; {
	return(
		(stchk_func(sym_p) || stchk_match(sym_p))
		&& (
			ELF32_ST_BIND(sym_p->st_info) == STB_GLOBAL
			|| ELF32_ST_BIND(sym_p->st_info) == STB_WEAK
		)
	);
}
static int
stchk_func(sym_p)	/* symbol is a function */
Elf32_Sym *sym_p; {
	return(ELF32_ST_TYPE(sym_p->st_info) == STT_FUNC);
}
static int
stchk_file(sym_p)	/* symbol is a file */
Elf32_Sym *sym_p; {
	return(ELF32_ST_TYPE(sym_p->st_info) == STT_FILE);
}
static int
stchk_cov(sym_p)	/* symbol is a coverage array */
Elf32_Sym *sym_p; {
	return(
		strncmp(ST_NAME(sym_p), COV_PREFIX, sizeof(COV_PREFIX)-1) == 0
	);
}
static int
stchk_match(sym_p)	/* symbol is the match symbol */
Elf32_Sym *sym_p; {
	return(
		strncmp(ST_NAME(sym_p), MATCH_STR, sizeof(MATCH_STR)-1) == 0
	);
}


/*
*	Initialize debug array (dblist).
*
*	This routine prepares the debug array for searching (see
*	also fillout_sym_dbinfo).
*
*	Initialization proceeds as follows:
*
*		- Count the debug entries that we care about.
*		- _Malloc space to contain the pointers.
*		- Extract pointers and fill in array.
*		- Sort entries alphabetically by name.
*/
static void
init_dblist()
{
	DB_TAG		tag;
	char		*cur_p;
	char		*lim_p;
	Elf_Data	*dat_p;
	int		k;
	extern char	*_CAleaf();

	DEBUG_LOC("init_dblist: top");

	dat_p = profPtr->pf_debugdat_p;

	DEBUG_EXP(printf("dat_p = 0x%lx, d_buf = 0x%x, d_size = %d\n",
		dat_p, dat_p->d_buf, dat_p->d_size
	));

	lim_p = (char *) (dat_p->d_buf) + dat_p->d_size;

	dblist_cnt = 0;
	cur_p = NULL;
	while (dbscan_tag(&cur_p, lim_p, &tag, dbchk_filosub)) {
		dblist_cnt++;
	}

	dblist = (SEARCH_NODE *) _Malloc(dblist_cnt, sizeof(*dblist));

	DEBUG_EXP(printf("dblist_cnt = %d\n",dblist_cnt));
	DEBUG_EXP(printf("dblist = 0x%lx\n", dblist));

	k = 0;
	cur_p = NULL;
	while (dbscan_tag(&cur_p, lim_p, &tag, dbchk_filosub)) {
		dblist[k].sn_name_p = 
			_CAleaf(debName(tag.tg_att_p, tag.tg_attlen));
		dblist[k].sn_value_p = cur_p;
		k++;
	}

	DEBUG_EXP(sn_dump("debug info (pre sort)", dblist, dblist_cnt));

	qsort(dblist, dblist_cnt, sizeof(*dblist), sn_compare);

	DEBUG_EXP(sn_dump("debug info (post sort)", dblist, dblist_cnt));

	DEBUG_LOC("init_dblist: bottom");
}


/*
*	Search for a given tag from the given starting point in
*	the debug information.  If found, fill in the tag at the
*	given pointer and return 1.  Otherwise, return 0.
*/
static int
dbscan_tag(dbpos_pp, dblim_p, tag_p, filter_p)
char	**dbpos_pp;
char	*dblim_p;
DB_TAG	*tag_p;
int	(*filter_p)();
{
	NO_DEBUG_LOC("dbscan_tag: top");

	if (*dbpos_pp == NULL) {
		*dbpos_pp = profPtr->pf_debugdat_p->d_buf;
	} else {
		*dbpos_pp += DB_TAGLEN(*dbpos_pp);
	}

	while (*dbpos_pp < dblim_p) {
		dbfill_tag(*dbpos_pp, tag_p);

		if ((*filter_p)(tag_p->tg_value)) {
			goto success;
		}
		*dbpos_pp += tag_p->tg_length;
	}
	return(0);
success:;
	return(1);
}

static void
dbfill_tag(dbpos_p, tag_p)
char	*dbpos_p;
DB_TAG	*tag_p;
{
	tag_p->tg_length = DB_TAGLEN(dbpos_p);
	tag_p->tg_value = alignval2(dbpos_p + sizeof(LEN4));
	tag_p->tg_att_p = dbpos_p + sizeof(LEN4) + sizeof(LEN2);
	tag_p->tg_attlen = tag_p->tg_length - sizeof(LEN4) - sizeof(LEN2);
}


/*
*	Search the given tag for the given attribute(s).
*	Return a pointer to the attribute or NULL if not found.
*/
static char *
dbseek_att(tag_p, filter_p)
DB_TAG	*tag_p;
int	(*filter_p)();
{
	int	size;
	char	*att_p;

	NO_DEBUG_LOC("dbseek_att: top");

	size = tag_p->tg_attlen;
	att_p = tag_p->tg_att_p;
	NO_DEBUG(printf("attribute size = %d\n",size));
	while (size > 0) {
		LEN4 length;

		if ((*filter_p)(alignval2(att_p))) {
			return(att_p);
		}

		length = bytesFor(att_p);
		NO_DEBUG(printf("bytesFor returns length = %d\n",length));

		size  -= sizeof(LEN2) + length;
		att_p += sizeof(LEN2) + length;
	}
	return((char *) 0);
}


/*
*	dbchk...: Routines that check debug tags and attributes.
*/
static int
dbchk_stmnts(value)	/* statement list (line section) */
LEN2 value;
{
	return(value == AT_stmt_list);
}
static int
dbchk_lowpc(value)	/* statement list (line section) */
LEN2 value;
{
	return(value == AT_low_pc);
}
static int
dbchk_highpc(value)	/* statement list (line section) */
LEN2 value;
{
	return(value == AT_high_pc);
}
static int
dbchk_filosub(value)	/* file or subroutine */
LEN2 value;
{
	switch(value) {
	case TAG_source_file:
	case TAG_subroutine:
	case TAG_global_subroutine:
	case TAG_inline_subroutine:
		return(1);
	default:
		return(0);
	}
}



#define PS_BLKFACTOR	(64)	/* handle this many symbols at a time */

/*
*	add_profsymbol: Add a new entry to the profsymbol array.
*
*	This routine allocates the space required (as needed) for 
*	the PROF_SYMBOL array, extracts the required information
*	from the symbol table and from the debug information, if
*	available (none is recorded for the coverage structures).
*
*	- Check current capacity, assuming one new symbol is to be added.
*	- Copy all of the symbol table information.
*	- Search for the symbol in the debug list.  If this search fails,
*	then flag this failure with "add_profsym_search_fail".  This is used
*	by "addcovset()" for an error exit when a file is not found in the
*	debug information.  This applies only to files - other symbols
*	which are not found may still be valid.
*	- If the symbol is a function, it may be either global or local
*	and local functions are not unique.  Therefore we must compare the
*	address (value) in the symbol table with the address (low_pc)
*	given in the debug information to verify the match.
*	- If the symbol is a file, no verification is needed, but we
*	must change to a new statement list.
*	- If the symbol is a coverage structure, then we are finished with it.
*/
static PROF_SYMBOL *
add_profsymbol(prsym_p)
Elf32_Sym *prsym_p;
{
	DB_TAG		tag;
	PROF_SYMBOL	*ps_p;
	char		*att_p;
	SEARCH_NODE	*sn_p;
	
	DEBUG_LOC("add_profsymbol: top");

	check_capacity();

	ps_p = prsym_list_p + prsym_cnt - 1;
	memcpy((char *) &(ps_p->ps_sym), (char *) prsym_p, prstsym_size);
	memset((char *) &(ps_p->ps_dbg), '\0', sizeof(PROF_DEBUGE));

	DEBUG_EXP(printf("symbol name = %s\n", ST_NAME(prsym_p)));
	ps_p->ps_dbg.pd_name = ST_NAME(prsym_p);

	add_profsym_search_fail = 0;
	if (!(sn_p = sn_search(ST_NAME(prsym_p), dblist, dblist_cnt))) {
		add_profsym_search_fail = 1;
		goto theend;
	}
	DEBUG_EXP(printf("Post search: sn_p->sn_name_p = %s\n",sn_p->sn_name_p));
	dbfill_tag(sn_p->sn_value_p, &tag);

	if (stchk_func(prsym_p)) {
		while (strcmp(ST_NAME(prsym_p), sn_p->sn_name_p) == 0) {
			if (add_function(ps_p, &tag)) {
				break;
			}
			sn_p++;
			dbfill_tag(sn_p->sn_value_p, &tag);
		}
	} else if (stchk_file(prsym_p)) {
		if (
			(sn_p + 1) < (dblist + dblist_cnt)
			&& strcmp(sn_p[0].sn_name_p, sn_p[1].sn_name_p) == 0
		) {
			_err_warn(
				"File name %s was used more than once.",
				sn_p->sn_name_p
			);
		}
		add_file(&tag);
	} else {
		goto theend;
	}

	ps_p->ps_dbg.pd_symtag = tag.tg_value;

theend:;
	DEBUG_LOC("add_profsymbol: bottom");
	return(ps_p);
}


static void
add_file(tag_p)
DB_TAG	*tag_p;
{
	int		i;
	char		*tp;
	PROF_LINE	*lnp;
	char		*att_p;

	att_p = dbseek_att(tag_p, dbchk_stmnts);

	curf_lp = ((char *) profPtr->pf_linedat_p->d_buf) + DB_STMNTOS(att_p);
	curf_lncnt = (alignval4(curf_lp) - 2*sizeof(LEN4)) / DBG_LINE_SIZE;
	curf_base = alignval4(curf_lp + sizeof(LEN4));
	curf_lp += sizeof(LEN4) + sizeof(LEN4);

	curf_lns_p = (PROF_LINE *) _Malloc(curf_lncnt, sizeof(*curf_lns_p));

	i = 0;
	tp = curf_lp;
	lnp = curf_lns_p;
	while (i++ < curf_lncnt) {
		*lnp++ = alignval4(tp);
		tp += DBG_LINE_SIZE;
	}

#ifdef DEBUG
	printf("File Debug Line Information\n");
	printf("   DBG_LINE_SIZE = %d\n", DBG_LINE_SIZE);
	printf("   curf_lp = 0x%x\n", curf_lp);
	printf("   curf_lncnt = %d\n", curf_lncnt);
	printf("   curf_base = 0x%x\n", curf_base);
	printf("   curf_lns_p = 0x%x\n", curf_lns_p);
	printf("Dump of line numbers\n");
	for (i = 0, lnp = curf_lns_p; i < curf_lncnt; i++) {
		printf("   line %d = %d\n", i, lnp[i]);
	}
#endif
}



/*
* add_function -- add to function's PROF_DEBUGE, line# pointer info.
* 
*	Warning: Because we are reading directly from memory, we
*	cannot depend upon the form of the structures we are reading
*	(e.g., pl_delta in PROF_LINE).   Thus, line_p is a "char *"
*	and NOT a "PROF_LINE *".
* 
*
* 	Note from below(***):
* 
* 	Note that this routine finds the range of .line section
* 	entries that should be associated with this function, from
* 	those which belong to this file.
* 
* 	The FIRST line entry for a fcn is selected because
* 	it is the first with a ``delta,'' or memory offset
* 	from the file ``base address (curf_base),''
* 	whose value is GREATER OR EQUAL to the effective offset
* 	associated with this function (lo_delta).
*
* 	The LAST line entry is selected because it is
* 	the LAST with a ``delta'' whose value is LESS THAN
* 	the effective offset of the END of this function (hi_delta)
* 	- i.e. it is the last line number associated with
* 	code that is wholly included in this function!
* 
* 	If no line number is found with a delta value that
* 	exceeds hi_delta (i.e. is part of the next function),
* 	then it is assumed that the last line number entry seen
* 	should simply be accepted as part of this function's set
* 	of line numbers; it simply has no ``bounding line entry.''
*
*/

static int
add_function(ps_p, tag_p)
PROF_SYMBOL	*ps_p;
DB_TAG		*tag_p;
{
	char		*att_p;
	LEN4		high_pc, hi_delta;
	LEN4		low_pc, lo_delta;
	char		*line_p;
	int		first_found = 0;
	PROF_LINE	*pl_p;
	int		i;

	DEBUG_LOC("add_function: top");

	att_p = dbseek_att(tag_p, dbchk_lowpc);
	low_pc = DB_PCVALUE(att_p);
	if (ps_p->ps_sym.st_value != low_pc) {
		DEBUG_LOC("add_function: returning - failed");
		return(0);
	}
	att_p = dbseek_att(tag_p, dbchk_highpc);
	high_pc = DB_PCVALUE(att_p);

	hi_delta = high_pc - curf_base;
	lo_delta = low_pc - curf_base;
	DEBUG_EXP(printf("lo_delta = 0x%x\n", lo_delta));
	DEBUG_EXP(printf("hi_delta = 0x%x\n", hi_delta));

	line_p = curf_lp;
	pl_p = curf_lns_p - 1;

	DEBUG_EXP(printf("Building symbol: %s\n",SYMBOL_NAME(ps_p)));
	i = 0;
	while (i++ < curf_lncnt) {
		LEN4	delad;

		pl_p++;
		delad = alignval4(line_p + sizeof(LEN4) + sizeof(LEN2));

		NO_DEBUG(printf("delad = 0x%x\n", delad));
		NO_DEBUG(printf("line_p = 0x%x\n", line_p));

		if (!first_found && (delad >= lo_delta)) {
			DEBUG_LOC("found first line");
			first_found = 1;
			ps_p->ps_dbg.pd_line_p = pl_p;
		} else if (delad >= hi_delta) {
			DEBUG_LOC("found last line");
			ps_p->ps_dbg.pd_lali_p = pl_p-1;
			break;
		}

		line_p += DBG_LINE_SIZE;
	}

	/*
	*	If the first line is not found, then we have failed
	*	and must return zero.  It is possible (e.g., sometimes
	*	when the function is the last one in the file) for the
	*	first line to be found, but the last not.  In this case,
	*	we assume it simply the last possible line.
	*/
	if (ps_p->ps_dbg.pd_line_p == NULL) {
		_err_exit(
			"Unable to locate line information for function %s.",
			SYMBOL_NAME(ps_p)
		);
	}
	if (ps_p->ps_dbg.pd_lali_p == NULL) {
		DEBUG_LOC("found last line (by default)");
		ps_p->ps_dbg.pd_lali_p = pl_p;
	}
	DEBUG_EXP(printf("first line (pd_line_p) = 0x%x\n",ps_p->ps_dbg.pd_line_p));
	DEBUG_EXP(printf("last line (pd_lali_p) = 0x%x\n",ps_p->ps_dbg.pd_lali_p));

	DEBUG_LOC("add_function: bottom");
	return(1);
}


/* * * * * *
 * If capacity will be exceeded with a new symbol, then
 * increase the capacity.
 */
static void
check_capacity()
{
	if ( ++prsym_cnt > prsym_cap ) {
		if ( prsym_cap == 0 ) {
			prsym_cap = PS_BLKFACTOR;
			prsym_list_p = (PROF_SYMBOL *)
				_Malloc( prsym_size, prsym_cap );
		} else {
			prsym_cap += PS_BLKFACTOR;
			prsym_list_p = (PROF_SYMBOL *)
				_Realloc( (char *) prsym_list_p,
					prsym_size * prsym_cap );
		}
	}
}



/* * * * * *
 * debName -- return ptr to name value for name attr type:attr value pair.
 * 
 * this routine is called by fillout_sym_dbinfo, to scan
 * through a list of debug attributes and return a ptr
 * to the name attrib value, when that type/value pair is found.
 */

static
char *
debName( att_p, att_size )
char	*att_p;		/* ptr to list of (attr_type,attr_value) pairs */
int	att_size;	/* byte length attribute list */
{
	char *name_p = "";
	/* * * * * *
	 * loop through the entries.  when you find a name,
	 * return the addr of the related data (a char string).
	 */

	NO_DEBUG_LOC("debName: top");
	while ( att_size>0 ) {
		LEN2 typea_one;
		LEN4 lena_one;

		NO_DEBUG(printf("att_size = %d\n",att_size));
		typea_one = alignval2(att_p) ;
		NO_DEBUG(printf("typea_one = 0x%x\n",typea_one));
		lena_one  =  bytesFor(att_p) ;
		NO_DEBUG(printf("lena_one = %d\n",lena_one));
		NO_DEBUG(printf("att_p = 0x%x\n",att_p));

		if( typea_one == AT_name ) {
			name_p = att_p + sizeof(LEN2);
			break;
		}
		att_size -= sizeof(LEN2) + lena_one;
		att_p    += sizeof(LEN2) + lena_one;
	}
	NO_DEBUG(printf("name = %s\n",name_p));
	NO_DEBUG_LOC("debName: bottom");
	return( name_p );
}


/* * * * * *
 * bytesFor - indicate the number of bytes of attribute data
 * 		expected to be defined for an attribute type,
 * 		given a ptr to the attribute type:value pair.
 * 
 * we don't particularly care about the specific attribute;
 * more, we are interested in the 'form' of the value
 * associated with this attribute type; hence the 'bit un-masking'.
 * 
 * used by fillout1(). 
 */
static LEN4
bytesFor(attr_p)
char	*attr_p;
{
	LEN4 len;
	LEN2 form, type;
	char *data_p = attr_p + sizeof(LEN2);	/* beginning of attr data */


	NO_DEBUG_LOC("bytesFor: top");

	type = alignval2(attr_p);
	form = type & FORM_MASK ;
	NO_DEBUG(printf("attribute: type = 0x%x",type));
	NO_DEBUG(printf(", form = 0x%x\n",form));
	switch( form )
	{
	case FORM_STRING:	/* NUL-terminated string */
	/* * * * * *
	 * len of string is #chars plus one for NULL.
	 */
		len = strlen(data_p) + 1;
		break;

	case FORM_DATA2:	/* 2 bytes */
		len = 2;
		break;

	case FORM_ADDR:		/* relocated address */
	case FORM_REF:		/* reference to another .debug entry */
	case FORM_DATA4:	/* 4 bytes */
		len = 4;
		break;

	case FORM_DATA8:	/* 8 bytes (two 4-byte values) */
		len = 8;
		break;

	case FORM_BLOCK2:	/* block with 2-byte length, then data */
		len = alignval2(data_p) + 2 ; /* + 2 -> len of length */
		break;

	case FORM_BLOCK4:	/* block with 4-byte length, then data */
		len = alignval4(data_p) + 4 ; /* + 4 -> len of length */
		break;

	case FORM_NONE:		/* error */
	default:
		len = 0;
		break;
	}

	if (len==0)
		_err_exit("Invalid FORM_value %#x for attribute type %#x\n",
			form , type);

	NO_DEBUG_LOC("bytesFor: bottom");
	return(len);
}


/*
*	sn_search: Search sorted list of SEARCH_NODE for given name.
*
*	Search the list for the entry with the given name.  If there
*	is no such entry, return 0.  Otherwise return a pointer to
*	the *first* entry in the list that matches.
*/
static SEARCH_NODE *
sn_search(name_p, list_p, count)
char		*name_p;
SEARCH_NODE	*list_p;
int		count;
{
	SEARCH_NODE	tnode;
	SEARCH_NODE	*sn_p;
	int		index;

	tnode.sn_name_p = name_p;

	sn_p = (SEARCH_NODE *) bsearch(
		(char *) &tnode,
		(char *) list_p,
		count,
		sizeof(*list_p),
		sn_compare
	);

	if (sn_p == NULL) {
		return(NULL);
	}

	index = sn_p - list_p;
	while ((index > 0) && (sn_compare(&list_p[index-1], &tnode) == 0))
		index--;

	return(&list_p[index]);
}

static int
sn_compare(a_p, b_p)
SEARCH_NODE *a_p;
SEARCH_NODE *b_p;
{
	return(strcmp(a_p->sn_name_p, b_p->sn_name_p));
}

#ifdef DEBUG
static void
sn_dump(title_p, snlist_p, sncount)
char		*title_p;
SEARCH_NODE	*snlist_p;
int		sncount;
{
	int i;

	printf("search list for %s: count = %d\n", title_p, sncount);
	for (i = 0; i < sncount; i++) {
		printf(
			"   name = %s,	pointer = 0x%lx\n"
			, snlist_p[i].sn_name_p
			, snlist_p[i].sn_value_p
		);
	}
}

static void
profsym_dump(list_p, count)
PROF_SYMBOL	*list_p;
int		count;
{
	int		i;
	PROF_LINE	*p;

	printf("Dump of %d prof symbols found.\n", count);
	for (i = 0; i < count; i++, list_p++) {
		printf("%d: location 0x%x\n", i, list_p);
		printf("\tSymbol %s\n", ST_NAME(&(list_p->ps_sym)));
		printf("\t   st_size = %d\n", list_p->ps_sym.st_size);
		printf("\t   st_info = 0x%lx\n", list_p->ps_sym.st_info);
		printf("\tDebug Information\n");
		printf("\t   pd_name = %s\n", list_p->ps_dbg.pd_name);
		printf("\t   pd_symtag = 0x%lx\n", list_p->ps_dbg.pd_symtag);
		p = list_p->ps_dbg.pd_line_p;
		printf("\t   *pd_line_p = %d\n", (p ? *p : 0));
		p = list_p->ps_dbg.pd_lali_p;
		printf("\t   *pd_lali_p = %d\n", (p ? *p : 0));
		printf("\t   pd_file_p = 0x%lx\n", list_p->ps_dbg.pd_file_p);
	}
}
#endif


/*
*	alignment routines
*
*	These routines are used to avoid the EMT trap that occurs
*	when moving a unit of data (of 2 or more bytes) across a
*	word boundry.
*/
static LEN2
alignval2(p)
char *p;
{
	LEN2 tmp; char *tp = (char *) &tmp;

	tp[0] = p[0]; tp[1] = p[1];
	return(tmp);
}

static LEN4
alignval4(p)
char *p;
{
	LEN4 tmp; char *tp = (char *) &tmp;

	tp[0] = p[0]; tp[1] = p[1]; tp[2] = p[2]; tp[3] = p[3];
	return(tmp);
}

/*
*	Disscussion of the argv[0] problem and solution.
*
*	If a process is run with a misleading first argument (argv[0]),
*	the profiler will be confused when trying to read the file and
*	match the information against that in memory.  As a confidence
*	check, we compare the address of MATCH_STR as seen in the symbol
*	table to the address of MATCH_STR as seen while running the code.
*	If these are the same, it is *very* unlikely that the file
*	does not correspond to the code in memory.
*
*	Because this code may be run from a shared object, we must
*	insure that our reference to MATCH_STR is that of the main routine.
*	We are depending on MATCH_NAME to not be defined by any shared object
*	(including this one - libprof.so - when so built).
*
*	The search for MATCH_STR in the symbol table is done in _symintLoad
*	because SymintLoad has an ordered version of selected entries from the
*	symbol table which makes searching very efficient (O(log n)).
*
*	See also soqueue.c.
*/

int	_prof_check_match;

static void
verify_match()
{
	SEARCH_NODE	*sn_p;
	Elf32_Sym	*sym_p;
	extern char	MATCH_NAME;

	if (_prof_check_match) {
		if (!(sn_p = sn_search(MATCH_STR, gstlist, gstlist_cnt))) {
			_err_exit("Cannot find match name.");
		} 
		sym_p = (Elf32_Sym *) sn_p->sn_value_p;
		if (sym_p->st_value != (Elf32_Addr) &MATCH_NAME) {
			_err_exit("Location of file for this process unknown.");
		}
	}
}
#endif