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

static int mapelf(Fhdr *fp, u64int base, Map *map, Regs**);
static int unpacknote(Elf *elf, uchar *a, uchar *ea, ElfNote *note, uchar **pa);

static struct
{
	uint etype;
	uint mtype;
	Mach *mach;
	char *name;
} mtab[] = 
{	/* Font Tab 4 */
	ElfMachSparc,	MSPARC,		nil,		"sparc",
	ElfMach386,		M386,		&mach386,	"386",
	ElfMachMips,	MMIPS,		nil,		"mips",
	ElfMachArm,		MARM,		nil,		"arm",
	ElfMachPower,	MPOWER,		nil,		"powerpc",
	ElfMachPower64,	MNONE,		nil,		"powerpc64",
	ElfMachAmd64,	MAMD64,	&machamd64,	"amd64",
};

static struct
{
	uint etype;
	uint atype;
	char *aname;
} atab[] = 
{	/* Font Tab 4 */
	ElfAbiSystemV,	ALINUX,		"linux",	/* [sic] */
	ElfAbiLinux,	ALINUX,		"linux",
	ElfAbiFreeBSD,	AFREEBSD,	"freebsd",
};

static struct
{
	uint mtype;
	uint atype;
	void (*elfcore)(Fhdr*, Elf*, ElfNote*);
} ctab[] = 
{	/* Font Tab 4 */
	M386,		ALINUX,		elfcorelinux386,
	M386,		ANONE,		elfcorelinux386,	/* [sic] */
//	M386,		AFREEBSD,	elfcorefreebsd386,
	MAMD64,		AFREEBSD,	elfcorefreebsdamd64,
};

int
crackelf(int fd, Fhdr *fp)
{
	uchar *a, *sa, *ea;
	int i, havetext, havedata, n;
	Elf *elf;
	ElfNote note;
	ElfProg *p;
	ElfSect *s1, *s2;
	void (*elfcore)(Fhdr*, Elf*, ElfNote*);

	if((elf = elfinit(fd)) == nil)
		return -1;

	fp->fd = fd;
	fp->elf = elf;
	fp->dwarf = dwarfopen(elf);	/* okay to fail */
	fp->syminit = symelf;

	if((s1 = elfsection(elf, ".stab")) != nil && s1->link!=0 && s1->link < elf->nsect){
		s2 = &elf->sect[s1->link];
		if(elfmap(elf, s1) >= 0 && elfmap(elf, s2) >= 0){
			fp->stabs.stabbase = s1->base;
			fp->stabs.stabsize = s1->size;
			fp->stabs.strbase = (char*)s2->base;
			fp->stabs.strsize = s2->size;
			fp->stabs.e2 = elf->hdr.e2;
			fp->stabs.e4 = elf->hdr.e4;
		}
	}

	for(i=0; i<nelem(mtab); i++){
		if(elf->hdr.machine != mtab[i].etype)
			continue;
		fp->mach = mtab[i].mach;
		fp->mname = mtab[i].name;
		fp->mtype = mtab[i].mtype;
		break;
	}
	if(i == nelem(mtab)){
		werrstr("unsupported machine type %d", elf->hdr.machine);
		goto err;
	}

	if(mach == nil)
		mach = fp->mach;

	fp->aname = "unknown";
	for(i=0; i<nelem(atab); i++){
		if(elf->hdr.abi != atab[i].etype)
			continue;
		fp->atype = atab[i].atype;
		fp->aname = atab[i].aname;
		break;
	}

	switch(elf->hdr.type){
	default:
		werrstr("unknown file type %d", elf->hdr.type);
		goto err;
	case ElfTypeExecutable:
		fp->ftype = FEXEC;
		fp->fname = "executable";
		break;
	case ElfTypeRelocatable:
		fp->ftype = FRELOC;
		fp->fname = "relocatable";
		break;
	case ElfTypeSharedObject:
		fp->ftype = FSHOBJ;
		fp->fname = "shared object";
		break;
	case ElfTypeCore:
		fp->ftype = FCORE;
		fp->fname = "core dump";
		break;
	}

	fp->map = mapelf;

	if(fp->ftype == FCORE){
		elfcore = nil;
		for(i=0; i<nelem(ctab); i++){
			if(ctab[i].atype != fp->atype
			|| ctab[i].mtype != fp->mtype)
				continue;
			elfcore = ctab[i].elfcore;
			break;
		}
		if(elfcore)
		for(i=0; i<elf->nprog; i++){
			p = &elf->prog[i];
			if(p->type != ElfProgNote)
				continue;
			n = p->filesz;
			a = malloc(n);
			if(a == nil)
				goto err;
			if(seek(fp->fd, p->offset, 0) < 0 || readn(fp->fd, a, n) != n){
				free(a);
				continue;
			}
			sa = a;
			ea = a+n;
			while(a < ea){
				note.offset = (a-sa) + p->offset;
				if(unpacknote(elf, a, ea, &note, &a) < 0)
					break;
				elfcore(fp, elf, &note);
			}
			free(sa);
		}
		return 0;
	}

	fp->entry = elf->hdr.entry;

	/* First r-x section we find is the text and initialized data */
	/* First rw- section we find is the r/w data */
	havetext = 0;
	havedata = 0;
	for(i=0; i<elf->nprog; i++){
		p = &elf->prog[i];
		if(p->type != ElfProgLoad)
			continue;
		if(!havetext && p->flags == (ElfProgFlagRead|ElfProgFlagExec) && p->align >= mach->pgsize){
			havetext = 1;
			fp->txtaddr = p->vaddr;
			fp->txtsz = p->memsz;
			fp->txtoff = p->offset;
		}
		if(!havedata && p->flags == (ElfProgFlagRead|ElfProgFlagWrite) && p->align >= mach->pgsize){
			havedata = 1;
			fp->dataddr = p->vaddr;
			fp->datsz = p->filesz;
			fp->datoff = p->offset;
			fp->bsssz = p->memsz - p->filesz;
		}
	}
	if(!havetext){
		werrstr("did not find text segment in elf binary");
		goto err;
	}
	if(!havedata){
		werrstr("did not find data segment in elf binary");
		goto err;
	}
	return 0;

err:
	elfclose(elf);
	return -1;
}

static int
mapelf(Fhdr *fp, u64int base, Map *map, Regs **regs)
{
	int i;
	Elf *elf;
	ElfProg *p;
	u64int sz;
	u64int lim;
	Seg s;

	elf = fp->elf;
	if(elf == nil){
		werrstr("not an elf file");
		return -1;
	}

	for(i=0; i<elf->nprog; i++){
		p = &elf->prog[i];
		if(p->type != ElfProgLoad)
			continue;
		if(p->align < mach->pgsize)
			continue;
		if(p->filesz){
			memset(&s, 0, sizeof s);
			s.file = fp->filename;
			s.fd = fp->fd;
			if(fp->ftype == FCORE)
				s.name = "core";
			else if(p->flags == 5)
				s.name = "text";
			else
				s.name = "data";
			s.base = base+p->vaddr;
			s.size = p->filesz;
			s.offset = p->offset;
			if(addseg(map, s) < 0)
				return -1;
		}
		/*
		 * If memsz > filesz, we're supposed to zero fill.
		 * Core files have zeroed sections where the pages
		 * can be filled in from the text file, so if this is a core
		 * we only fill in that which isn't yet mapped.
		 */
		if(fp->ftype == FCORE){
			sz = p->filesz;
			while(sz < p->memsz){
				if(addrtoseg(map, base+p->vaddr+sz, &s) < 0){
					lim = base + p->vaddr + p->memsz;
					if(addrtosegafter(map, base+p->vaddr+sz, &s) >= 0 && s.base < lim)
						lim = s.base;
					memset(&s, 0, sizeof s);
					s.name = "zero";
					s.base = base + p->vaddr + sz;
					s.size = lim - s.base;
					s.offset = p->offset;
					if(addseg(map, s) < 0)
						return -1;
				}else
					sz = (s.base+s.size) - (base + p->vaddr);
			}
		}else{
			if(p->filesz < p->memsz){
				memset(&s, 0, sizeof s);
				s.name = "zero";
				s.base = base + p->vaddr + p->filesz;
				s.size = p->memsz - p->filesz;
				if(addseg(map, s) < 0)
					return -1;
			}
		}			
	}

	if(fp->nthread && regs)
		*regs = coreregs(fp, fp->thread[0].id);

	return 0;	
}

static int
unpacknote(Elf *elf, uchar *a, uchar *ea, ElfNote *note, uchar **pa)
{
	if(a+12 > ea)
		return -1;
	note->namesz = elf->hdr.e4(a);
	note->descsz = elf->hdr.e4(a+4);
	note->type = elf->hdr.e4(a+8);
	a += 12;
	note->name = (char*)a;
/* XXX fetch alignment constants from elsewhere */
	a += (note->namesz+3)&~3;
	note->desc = (uchar*)a;
	a += (note->descsz+3)&~3;
	if(a > ea)
		return -1;
	*pa = a;
	return 0;
}