/*
 * Dwarf info parse and search.
 */

#include <u.h>
#include <libc.h>
#include <bio.h>
#include "elf.h"
#include "dwarf.h"

enum
{
	DwarfAttrSibling = 0x01,
	DwarfAttrLocation = 0x02,
	DwarfAttrName = 0x03,
	DwarfAttrOrdering = 0x09,
	DwarfAttrByteSize = 0x0B,
	DwarfAttrBitOffset = 0x0C,
	DwarfAttrBitSize = 0x0D,
	DwarfAttrStmtList = 0x10,
	DwarfAttrLowpc = 0x11,
	DwarfAttrHighpc = 0x12,
	DwarfAttrLanguage = 0x13,
	DwarfAttrDiscr = 0x15,
	DwarfAttrDiscrValue = 0x16,
	DwarfAttrVisibility = 0x17,
	DwarfAttrImport = 0x18,
	DwarfAttrStringLength = 0x19,
	DwarfAttrCommonRef = 0x1A,
	DwarfAttrCompDir = 0x1B,
	DwarfAttrConstValue = 0x1C,
	DwarfAttrContainingType = 0x1D,
	DwarfAttrDefaultValue = 0x1E,
	DwarfAttrInline = 0x20,
	DwarfAttrIsOptional = 0x21,
	DwarfAttrLowerBound = 0x22,
	DwarfAttrProducer = 0x25,
	DwarfAttrPrototyped = 0x27,
	DwarfAttrReturnAddr = 0x2A,
	DwarfAttrStartScope = 0x2C,
	DwarfAttrStrideSize = 0x2E,
	DwarfAttrUpperBound = 0x2F,
	DwarfAttrAbstractOrigin = 0x31,
	DwarfAttrAccessibility = 0x32,
	DwarfAttrAddrClass = 0x33,
	DwarfAttrArtificial = 0x34,
	DwarfAttrBaseTypes = 0x35,
	DwarfAttrCalling = 0x36,
	DwarfAttrCount = 0x37,
	DwarfAttrDataMemberLoc = 0x38,
	DwarfAttrDeclColumn = 0x39,
	DwarfAttrDeclFile = 0x3A,
	DwarfAttrDeclLine = 0x3B,
	DwarfAttrDeclaration = 0x3C,
	DwarfAttrDiscrList = 0x3D,
	DwarfAttrEncoding = 0x3E,
	DwarfAttrExternal = 0x3F,
	DwarfAttrFrameBase = 0x40,
	DwarfAttrFriend = 0x41,
	DwarfAttrIdentifierCase = 0x42,
	DwarfAttrMacroInfo = 0x43,
	DwarfAttrNamelistItem = 0x44,
	DwarfAttrPriority = 0x45,
	DwarfAttrSegment = 0x46,
	DwarfAttrSpecification = 0x47,
	DwarfAttrStaticLink = 0x48,
	DwarfAttrType = 0x49,
	DwarfAttrUseLocation = 0x4A,
	DwarfAttrVarParam = 0x4B,
	DwarfAttrVirtuality = 0x4C,
	DwarfAttrVtableElemLoc = 0x4D,
	DwarfAttrAllocated = 0x4E,
	DwarfAttrAssociated = 0x4F,
	DwarfAttrDataLocation = 0x50,
	DwarfAttrStride = 0x51,
	DwarfAttrEntrypc = 0x52,
	DwarfAttrUseUTF8 = 0x53,
	DwarfAttrExtension = 0x54,
	DwarfAttrRanges = 0x55,
	DwarfAttrTrampoline = 0x56,
	DwarfAttrCallColumn = 0x57,
	DwarfAttrCallFile = 0x58,
	DwarfAttrCallLine = 0x59,
	DwarfAttrDescription = 0x5A,
	DwarfAttrMax,

	FormAddr = 0x01,
	FormDwarfBlock2 = 0x03,
	FormDwarfBlock4 = 0x04,
	FormData2 = 0x05,
	FormData4 = 0x06,
	FormData8 = 0x07,
	FormString = 0x08,
	FormDwarfBlock = 0x09,
	FormDwarfBlock1 = 0x0A,
	FormData1 = 0x0B,
	FormFlag = 0x0C,
	FormSdata = 0x0D,
	FormStrp = 0x0E,
	FormUdata = 0x0F,
	FormRefAddr = 0x10,
	FormRef1 = 0x11,
	FormRef2 = 0x12,
	FormRef4 = 0x13,
	FormRef8 = 0x14,
	FormRefUdata = 0x15,
	FormIndirect = 0x16
};

static int parseattrs(DwarfBuf*, ulong, DwarfAbbrev*, DwarfAttrs*);
static int getulong(DwarfBuf*, int, ulong, ulong*, int*);
static int getuchar(DwarfBuf*, int, uchar*);
static int getstring(DwarfBuf*, int, char**);
static int getblock(DwarfBuf*, int, DwarfBlock*);
static int skipform(DwarfBuf*, int);
static int constblock(Dwarf*, DwarfBlock*, ulong*);

int
dwarflookupnameinunit(Dwarf *d, ulong unit, char *name, DwarfSym *s)
{
	if(dwarfenumunit(d, unit, s) < 0)
		return -1;

	dwarfnextsymat(d, s, 0);	/* s is now the CompileUnit */
	while(dwarfnextsymat(d, s, 1) == 1)
		if(s->attrs.name && strcmp(s->attrs.name, name) == 0)
			return 0;
	werrstr("symbol '%s' not found", name);
	return -1;
}


int
dwarflookupsubname(Dwarf *d, DwarfSym *parent, char *name, DwarfSym *s)
{
	*s = *parent;
	while(dwarfnextsymat(d, s, parent->depth+1))
		if(s->attrs.name && strcmp(s->attrs.name, name) == 0)
			return 0;
	werrstr("symbol '%s' not found", name);
	return -1;
}

int
dwarflookuptag(Dwarf *d, ulong unit, ulong tag, DwarfSym *s)
{
	if(dwarfenumunit(d, unit, s) < 0)
		return -1;

	dwarfnextsymat(d, s, 0);	/* s is now the CompileUnit */
	if(s->attrs.tag == tag)
		return 0;
	while(dwarfnextsymat(d, s, 1) == 1)
		if(s->attrs.tag == tag)
			return 0;
	werrstr("symbol with tag 0x%lux not found", tag);
	return -1;
}

int
dwarfseeksym(Dwarf *d, ulong unit, ulong off, DwarfSym *s)
{
	if(dwarfenumunit(d, unit, s) < 0)
		return -1;
	s->b.p = d->info.data + unit + off;
	if(dwarfnextsymat(d, s, 0) != 1)
		return -1;
	return 0;
}

int
dwarflookupfn(Dwarf *d, ulong unit, ulong pc, DwarfSym *s)
{
	if(dwarfenumunit(d, unit, s) < 0)
		return -1;

	if(dwarfnextsymat(d, s, 0) != 1)
		return -1;
	/* s is now the CompileUnit */

	while(dwarfnextsymat(d, s, 1) == 1){
		if(s->attrs.tag != TagSubprogram)
			continue;
		if(s->attrs.lowpc <= pc && pc < s->attrs.highpc)
			return 0;
	}
	werrstr("fn containing pc 0x%lux not found", pc);
	return -1;
}

int
dwarfenumunit(Dwarf *d, ulong unit, DwarfSym *s)
{
	int i;
	ulong aoff, len;

	if(unit >= d->info.len){
		werrstr("dwarf unit address 0x%lux >= 0x%lux out of range", unit, d->info.len);
		return -1;
	}
	memset(s, 0, sizeof *s);
	memset(&s->b, 0, sizeof s->b);
	s->b.d = d;
	s->b.p = d->info.data + unit;
	s->b.ep = d->info.data + d->info.len;
	len = dwarfget4(&s->b);
	s->nextunit = unit + 4 + len;

	if(s->b.ep - s->b.p < len){
	badheader:
		werrstr("bad dwarf unit header at unit 0x%lux", unit);
		return -1;
	}
	s->b.ep = s->b.p+len;
	if((i=dwarfget2(&s->b)) != 2)
		goto badheader;
	aoff = dwarfget4(&s->b);
	s->b.addrsize = dwarfget1(&s->b);
	if(d->addrsize == 0)
		d->addrsize = s->b.addrsize;
	if(s->b.p == nil)
		goto badheader;

	s->aoff = aoff;
	s->unit = unit;
	s->depth = 0;
	return 0;
}

int
dwarfenum(Dwarf *d, DwarfSym *s)
{
	if(dwarfenumunit(d, 0, s) < 0)
		return -1;
	s->allunits = 1;
	return 0;
}

int
dwarfnextsym(Dwarf *d, DwarfSym *s)
{
	ulong num;
	DwarfAbbrev *a;

	if(s->attrs.haskids)
		s->depth++;
top:
	if(s->b.p >= s->b.ep){
		if(s->allunits && s->nextunit < d->info.len){
			if(dwarfenumunit(d, s->nextunit, s) < 0)
				return -1;
			s->allunits = 1;
			goto top;
		}
		return 0;
	}

	s->uoff = s->b.p - (d->info.data+s->unit);
	num = dwarfget128(&s->b);
	if(num == 0){
		if(s->depth == 0)
			return 0;
		if(s->depth > 0)
			s->depth--;
		goto top;
	}

	a = dwarfgetabbrev(d, s->aoff, num);
	if(a == nil){
		fprint(2, "getabbrev %ud %ud for %ud,%ud: %r\n", s->aoff, num, s->unit, s->uoff);
		abort();
		return -1;
	}
	if(parseattrs(&s->b, s->unit, a, &s->attrs) < 0)
		return -1;
	return 1;
}

int
dwarfnextsymat(Dwarf *d, DwarfSym *s, int depth)
{
	int r;
	DwarfSym t;
	uint sib;

	if(s->depth == depth && s->attrs.have.sibling){
		sib = s->attrs.sibling;
		if(sib < d->info.len && d->info.data+sib >= s->b.p)
			s->b.p = d->info.data+sib;
		s->attrs.haskids = 0;
	}

	/*
	 * The funny game with t and s make sure that
	 * if we get to the end of a run of a particular
	 * depth, we leave s so that a call to nextsymat with depth-1
	 * will actually produce the desired guy.  We could change
	 * the interface to dwarfnextsym instead, but I'm scared
	 * to touch it.
	 */
	t = *s;
	for(;;){
		if((r = dwarfnextsym(d, &t)) != 1)
			return r;
		if(t.depth < depth){
			/* went too far - nothing to see */
			return 0;
		}
		*s = t;
		if(t.depth == depth)
			return 1;
	}
}

typedef struct Parse Parse;
struct Parse {
	int name;
	int off;
	int haveoff;
	int type;
};

#define OFFSET(x) offsetof(DwarfAttrs, x), offsetof(DwarfAttrs, have.x)

static Parse plist[] = {	/* Font Tab 4 */
	DwarfAttrAbstractOrigin,	OFFSET(abstractorigin),		TReference,
	DwarfAttrAccessibility,	OFFSET(accessibility),		TConstant,
	DwarfAttrAddrClass, 		OFFSET(addrclass), 			TConstant,
	DwarfAttrArtificial,		OFFSET(isartificial), 		TFlag,
	DwarfAttrBaseTypes,		OFFSET(basetypes),			TReference,
	DwarfAttrBitOffset,		OFFSET(bitoffset),			TConstant,
	DwarfAttrBitSize,		OFFSET(bitsize),			TConstant,
	DwarfAttrByteSize,		OFFSET(bytesize),			TConstant,
	DwarfAttrCalling,		OFFSET(calling),			TConstant,
	DwarfAttrCommonRef,		OFFSET(commonref),			TReference,
	DwarfAttrCompDir,		OFFSET(compdir),			TString,
	DwarfAttrConstValue,		OFFSET(constvalue),			TString|TConstant|TBlock,
	DwarfAttrContainingType,	OFFSET(containingtype),		TReference,
	DwarfAttrCount,			OFFSET(count),				TConstant|TReference,
	DwarfAttrDataMemberLoc,	OFFSET(datamemberloc),		TBlock|TConstant|TReference,
	DwarfAttrDeclColumn,		OFFSET(declcolumn),			TConstant,
	DwarfAttrDeclFile,		OFFSET(declfile),			TConstant,
	DwarfAttrDeclLine,		OFFSET(declline),			TConstant,
	DwarfAttrDeclaration,	OFFSET(isdeclaration),		TFlag,
	DwarfAttrDefaultValue,	OFFSET(defaultvalue),		TReference,
	DwarfAttrDiscr,			OFFSET(discr),				TReference,
	DwarfAttrDiscrList,		OFFSET(discrlist),			TBlock,
	DwarfAttrDiscrValue,		OFFSET(discrvalue),			TConstant,
	DwarfAttrEncoding,		OFFSET(encoding),			TConstant,
	DwarfAttrExternal,		OFFSET(isexternal),			TFlag,
	DwarfAttrFrameBase,		OFFSET(framebase),			TBlock|TConstant,
	DwarfAttrFriend,			OFFSET(friend),				TReference,
	DwarfAttrHighpc,			OFFSET(highpc),				TAddress,
	DwarfAttrIdentifierCase,	OFFSET(identifiercase),		TConstant,
	DwarfAttrImport,			OFFSET(import),				TReference,
	DwarfAttrInline,			OFFSET(inlined),			TConstant,
	DwarfAttrIsOptional,		OFFSET(isoptional),			TFlag,
	DwarfAttrLanguage,		OFFSET(language),			TConstant,
	DwarfAttrLocation,		OFFSET(location),			TBlock|TConstant,
	DwarfAttrLowerBound,		OFFSET(lowerbound),			TConstant|TReference,
	DwarfAttrLowpc,			OFFSET(lowpc),				TAddress,
	DwarfAttrMacroInfo,		OFFSET(macroinfo),			TConstant,
	DwarfAttrName,			OFFSET(name),				TString,
	DwarfAttrNamelistItem,	OFFSET(namelistitem),		TBlock,
	DwarfAttrOrdering, 		OFFSET(ordering),			TConstant,
	DwarfAttrPriority,		OFFSET(priority),			TReference,
	DwarfAttrProducer,		OFFSET(producer),			TString,
	DwarfAttrPrototyped,		OFFSET(isprototyped),		TFlag,
	DwarfAttrRanges,			OFFSET(ranges),				TReference,
	DwarfAttrReturnAddr,		OFFSET(returnaddr),			TBlock|TConstant,
	DwarfAttrSegment,		OFFSET(segment),			TBlock|TConstant,
	DwarfAttrSibling,		OFFSET(sibling),			TReference,
	DwarfAttrSpecification,	OFFSET(specification),		TReference,
	DwarfAttrStartScope,		OFFSET(startscope),			TConstant,
	DwarfAttrStaticLink,		OFFSET(staticlink),			TBlock|TConstant,
	DwarfAttrStmtList,		OFFSET(stmtlist),			TConstant,
	DwarfAttrStrideSize,		OFFSET(stridesize),			TConstant,
	DwarfAttrStringLength,	OFFSET(stringlength),		TBlock|TConstant,
	DwarfAttrType,			OFFSET(type),				TReference,
	DwarfAttrUpperBound,		OFFSET(upperbound),			TConstant|TReference,
	DwarfAttrUseLocation,	OFFSET(uselocation),		TBlock|TConstant,
	DwarfAttrVarParam,		OFFSET(isvarparam),			TFlag,
	DwarfAttrVirtuality,		OFFSET(virtuality),			TConstant,
	DwarfAttrVisibility,		OFFSET(visibility),			TConstant,
	DwarfAttrVtableElemLoc,	OFFSET(vtableelemloc),		TBlock|TReference,
};

static Parse ptab[DwarfAttrMax];

static int
parseattrs(DwarfBuf *b, ulong unit, DwarfAbbrev *a, DwarfAttrs *attrs)
{
	int i, f, n, got;
	static int nbad;
	void *v;

	/* initialize ptab first time through for quick access */
	if(ptab[DwarfAttrName].name != DwarfAttrName)
		for(i=0; i<nelem(plist); i++)
			ptab[plist[i].name] = plist[i];

	memset(attrs, 0, sizeof *attrs);
	attrs->tag = a->tag;
	attrs->haskids = a->haskids;

	for(i=0; i<a->nattr; i++){
		n = a->attr[i].name;
		f = a->attr[i].form;
		if(n < 0 || n >= nelem(ptab) || ptab[n].name==0){
			if(++nbad == 1)
				fprint(2, "dwarf parse attrs: unexpected attribute name 0x%ux\n", n);
			return -1;
		}
		v = (char*)attrs + ptab[n].off;
		got = 0;
		if(f == FormIndirect)
			f = dwarfget128(b);
		if((ptab[n].type&(TConstant|TReference|TAddress))
		&& getulong(b, f, unit, v, &got) >= 0)
			;
		else if((ptab[n].type&TFlag) && getuchar(b, f, v) >= 0)
			got = TFlag;
		else if((ptab[n].type&TString) && getstring(b, f, v) >= 0)
			got = TString;
		else if((ptab[n].type&TBlock) && getblock(b, f, v) >= 0)
			got = TBlock;
		else{
			if(skipform(b, f) < 0){
				if(++nbad == 1)
					fprint(2, "dwarf parse attrs: cannot skip form %d\n", f);
				return -1;
			}
		}
		if(got == TBlock && (ptab[n].type&TConstant))
			got = constblock(b->d, v, v);
		*((uchar*)attrs+ptab[n].haveoff) = got;
	}
	return 0;
}

static int
getulong(DwarfBuf *b, int form, ulong unit, ulong *u, int *type)
{
	static int nbad;
	uvlong uv;

	switch(form){
	default:
		return -1;

	/* addresses */
	case FormAddr:
		*type = TAddress;
		*u = dwarfgetaddr(b);
		return 0;

	/* references */
	case FormRefAddr:
		/* absolute ref in .debug_info */
		*type = TReference;
		*u = dwarfgetaddr(b);
		return 0;
	case FormRef1:
		*u = dwarfget1(b);
		goto relativeref;
	case FormRef2:
		*u = dwarfget2(b);
		goto relativeref;
	case FormRef4:
		*u = dwarfget4(b);
		goto relativeref;
	case FormRef8:
		*u = dwarfget8(b);
		goto relativeref;
	case FormRefUdata:
		*u = dwarfget128(b);
	relativeref:
		*u += unit;
		*type = TReference;
		return 0;

	/* constants */
	case FormData1:
		*u = dwarfget1(b);
		goto constant;
	case FormData2:
		*u = dwarfget2(b);
		goto constant;
	case FormData4:
		*u = dwarfget4(b);
		goto constant;
	case FormData8:
		uv = dwarfget8(b);
		*u = uv;
		if(uv != *u && ++nbad == 1)
			fprint(2, "dwarf: truncating 64-bit attribute constants\n");
		goto constant;
	case FormSdata:
		*u = dwarfget128s(b);
		goto constant;
	case FormUdata:
		*u = dwarfget128(b);
	constant:
		*type = TConstant;
		return 0;
	}
}

static int
getuchar(DwarfBuf *b, int form, uchar *u)
{
	switch(form){
	default:
		return -1;

	case FormFlag:
		*u = dwarfget1(b);
		return 0;
	}
}

static int
getstring(DwarfBuf *b, int form, char **s)
{
	static int nbad;
	ulong u;

	switch(form){
	default:
		return -1;

	case FormString:
		*s = dwarfgetstring(b);
		return 0;

	case FormStrp:
		u = dwarfget4(b);
		if(u >= b->d->str.len){
			if(++nbad == 1)
				fprint(2, "dwarf: bad string pointer 0x%lux in attribute\n", u);
			/* don't return error - maybe can proceed */
			*s = nil;
		}else
			*s = (char*)b->d->str.data + u;
		return 0;

	}
}

static int
getblock(DwarfBuf *b, int form, DwarfBlock *bl)
{
	ulong n;

	switch(form){
	default:
		return -1;
	case FormDwarfBlock:
		n = dwarfget128(b);
		goto copyn;
	case FormDwarfBlock1:
		n = dwarfget1(b);
		goto copyn;
	case FormDwarfBlock2:
		n = dwarfget2(b);
		goto copyn;
	case FormDwarfBlock4:
		n = dwarfget4(b);
	copyn:
		bl->data = dwarfgetnref(b, n);
		bl->len = n;
		if(bl->data == nil)
			return -1;
		return 0;
	}
}

static int
constblock(Dwarf *d, DwarfBlock *bl, ulong *pval)
{
	DwarfBuf b;

	memset(&b, 0, sizeof b);
	b.p = bl->data;
	b.ep = bl->data+bl->len;
	b.d = d;

	switch(dwarfget1(&b)){
	case OpAddr:
		*pval = dwarfgetaddr(&b);
		return TConstant;
	case OpConst1u:
		*pval = dwarfget1(&b);
		return TConstant;
	case OpConst1s:
		*pval = (schar)dwarfget1(&b);
		return TConstant;
	case OpConst2u:
		*pval = dwarfget2(&b);
		return TConstant;
	case OpConst2s:
		*pval = (s16int)dwarfget2(&b);
		return TConstant;
	case OpConst4u:
		*pval = dwarfget4(&b);
		return TConstant;
	case OpConst4s:
		*pval = (s32int)dwarfget4(&b);
		return TConstant;
	case OpConst8u:
		*pval = (u64int)dwarfget8(&b);
		return TConstant;
	case OpConst8s:
		*pval = (s64int)dwarfget8(&b);
		return TConstant;
	case OpConstu:
		*pval = dwarfget128(&b);
		return TConstant;
	case OpConsts:
		*pval = dwarfget128s(&b);
		return TConstant;
	case OpPlusUconst:
		*pval = dwarfget128(&b);
		return TConstant;
	default:
		return TBlock;
	}
}

/* last resort */
static int
skipform(DwarfBuf *b, int form)
{
	int type;
	DwarfVal val;

	if(getulong(b, form, 0, &val.c, &type) < 0
	&& getuchar(b, form, (uchar*)&val) < 0
	&& getstring(b, form, &val.s) < 0
	&& getblock(b, form, &val.b) < 0)
		return -1;
	return 0;
}
