// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Runtime symbol table access.  Work in progress.
// The Plan 9 symbol table is not in a particularly convenient form.
// The routines here massage it into a more usable form; eventually
// we'll change 6l to do this for us, but it is easier to experiment
// here than to change 6l and all the other tools.
//
// The symbol table also needs to be better integrated with the type
// strings table in the future.  This is just a quick way to get started
// and figure out exactly what we want.

#include "runtime.h"
#include "defs.h"
#include "os.h"
#include "arch.h"

extern int32 symdat[];

typedef struct Sym Sym;
struct Sym
{
	uintptr value;
	byte symtype;
	byte *name;
//	byte *gotype;
};

// Walk over symtab, calling fn(&s) for each symbol.
static void
walksymtab(void (*fn)(Sym*))
{
	int32 *v;
	byte *p, *ep, *q;
	Sym s;

	if(symdat == nil)
		return;

#ifdef __WINDOWS__
	v = get_symdat_addr();
	p = (byte*)v+8;
#else
	v = symdat;
	p = (byte*)(symdat+2);
#endif
	ep = p + v[0];
	while(p < ep) {
		if(p + 7 > ep)
			break;
		s.value = ((uint32)p[0]<<24) | ((uint32)p[1]<<16) | ((uint32)p[2]<<8) | ((uint32)p[3]);
		if(!(p[4]&0x80))
			break;
		s.symtype = p[4] & ~0x80;
		p += 5;
		s.name = p;
		if(s.symtype == 'z' || s.symtype == 'Z') {
			// path reference string - skip first byte,
			// then 2-byte pairs ending at two zeros.
			q = p+1;
			for(;;) {
				if(q+2 > ep)
					return;
				if(q[0] == '\0' && q[1] == '\0')
					break;
				q += 2;
			}
			p = q+2;
		}else{
			q = mchr(p, '\0', ep);
			if(q == nil)
				break;
			p = q+1;
		}
		p += 4;	// go type
		fn(&s);
	}
}

// Symtab walker; accumulates info about functions.

static Func *func;
static int32 nfunc;

static byte **fname;
static int32 nfname;

static Lock funclock;

static void
dofunc(Sym *sym)
{
	Func *f;

	switch(sym->symtype) {
	case 't':
	case 'T':
	case 'l':
	case 'L':
		if(strcmp(sym->name, (byte*)"etext") == 0)
			break;
		if(func == nil) {
			nfunc++;
			break;
		}
		f = &func[nfunc++];
		f->name = gostringnocopy(sym->name);
		f->entry = sym->value;
		if(sym->symtype == 'L' || sym->symtype == 'l')
			f->frame = -sizeof(uintptr);
		break;
	case 'm':
		if(nfunc > 0 && func != nil)
			func[nfunc-1].frame += sym->value;
		break;
	case 'p':
		if(nfunc > 0 && func != nil) {
			f = &func[nfunc-1];
			// args counts 32-bit words.
			// sym->value is the arg's offset.
			// don't know width of this arg, so assume it is 64 bits.
			if(f->args < sym->value/4 + 2)
				f->args = sym->value/4 + 2;
		}
		break;
	case 'f':
		if(fname == nil) {
			if(sym->value >= nfname)
				nfname = sym->value+1;
			break;
		}
		fname[sym->value] = sym->name;
		break;
	}
}

// put together the path name for a z entry.
// the f entries have been accumulated into fname already.
static void
makepath(byte *buf, int32 nbuf, byte *path)
{
	int32 n, len;
	byte *p, *ep, *q;

	if(nbuf <= 0)
		return;

	p = buf;
	ep = buf + nbuf;
	*p = '\0';
	for(;;) {
		if(path[0] == 0 && path[1] == 0)
			break;
		n = (path[0]<<8) | path[1];
		path += 2;
		if(n >= nfname)
			break;
		q = fname[n];
		len = findnull(q);
		if(p+1+len >= ep)
			break;
		if(p > buf && p[-1] != '/')
			*p++ = '/';
		mcpy(p, q, len+1);
		p += len;
	}
}

// walk symtab accumulating path names for use by pc/ln table.
// don't need the full generality of the z entry history stack because
// there are no includes in go (and only sensible includes in our c);
// assume code only appear in top-level files.
static void
dosrcline(Sym *sym)
{
	static byte srcbuf[1000];
	static struct {
		String srcstring;
		int32 aline;
		int32 delta;
	} files[200];
	static int32 incstart;
	static int32 nfunc, nfile, nhist;
	Func *f;
	int32 i;

	switch(sym->symtype) {
	case 't':
	case 'T':
		if(strcmp(sym->name, (byte*)"etext") == 0)
			break;
		f = &func[nfunc++];
		// find source file
		for(i = 0; i < nfile - 1; i++) {
			if (files[i+1].aline > f->ln0)
				break;
		}
		f->src = files[i].srcstring;
		f->ln0 -= files[i].delta;
		break;
	case 'z':
		if(sym->value == 1) {
			// entry for main source file for a new object.
			makepath(srcbuf, sizeof srcbuf, sym->name+1);
			nhist = 0;
			nfile = 0;
			if(nfile == nelem(files))
				return;
			files[nfile].srcstring = gostring(srcbuf);
			files[nfile].aline = 0;
			files[nfile++].delta = 0;
		} else {
			// push or pop of included file.
			makepath(srcbuf, sizeof srcbuf, sym->name+1);
			if(srcbuf[0] != '\0') {
				if(nhist++ == 0)
					incstart = sym->value;
				if(nhist == 0 && nfile < nelem(files)) {
					// new top-level file
					files[nfile].srcstring = gostring(srcbuf);
					files[nfile].aline = sym->value;
					// this is "line 0"
					files[nfile++].delta = sym->value - 1;
				}
			}else{
				if(--nhist == 0)
					files[nfile-1].delta += sym->value - incstart;
			}
		}
	}
}

// Interpret pc/ln table, saving the subpiece for each func.
static void
splitpcln(void)
{
	int32 line;
	uintptr pc;
	byte *p, *ep;
	Func *f, *ef;
	int32 *v;
	int32 pcquant;

	switch(thechar) {
	case '5':
		pcquant = 4;
		break;
	default:	// 6, 8
		pcquant = 1;
		break;
	}

	if(symdat == nil)
		return;

	// pc/ln table bounds
#ifdef __WINDOWS__
	v = get_symdat_addr();
	p = (byte*)v+8;
#else
	v = symdat;
	p = (byte*)(symdat+2);
#endif
	p += v[0];
	ep = p+v[1];

	f = func;
	ef = func + nfunc;
	pc = func[0].entry;	// text base
	f->pcln.array = p;
	f->pc0 = pc - pcquant;
	line = 0;
	for(; p < ep; p++) {
		if(f < ef && pc > (f+1)->entry) {
			f->pcln.len = p - f->pcln.array;
			f->pcln.cap = f->pcln.len;
			f++;
			f->pcln.array = p;
			f->pc0 = pc;
			f->ln0 = line;
		}
		if(*p == 0) {
			// 4 byte add to line
			line += (p[1]<<24) | (p[2]<<16) | (p[3]<<8) | p[4];
			p += 4;
		} else if(*p <= 64) {
			line += *p;
		} else if(*p <= 128) {
			line -= *p - 64;
		} else {
			pc += pcquant*(*p - 129);
		}
		pc += pcquant;
	}
	if(f < ef) {
		f->pcln.len = p - f->pcln.array;
		f->pcln.cap = f->pcln.len;
	}
}


// Return actual file line number for targetpc in func f.
// (Source file is f->src.)
// NOTE(rsc): If you edit this function, also edit extern.go:/FileLine
int32
funcline(Func *f, uint64 targetpc)
{
	byte *p, *ep;
	uintptr pc;
	int32 line;
	int32 pcquant;
	
	switch(thechar) {
	case '5':
		pcquant = 4;
		break;
	default:	// 6, 8
		pcquant = 1;
		break;
	}

	p = f->pcln.array;
	ep = p + f->pcln.len;
	pc = f->pc0;
	line = f->ln0;
	for(; p < ep && pc <= targetpc; p++) {
		if(*p == 0) {
			line += (p[1]<<24) | (p[2]<<16) | (p[3]<<8) | p[4];
			p += 4;
		} else if(*p <= 64) {
			line += *p;
		} else if(*p <= 128) {
			line -= *p - 64;
		} else {
			pc += pcquant*(*p - 129);
		}
		pc += pcquant;
	}
	return line;
}

static void
buildfuncs(void)
{
	extern byte etext[];

	if(func != nil)
		return;

	// Memory profiling uses this code;
	// can deadlock if the profiler ends
	// up back here.
	m->nomemprof++;

	// count funcs, fnames
	nfunc = 0;
	nfname = 0;
	walksymtab(dofunc);

	// initialize tables
	func = mal((nfunc+1)*sizeof func[0]);
	func[nfunc].entry = (uint64)etext;
	fname = mal(nfname*sizeof fname[0]);
	nfunc = 0;
	walksymtab(dofunc);

	// split pc/ln table by func
	splitpcln();

	// record src file and line info for each func
	walksymtab(dosrcline);

	m->nomemprof--;
}

Func*
findfunc(uintptr addr)
{
	Func *f;
	int32 nf, n;

	lock(&funclock);
	if(func == nil)
		buildfuncs();
	unlock(&funclock);

	if(nfunc == 0)
		return nil;
	if(addr < func[0].entry || addr >= func[nfunc].entry)
		return nil;

	// binary search to find func with entry <= addr.
	f = func;
	nf = nfunc;
	while(nf > 0) {
		n = nf/2;
		if(f[n].entry <= addr && addr < f[n+1].entry)
			return &f[n];
		else if(addr < f[n].entry)
			nf = n;
		else {
			f += n+1;
			nf -= n+1;
		}
	}

	// can't get here -- we already checked above
	// that the address was in the table bounds.
	// this can only happen if the table isn't sorted
	// by address or if the binary search above is buggy.
	prints("findfunc unreachable\n");
	return nil;
}
