#include <u.h>
#include <libc.h>
#include <bio.h>
#include <mach.h>

#define	HUGEINT	0x7fffffff
#define	NNAME	20		/* a relic of the past */

typedef	struct txtsym Txtsym;
typedef	struct file File;
typedef	struct hist Hist;

struct txtsym {				/* Text Symbol table */
	int 	n;			/* number of local vars */
	Sym	**locals;		/* array of ptrs to autos */
	Sym	*sym;			/* function symbol entry */
};

struct hist {				/* Stack of include files & #line directives */
	char	*name;			/* Assumes names Null terminated in file */
	long	line;			/* line # where it was included */
	long	offset;			/* line # of #line directive */
};

struct file {				/* Per input file header to history stack */
	uvlong	addr;			/* address of first text sym */
	union {
		Txtsym	*txt;		/* first text symbol */
		Sym	*sym;		/* only during initilization */
	};
	int	n;			/* size of history stack */
	Hist	*hist;			/* history stack */
};

static	int	debug = 0;

static	Sym	**autos;		/* Base of auto variables */
static	File	*files;			/* Base of file arena */
static	int	fmax;			/* largest file path index */
static	Sym	**fnames;		/* file names path component table */
static	Sym	**globals;		/* globals by addr table */
static	Hist	*hist;			/* base of history stack */
static	int	isbuilt;		/* internal table init flag */
static	long	nauto;			/* number of automatics */
static	long	nfiles;			/* number of files */
static	long	nglob;			/* number of globals */
static	long	nhist;			/* number of history stack entries */
static	long	nsym;			/* number of symbols */
static	int	ntxt;			/* number of text symbols */
static	uchar	*pcline;		/* start of pc-line state table */
static	uchar 	*pclineend;		/* end of pc-line table */
static	uchar	*spoff;			/* start of pc-sp state table */
static	uchar	*spoffend;		/* end of pc-sp offset table */
static	Sym	*symbols;		/* symbol table */
static	Txtsym	*txt;			/* Base of text symbol table */
static	uvlong	txtstart;		/* start of text segment */
static	uvlong	txtend;			/* end of text segment */

static void	cleansyms(void);
static long	decodename(Biobuf*, Sym*);
static short	*encfname(char*);
static int 	fline(char*, int, long, Hist*, Hist**);
static void	fillsym(Sym*, Symbol*);
static int	findglobal(char*, Symbol*);
static int	findlocvar(Symbol*, char *, Symbol*);
static int	findtext(char*, Symbol*);
static int	hcomp(Hist*, short*);
static int	hline(File*, short*, long*);
static void	printhist(char*, Hist*, int);
static int	buildtbls(void);
static int	symcomp(void*, void*);
static int	symerrmsg(int, char*);
static int	txtcomp(void*, void*);
static int	filecomp(void*, void*);

/*
 *	initialize the symbol tables
 */
int
syminit(int fd, Fhdr *fp)
{
	Sym *p;
	long i, l, size;
	vlong vl;
	Biobuf b;
	int svalsz;

	if(fp->symsz == 0)
		return 0;
	if(fp->type == FNONE)
		return 0;

	cleansyms();
	textseg(fp->txtaddr, fp);
		/* minimum symbol record size = 4+1+2 bytes */
	symbols = malloc((fp->symsz/(4+1+2)+1)*sizeof(Sym));
	if(symbols == 0) {
		werrstr("can't malloc %ld bytes", fp->symsz);
		return -1;
	}
	Binit(&b, fd, OREAD);
	Bseek(&b, fp->symoff, 0);
	nsym = 0;
	size = 0;
	if((fp->_magic && (fp->magic & HDR_MAGIC)) || mach->szaddr == 8)
		svalsz = 8;
	else
		svalsz = 4;
	for(p = symbols; size < fp->symsz; p++, nsym++) {
		if(svalsz == 8){
			if(Bread(&b, &vl, 8) != 8)
				return symerrmsg(8, "symbol");
			p->value = beswav(vl);
		}
		else{
			if(Bread(&b, &l, 4) != 4)
				return symerrmsg(4, "symbol");
			p->value = (u32int)beswal(l);
		}
		if(Bread(&b, &p->type, sizeof(p->type)) != sizeof(p->type))
			return symerrmsg(sizeof(p->value), "symbol");

		i = decodename(&b, p);
		if(i < 0)
			return -1;
		size += i+svalsz+sizeof(p->type);

		/* count global & auto vars, text symbols, and file names */
		switch (p->type) {
		case 'l':
		case 'L':
		case 't':
		case 'T':
			ntxt++;
			break;
		case 'd':
		case 'D':
		case 'b':
		case 'B':
			nglob++;
			break;
		case 'f':
			if(strcmp(p->name, ".frame") == 0) {
				p->type = 'm';
				nauto++;
			}
			else if(p->value > fmax)
				fmax = p->value;	/* highest path index */
			break;
		case 'a':
		case 'p':
		case 'm':
			nauto++;
			break;
		case 'z':
			if(p->value == 1) {		/* one extra per file */
				nhist++;
				nfiles++;
			}
			nhist++;
			break;
		default:
			break;
		}
	}
	if (debug)
		print("NG: %ld NT: %d NF: %d\n", nglob, ntxt, fmax);
	if (fp->sppcsz) {			/* pc-sp offset table */
		spoff = (uchar *)malloc(fp->sppcsz);
		if(spoff == 0) {
			werrstr("can't malloc %ld bytes", fp->sppcsz);
			return -1;
		}
		Bseek(&b, fp->sppcoff, 0);
		if(Bread(&b, spoff, fp->sppcsz) != fp->sppcsz){
			spoff = 0;
			return symerrmsg(fp->sppcsz, "sp-pc");
		}
		spoffend = spoff+fp->sppcsz;
	}
	if (fp->lnpcsz) {			/* pc-line number table */
		pcline = (uchar *)malloc(fp->lnpcsz);
		if(pcline == 0) {
			werrstr("can't malloc %ld bytes", fp->lnpcsz);
			return -1;
		}
		Bseek(&b, fp->lnpcoff, 0);
		if(Bread(&b, pcline, fp->lnpcsz) != fp->lnpcsz){
			pcline = 0;
			return symerrmsg(fp->lnpcsz, "pc-line");
		}
		pclineend = pcline+fp->lnpcsz;
	}
	return nsym;
}

static int
symerrmsg(int n, char *table)
{
	werrstr("can't read %d bytes of %s table", n, table);
	return -1;
}

static long
decodename(Biobuf *bp, Sym *p)
{
	char *cp;
	int c1, c2;
	long n;
	vlong o;

	if((p->type & 0x80) == 0) {		/* old-style, fixed length names */
		p->name = malloc(NNAME);
		if(p->name == 0) {
			werrstr("can't malloc %d bytes", NNAME);
			return -1;
		}
		if(Bread(bp, p->name, NNAME) != NNAME)
			return symerrmsg(NNAME, "symbol");
		Bseek(bp, 3, 1);
		return NNAME+3;
	}

	p->type &= ~0x80;
	if(p->type == 'z' || p->type == 'Z') {
		o = Bseek(bp, 0, 1);
		if(Bgetc(bp) < 0) {
			werrstr("can't read symbol name");
			return -1;
		}
		for(;;) {
			c1 = Bgetc(bp);
			c2 = Bgetc(bp);
			if(c1 < 0 || c2 < 0) {
				werrstr("can't read symbol name");
				return -1;
			}
			if(c1 == 0 && c2 == 0)
				break;
		}
		n = Bseek(bp, 0, 1)-o;
		p->name = malloc(n);
		if(p->name == 0) {
			werrstr("can't malloc %ld bytes", n);
			return -1;
		}
		Bseek(bp, -n, 1);
		if(Bread(bp, p->name, n) != n) {
			werrstr("can't read %ld bytes of symbol name", n);
			return -1;
		}
	} else {
		cp = Brdline(bp, '\0');
		if(cp == 0) {
			werrstr("can't read symbol name");
			return -1;
		}
		n = Blinelen(bp);
		p->name = malloc(n);
		if(p->name == 0) {
			werrstr("can't malloc %ld bytes", n);
			return -1;
		}
		strcpy(p->name, cp);
	}
	return n;
}

/*
 *	free any previously loaded symbol tables
 */
static void
cleansyms(void)
{
	if(globals)
		free(globals);
	globals = 0;
	nglob = 0;
	if(txt)
		free(txt);
	txt = 0;
	ntxt = 0;
	if(fnames)
		free(fnames);
	fnames = 0;
	fmax = 0;

	if(files)
		free(files);
	files = 0;
	nfiles = 0;
	if(hist)
		free(hist);
	hist = 0;
	nhist = 0;
	if(autos)
		free(autos);
	autos = 0;
	nauto = 0;
	isbuilt = 0;
	if(symbols)
		free(symbols);
	symbols = 0;
	nsym = 0;
	if(spoff)
		free(spoff);
	spoff = 0;
	if(pcline)
		free(pcline);
	pcline = 0;
}

/*
 *	delimit the text segment
 */
void
textseg(uvlong base, Fhdr *fp)
{
	txtstart = base;
	txtend = base+fp->txtsz;
}

/*
 *	symbase: return base and size of raw symbol table
 *		(special hack for high access rate operations)
 */
Sym *
symbase(long *n)
{
	*n = nsym;
	return symbols;
}

/*
 *	Get the ith symbol table entry
 */
Sym *
getsym(int index)
{
	if(index >= 0 && index < nsym)
		return &symbols[index];
	return 0;
}

/*
 *	initialize internal symbol tables
 */
static int
buildtbls(void)
{
	long i;
	int j, nh, ng, nt;
	File *f;
	Txtsym *tp;
	Hist *hp;
	Sym *p, **ap;

	if(isbuilt)
		return 1;
	isbuilt = 1;
			/* allocate the tables */
	if(nglob) {
		globals = malloc(nglob*sizeof(*globals));
		if(!globals) {
			werrstr("can't malloc global symbol table");
			return 0;
		}
	}
	if(ntxt) {
		txt = malloc(ntxt*sizeof(*txt));
		if (!txt) {
			werrstr("can't malloc text symbol table");
			return 0;
		}
	}
	fnames = malloc((fmax+1)*sizeof(*fnames));
	if (!fnames) {
		werrstr("can't malloc file name table");
		return 0;
	}
	memset(fnames, 0, (fmax+1)*sizeof(*fnames));
	files = malloc(nfiles*sizeof(*files));
	if(!files) {
		werrstr("can't malloc file table");
		return 0;
	}
	hist = malloc(nhist*sizeof(Hist));
	if(hist == 0) {
		werrstr("can't malloc history stack");
		return 0;
	}
	autos = malloc(nauto*sizeof(Sym*));
	if(autos == 0) {
		werrstr("can't malloc auto symbol table");
		return 0;
	}
		/* load the tables */
	ng = nt = nh = 0;
	f = 0;
	tp = 0;
	i = nsym;
	hp = hist;
	ap = autos;
	for(p = symbols; i-- > 0; p++) {
		switch(p->type) {
		case 'D':
		case 'd':
		case 'B':
		case 'b':
			if(debug)
				print("Global: %s %llux\n", p->name, p->value);
			globals[ng++] = p;
			break;
		case 'z':
			if(p->value == 1) {		/* New file */
				if(f) {
					f->n = nh;
					f->hist[nh].name = 0;	/* one extra */
					hp += nh+1;
					f++;
				}
				else
					f = files;
				f->hist = hp;
				f->sym = 0;
				f->addr = 0;
				nh = 0;
			}
				/* alloc one slot extra as terminator */
			f->hist[nh].name = p->name;
			f->hist[nh].line = p->value;
			f->hist[nh].offset = 0;
			if(debug)
				printhist("-> ", &f->hist[nh], 1);
			nh++;
			break;
		case 'Z':
			if(f && nh > 0)
				f->hist[nh-1].offset = p->value;
			break;
		case 'T':
		case 't':	/* Text: terminate history if first in file */
		case 'L':
		case 'l':
			tp = &txt[nt++];
			tp->n = 0;
			tp->sym = p;
			tp->locals = ap;
			if(debug)
				print("TEXT: %s at %llux\n", p->name, p->value);
			if(f && !f->sym) {			/* first  */
				f->sym = p;
				f->addr = p->value;
			}
			break;
		case 'a':
		case 'p':
		case 'm':		/* Local Vars */
			if(!tp)
				print("Warning: Free floating local var: %s\n",
					p->name);
			else {
				if(debug)
					print("Local: %s %llux\n", p->name, p->value);
				tp->locals[tp->n] = p;
				tp->n++;
				ap++;
			}
			break;
		case 'f':		/* File names */
			if(debug)
				print("Fname: %s\n", p->name);
			fnames[p->value] = p;
			break;
		default:
			break;
		}
	}
		/* sort global and text tables into ascending address order */
	qsort(globals, nglob, sizeof(Sym*), symcomp);
	qsort(txt, ntxt, sizeof(Txtsym), txtcomp);
	qsort(files, nfiles, sizeof(File), filecomp);
	tp = txt;
	for(i = 0, f = files; i < nfiles; i++, f++) {
		for(j = 0; j < ntxt; j++) {
			if(f->sym == tp->sym) {
				if(debug) {
					print("LINK: %s to at %llux", f->sym->name, f->addr);
					printhist("... ", f->hist, 1);
				}
				f->txt = tp++;
				break;
			}
			if(++tp >= txt+ntxt)	/* wrap around */
				tp = txt;
		}
	}
	return 1;
}

/*
 * find symbol function.var by name.
 *	fn != 0 && var != 0	=> look for fn in text, var in data
 *	fn != 0 && var == 0	=> look for fn in text
 *	fn == 0 && var != 0	=> look for var first in text then in data space.
 */
int
lookup(char *fn, char *var, Symbol *s)
{
	int found;

	if(buildtbls() == 0)
		return 0;
	if(fn) {
		found = findtext(fn, s);
		if(var == 0)		/* case 2: fn not in text */
			return found;
		else if(!found)		/* case 1: fn not found */
			return 0;
	} else if(var) {
		found = findtext(var, s);
		if(found)
			return 1;	/* case 3: var found in text */
	} else return 0;		/* case 4: fn & var == zero */

	if(found)
		return findlocal(s, var, s);	/* case 1: fn found */
	return findglobal(var, s);		/* case 3: var not found */

}

/*
 * find a function by name
 */
static int
findtext(char *name, Symbol *s)
{
	int i;

	for(i = 0; i < ntxt; i++) {
		if(strcmp(txt[i].sym->name, name) == 0) {
			fillsym(txt[i].sym, s);
			s->handle = (void *) &txt[i];
			s->index = i;
			return 1;
		}
	}
	return 0;
}
/*
 * find global variable by name
 */
static int
findglobal(char *name, Symbol *s)
{
	long i;

	for(i = 0; i < nglob; i++) {
		if(strcmp(globals[i]->name, name) == 0) {
			fillsym(globals[i], s);
			s->index = i;
			return 1;
		}
	}
	return 0;
}

/*
 *	find the local variable by name within a given function
 */
int
findlocal(Symbol *s1, char *name, Symbol *s2)
{
	if(s1 == 0)
		return 0;
	if(buildtbls() == 0)
		return 0;
	return findlocvar(s1, name, s2);
}

/*
 *	find the local variable by name within a given function
 *		(internal function - does no parameter validation)
 */
static int
findlocvar(Symbol *s1, char *name, Symbol *s2)
{
	Txtsym *tp;
	int i;

	tp = (Txtsym *)s1->handle;
	if(tp && tp->locals) {
		for(i = 0; i < tp->n; i++)
			if (strcmp(tp->locals[i]->name, name) == 0) {
				fillsym(tp->locals[i], s2);
				s2->handle = (void *)tp;
				s2->index = tp->n-1 - i;
				return 1;
			}
	}
	return 0;
}

/*
 *	Get ith text symbol
 */
int
textsym(Symbol *s, int index)
{

	if(buildtbls() == 0)
		return 0;
	if(index < 0 || index >= ntxt)
		return 0;
	fillsym(txt[index].sym, s);
	s->handle = (void *)&txt[index];
	s->index = index;
	return 1;
}

/*	
 *	Get ith file name
 */
int
filesym(int index, char *buf, int n)
{
	Hist *hp;

	if(buildtbls() == 0)
		return 0;
	if(index < 0 || index >= nfiles)
		return 0;
	hp = files[index].hist;
	if(!hp || !hp->name)
		return 0;
	return fileelem(fnames, (uchar*)hp->name, buf, n);
}

/*
 *	Lookup name of local variable located at an offset into the frame.
 *	The type selects either a parameter or automatic.
 */
int
getauto(Symbol *s1, int off, int type, Symbol *s2)
{
	Txtsym *tp;
	Sym *p;
	int i, t;

	if(s1 == 0)
		return 0;
	if(type == CPARAM)
		t = 'p';
	else if(type == CAUTO)
		t = 'a';
	else
		return 0;
	if(buildtbls() == 0)
		return 0;
	tp = (Txtsym *)s1->handle;
	if(tp == 0)
		return 0;
	for(i = 0; i < tp->n; i++) {
		p = tp->locals[i];
		if(p->type == t && p->value == off) {
			fillsym(p, s2);
			s2->handle = s1->handle;
			s2->index = tp->n-1 - i;
			return 1;
		}
	}
	return 0;
}

/*
 * Find text symbol containing addr; binary search assumes text array is sorted by addr
 */
static int
srchtext(uvlong addr)
{
	uvlong val;
	int top, bot, mid;
	Sym *sp;

	val = addr;
	bot = 0;
	top = ntxt;
	for (mid = (bot+top)/2; mid < top; mid = (bot+top)/2) {
		sp = txt[mid].sym;
		if(val < sp->value)
			top = mid;
		else if(mid != ntxt-1 && val >= txt[mid+1].sym->value)
			bot = mid;
		else
			return mid;
	}
	return -1;
}

/*
 * Find data symbol containing addr; binary search assumes data array is sorted by addr
 */
static int
srchdata(uvlong addr)
{
	uvlong val;
	int top, bot, mid;
	Sym *sp;

	bot = 0;
	top = nglob;
	val = addr;
	for(mid = (bot+top)/2; mid < top; mid = (bot+top)/2) {
		sp = globals[mid];
		if(val < sp->value)
			top = mid;
		else if(mid < nglob-1 && val >= globals[mid+1]->value)
			bot = mid;
		else
			return mid;
	}
	return -1;
}

/*
 * Find symbol containing val in specified search space
 * There is a special case when a value falls beyond the end
 * of the text segment; if the search space is CTEXT, that value
 * (usually etext) is returned.  If the search space is CANY, symbols in the
 * data space are searched for a match.
 */
int
findsym(uvlong val, int type, Symbol *s)
{
	int i;

	if(buildtbls() == 0)
		return 0;

	if(type == CTEXT || type == CANY) {
		i = srchtext(val);
		if(i >= 0) {
			if(type == CTEXT || i != ntxt-1) {
				fillsym(txt[i].sym, s);
				s->handle = (void *) &txt[i];
				s->index = i;
				return 1;
			}
		}
	}
	if(type == CDATA || type == CANY) {
		i = srchdata(val);
		if(i >= 0) {
			fillsym(globals[i], s);
			s->index = i;
			return 1;
		}
	}
	return 0;
}

/*
 *	Find the start and end address of the function containing addr
 */
int
fnbound(uvlong addr, uvlong *bounds)
{
	int i;

	if(buildtbls() == 0)
		return 0;

	i = srchtext(addr);
	if(0 <= i && i < ntxt-1) {
		bounds[0] = txt[i].sym->value;
		bounds[1] = txt[i+1].sym->value;
		return 1;
	}
	return 0;
}

/*
 * get the ith local symbol for a function
 * the input symbol table is reverse ordered, so we reverse
 * accesses here to maintain approx. parameter ordering in a stack trace.
 */
int
localsym(Symbol *s, int index)
{
	Txtsym *tp;

	if(s == 0 || index < 0)
		return 0;
	if(buildtbls() == 0)
		return 0;

	tp = (Txtsym *)s->handle;
	if(tp && tp->locals && index < tp->n) {
		fillsym(tp->locals[tp->n-index-1], s);	/* reverse */
		s->handle = (void *)tp;
		s->index = index;
		return 1;
	}
	return 0;
}

/*
 * get the ith global symbol
 */
int
globalsym(Symbol *s, int index)
{
	if(s == 0)
		return 0;
	if(buildtbls() == 0)
		return 0;

	if(index >=0 && index < nglob) {
		fillsym(globals[index], s);
		s->index = index;
		return 1;
	}
	return 0;
}

/*
 *	find the pc given a file name and line offset into it.
 */
uvlong
file2pc(char *file, long line)
{
	File *fp;
	long i;
	uvlong pc, start, end;
	short *name;

	if(buildtbls() == 0 || files == 0)
		return ~0;
	name = encfname(file);
	if(name == 0) {			/* encode the file name */
		werrstr("file %s not found", file);
		return ~0;
	} 
		/* find this history stack */
	for(i = 0, fp = files; i < nfiles; i++, fp++)
		if (hline(fp, name, &line))
			break;
	free(name);
	if(i >= nfiles) {
		werrstr("line %ld in file %s not found", line, file);
		return ~0;
	}
	start = fp->addr;		/* first text addr this file */
	if(i < nfiles-1)
		end = (fp+1)->addr;	/* first text addr next file */
	else
		end = 0;		/* last file in load module */
	/*
	 * At this point, line contains the offset into the file.
	 * run the state machine to locate the pc closest to that value.
	 */
	if(debug)
		print("find pc for %ld - between: %llux and %llux\n", line, start, end);
	pc = line2addr(line, start, end);
	if(pc == ~0) {
		werrstr("line %ld not in file %s", line, file);
		return ~0;
	}
	return pc;
}

/*
 *	search for a path component index
 */
static int
pathcomp(char *s, int n)
{
	int i;

	for(i = 0; i <= fmax; i++)
		if(fnames[i] && strncmp(s, fnames[i]->name, n) == 0)
			return i;
	return -1;
}

/*
 *	Encode a char file name as a sequence of short indices
 *	into the file name dictionary.
 */
static short*
encfname(char *file)
{
	int i, j;
	char *cp, *cp2;
	short *dest;

	if(*file == '/')	/* always check first '/' */
		cp2 = file+1;
	else {
		cp2 = strchr(file, '/');
		if(!cp2)
			cp2 = strchr(file, 0);
	}
	cp = file;
	dest = 0;
	for(i = 0; *cp; i++) {
		j = pathcomp(cp, cp2-cp);
		if(j < 0)
			return 0;	/* not found */
		dest = realloc(dest, (i+1)*sizeof(short));
		dest[i] = j;
		cp = cp2;
		while(*cp == '/')	/* skip embedded '/'s */
			cp++;
		cp2 = strchr(cp, '/');
		if(!cp2)
			cp2 = strchr(cp, 0);
	}
	dest = realloc(dest, (i+1)*sizeof(short));
	dest[i] = 0;
	return dest;
}

/*
 *	Search a history stack for a matching file name accumulating
 *	the size of intervening files in the stack.
 */
static int
hline(File *fp, short *name, long *line)
{
	Hist *hp;
	int offset, depth;
	long ln;

	for(hp = fp->hist; hp->name; hp++)		/* find name in stack */
		if(hp->name[1] || hp->name[2]) {
			if(hcomp(hp, name))
				break;
		}
	if(!hp->name)		/* match not found */
		return 0;
	if(debug)
		printhist("hline found ... ", hp, 1);
	/*
	 * unwind the stack until empty or we hit an entry beyond our line
	 */
	ln = *line;
	offset = hp->line-1;
	depth = 1;
	for(hp++; depth && hp->name; hp++) {
		if(debug)
			printhist("hline inspect ... ", hp, 1);
		if(hp->name[1] || hp->name[2]) {
			if(hp->offset){			/* Z record */
				offset = 0;
				if(hcomp(hp, name)) {
					if(*line <= hp->offset)
						break;
					ln = *line+hp->line-hp->offset;
					depth = 1;	/* implicit pop */
				} else
					depth = 2;	/* implicit push */
			} else if(depth == 1 && ln < hp->line-offset)
					break;		/* Beyond our line */
			else if(depth++ == 1)		/* push	*/
				offset -= hp->line;
		} else if(--depth == 1)		/* pop */
			offset += hp->line;	
	}
	*line = ln+offset;
	return 1;
}

/*
 *	compare two encoded file names
 */
static int
hcomp(Hist *hp, short *sp)
{
	uchar *cp;
	int i, j;
	short *s;

	cp = (uchar *)hp->name;
	s = sp;
	if (*s == 0)
		return 0;
	for (i = 1; j = (cp[i]<<8)|cp[i+1]; i += 2) {
		if(j == 0)
			break;
		if(*s == j)
			s++;
		else
			s = sp;
	}
	return *s == 0;
}

/*
 *	Convert a pc to a "file:line {file:line}" string.
 */
long
fileline(char *str, int n, uvlong dot)
{
	long line, top, bot, mid;
	File *f;

	*str = 0;
	if(buildtbls() == 0)
		return 0;
		/* binary search assumes file list is sorted by addr */
	bot = 0;
	top = nfiles;
	for (mid = (bot+top)/2; mid < top; mid = (bot+top)/2) {
		f = &files[mid];
		if(dot < f->addr)
			top = mid;
		else if(mid < nfiles-1 && dot >= (f+1)->addr)
			bot = mid;
		else {
			line = pc2line(dot);
			if(line > 0 && fline(str, n, line, f->hist, 0) >= 0)
				return 1;
			break;
		}
	}
	return 0;
}

/*
 *	Convert a line number within a composite file to relative line
 *	number in a source file.  A composite file is the source
 *	file with included files inserted in line.
 */
static int
fline(char *str, int n, long line, Hist *base, Hist **ret)
{
	Hist *start;			/* start of current level */
	Hist *h;			/* current entry */
	long delta;			/* sum of size of files this level */
	int k;

	start = base;
	h = base;
	delta = h->line;
	while(h && h->name && line > h->line) {
		if(h->name[1] || h->name[2]) {
			if(h->offset != 0) {	/* #line Directive */
				delta = h->line-h->offset+1;
				start = h;
				base = h++;
			} else {		/* beginning of File */
				if(start == base)
					start = h++;
				else {
					k = fline(str, n, line, start, &h);
					if(k <= 0)
						return k;
				}
			}
		} else {
			if(start == base && ret) {	/* end of recursion level */
				*ret = h;
				return 1;
			} else {			/* end of included file */
				delta += h->line-start->line;
				h++;
				start = base;
			}
		}
	}
	if(!h)
		return -1;
	if(start != base)
		line = line-start->line+1;
	else
		line = line-delta+1;
	if(!h->name)
		strncpy(str, "<eof>", n);
	else {
		k = fileelem(fnames, (uchar*)start->name, str, n);
		if(k+8 < n)
			sprint(str+k, ":%ld", line);
	}
/**********Remove comments for complete back-trace of include sequence
 *	if(start != base) {
 *		k = strlen(str);
 *		if(k+2 < n) {
 *			str[k++] = ' ';
 *			str[k++] = '{';
 *		}
 *		k += fileelem(fnames, (uchar*) base->name, str+k, n-k);
 *		if(k+10 < n)
 *			sprint(str+k, ":%ld}", start->line-delta);
 *	}
 ********************/
	return 0;
}

/*
 *	convert an encoded file name to a string.
 */
int
fileelem(Sym **fp, uchar *cp, char *buf, int n)
{
	int i, j;
	char *c, *bp, *end;

	bp = buf;
	end = buf+n-1;
	for(i = 1; j = (cp[i]<<8)|cp[i+1]; i+=2){
		c = fp[j]->name;
		if(bp != buf && bp[-1] != '/' && bp < end)
			*bp++ = '/';
		while(bp < end && *c)
			*bp++ = *c++;
	}
	*bp = 0;
	i =  bp-buf;
	if(i > 1) {
		cleanname(buf);
		i = strlen(buf);
	}
	return i;
}

/*
 *	compare the values of two symbol table entries.
 */
static int
symcomp(void *a, void *b)
{
	int i;

	i = (*(Sym**)a)->value - (*(Sym**)b)->value;
	if (i)
		return i;
	return strcmp((*(Sym**)a)->name, (*(Sym**)b)->name);
}

/*
 *	compare the values of the symbols referenced by two text table entries
 */
static int
txtcomp(void *a, void *b)
{
	return ((Txtsym*)a)->sym->value - ((Txtsym*)b)->sym->value;
}

/*
 *	compare the values of the symbols referenced by two file table entries
 */
static int
filecomp(void *a, void *b)
{
	return ((File*)a)->addr - ((File*)b)->addr;
}

/*
 *	fill an interface Symbol structure from a symbol table entry
 */
static void
fillsym(Sym *sp, Symbol *s)
{
	s->type = sp->type;
	s->value = sp->value;
	s->name = sp->name;
	s->index = 0;
	switch(sp->type) {
	case 'b':
	case 'B':
	case 'D':
	case 'd':
		s->class = CDATA;
		break;
	case 't':
	case 'T':
	case 'l':
	case 'L':
		s->class = CTEXT;
		break;
	case 'a':
		s->class = CAUTO;
		break;
	case 'p':
		s->class = CPARAM;
		break;
	case 'm':
		s->class = CSTAB;
		break;
	default:
		s->class = CNONE;
		break;
	}
	s->handle = 0;
}

/*
 *	find the stack frame, given the pc
 */
uvlong
pc2sp(uvlong pc)
{
	uchar *c, u;
	uvlong currpc, currsp;

	if(spoff == 0)
		return ~0;
	currsp = 0;
	currpc = txtstart - mach->pcquant;

	if(pc<currpc || pc>txtend)
		return ~0;
	for(c = spoff; c < spoffend; c++) {
		if (currpc >= pc)
			return currsp;
		u = *c;
		if (u == 0) {
			currsp += (c[1]<<24)|(c[2]<<16)|(c[3]<<8)|c[4];
			c += 4;
		}
		else if (u < 65)
			currsp += 4*u;
		else if (u < 129)
			currsp -= 4*(u-64);
		else 
			currpc += mach->pcquant*(u-129);
		currpc += mach->pcquant;
	}
	return ~0;
}

/*
 *	find the source file line number for a given value of the pc
 */
long
pc2line(uvlong pc)
{
	uchar *c, u;
	uvlong currpc;
	long currline;

	if(pcline == 0)
		return -1;
	currline = 0;
	currpc = txtstart-mach->pcquant;
	if(pc<currpc || pc>txtend)
		return ~0;

	for(c = pcline; c < pclineend; c++) {
		if(currpc >= pc)
			return currline;
		u = *c;
		if(u == 0) {
			currline += (c[1]<<24)|(c[2]<<16)|(c[3]<<8)|c[4];
			c += 4;
		}
		else if(u < 65)
			currline += u;
		else if(u < 129)
			currline -= (u-64);
		else 
			currpc += mach->pcquant*(u-129);
		currpc += mach->pcquant;
	}
	return ~0;
}

/*
 *	find the pc associated with a line number
 *	basepc and endpc are text addresses bounding the search.
 *	if endpc == 0, the end of the table is used (i.e., no upper bound).
 *	usually, basepc and endpc contain the first text address in
 *	a file and the first text address in the following file, respectively.
 */
uvlong
line2addr(long line, uvlong basepc, uvlong endpc)
{
	uchar *c,  u;
	uvlong currpc, pc;
	long currline;
	long delta, d;
	int found;

	if(pcline == 0 || line == 0)
		return ~0;

	currline = 0;
	currpc = txtstart-mach->pcquant;
	pc = ~0;
	found = 0;
	delta = HUGEINT;

	for(c = pcline; c < pclineend; c++) {
		if(endpc && currpc >= endpc)	/* end of file of interest */
			break;
		if(currpc >= basepc) {		/* proper file */
			if(currline >= line) {
				d = currline-line;
				found = 1;
			} else
				d = line-currline;
			if(d < delta) {
				delta = d;
				pc = currpc;
			}
		}
		u = *c;
		if(u == 0) {
			currline += (c[1]<<24)|(c[2]<<16)|(c[3]<<8)|c[4];
			c += 4;
		}
		else if(u < 65)
			currline += u;
		else if(u < 129)
			currline -= (u-64);
		else 
			currpc += mach->pcquant*(u-129);
		currpc += mach->pcquant;
	}
	if(found)
		return pc;
	return ~0;
}

/*
 *	Print a history stack (debug). if count is 0, prints the whole stack
 */
static void
printhist(char *msg, Hist *hp, int count)
{
	int i;
	uchar *cp;
	char buf[128];

	i = 0;
	while(hp->name) {
		if(count && ++i > count)
			break;
		print("%s Line: %lx (%ld)  Offset: %lx (%ld)  Name: ", msg,
			hp->line, hp->line, hp->offset, hp->offset);
		for(cp = (uchar *)hp->name+1; (*cp<<8)|cp[1]; cp += 2) {
			if (cp != (uchar *)hp->name+1)
				print("/");
			print("%x", (*cp<<8)|cp[1]);
		}
		fileelem(fnames, (uchar *) hp->name, buf, sizeof(buf));
		print(" (%s)\n", buf);
		hp++;
	}
}

#ifdef DEBUG
/*
 *	print the history stack for a file. (debug only)
 *	if (name == 0) => print all history stacks.
 */
void
dumphist(char *name)
{
	int i;
	File *f;
	short *fname;

	if(buildtbls() == 0)
		return;
	if(name)
		fname = encfname(name);
	for(i = 0, f = files; i < nfiles; i++, f++)
		if(fname == 0 || hcomp(f->hist, fname))
			printhist("> ", f->hist, f->n);

	if(fname)
		free(fname);
}
#endif
