// 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.

// Mach-O file writing
// http://developer.apple.com/mac/library/DOCUMENTATION/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html

#include "l.h"
#include "../ld/dwarf.h"
#include "../ld/lib.h"
#include "../ld/macho.h"

static	int	macho64;
static	MachoHdr	hdr;
static	MachoLoad	*load;
static	MachoSeg	seg[16];
static	int	nload, mload, nseg, ndebug, nsect;

enum
{
	SymKindLocal = 0,
	SymKindExtdef,
	SymKindUndef,
	NumSymKind
};

static	int nkind[NumSymKind];
static	LSym** sortsym;
static	int	nsortsym;

// Amount of space left for adding load commands
// that refer to dynamic libraries.  Because these have
// to go in the Mach-O header, we can't just pick a
// "big enough" header size.  The initial header is 
// one page, the non-dynamic library stuff takes
// up about 1300 bytes; we overestimate that as 2k.
static	int	load_budget = INITIAL_MACHO_HEADR - 2*1024;

static	void	machodysymtab(void);

void
machoinit(void)
{
	switch(thechar) {
	// 64-bit architectures
	case '6':
		macho64 = 1;
		break;

	// 32-bit architectures
	default:
		break;
	}
}

MachoHdr*
getMachoHdr(void)
{
	return &hdr;
}

MachoLoad*
newMachoLoad(uint32 type, uint32 ndata)
{
	MachoLoad *l;

	if(nload >= mload) {
		if(mload == 0)
			mload = 1;
		else
			mload *= 2;
		load = erealloc(load, mload*sizeof load[0]);
	}

	if(macho64 && (ndata & 1))
		ndata++;
	
	l = &load[nload++];
	l->type = type;
	l->ndata = ndata;
	l->data = mal(ndata*4);
	return l;
}

MachoSeg*
newMachoSeg(char *name, int msect)
{
	MachoSeg *s;

	if(nseg >= nelem(seg)) {
		diag("too many segs");
		errorexit();
	}
	s = &seg[nseg++];
	s->name = name;
	s->msect = msect;
	s->sect = mal(msect*sizeof s->sect[0]);
	return s;
}

MachoSect*
newMachoSect(MachoSeg *seg, char *name, char *segname)
{
	MachoSect *s;

	if(seg->nsect >= seg->msect) {
		diag("too many sects in segment %s", seg->name);
		errorexit();
	}
	s = &seg->sect[seg->nsect++];
	s->name = name;
	s->segname = segname;
	nsect++;
	return s;
}

// Generic linking code.

static char **dylib;
static int ndylib;

static vlong linkoff;

int
machowrite(void)
{
	vlong o1;
	int loadsize;
	int i, j;
	MachoSeg *s;
	MachoSect *t;
	MachoLoad *l;

	o1 = cpos();

	loadsize = 4*4*ndebug;
	for(i=0; i<nload; i++)
		loadsize += 4*(load[i].ndata+2);
	if(macho64) {
		loadsize += 18*4*nseg;
		loadsize += 20*4*nsect;
	} else {
		loadsize += 14*4*nseg;
		loadsize += 17*4*nsect;
	}

	if(macho64)
		LPUT(0xfeedfacf);
	else
		LPUT(0xfeedface);
	LPUT(hdr.cpu);
	LPUT(hdr.subcpu);
	if(linkmode == LinkExternal)
		LPUT(1);	/* file type - mach object */
	else
		LPUT(2);	/* file type - mach executable */
	LPUT(nload+nseg+ndebug);
	LPUT(loadsize);
	LPUT(1);	/* flags - no undefines */
	if(macho64)
		LPUT(0);	/* reserved */

	for(i=0; i<nseg; i++) {
		s = &seg[i];
		if(macho64) {
			LPUT(25);	/* segment 64 */
			LPUT(72+80*s->nsect);
			strnput(s->name, 16);
			VPUT(s->vaddr);
			VPUT(s->vsize);
			VPUT(s->fileoffset);
			VPUT(s->filesize);
			LPUT(s->prot1);
			LPUT(s->prot2);
			LPUT(s->nsect);
			LPUT(s->flag);
		} else {
			LPUT(1);	/* segment 32 */
			LPUT(56+68*s->nsect);
			strnput(s->name, 16);
			LPUT(s->vaddr);
			LPUT(s->vsize);
			LPUT(s->fileoffset);
			LPUT(s->filesize);
			LPUT(s->prot1);
			LPUT(s->prot2);
			LPUT(s->nsect);
			LPUT(s->flag);
		}
		for(j=0; j<s->nsect; j++) {
			t = &s->sect[j];
			if(macho64) {
				strnput(t->name, 16);
				strnput(t->segname, 16);
				VPUT(t->addr);
				VPUT(t->size);
				LPUT(t->off);
				LPUT(t->align);
				LPUT(t->reloc);
				LPUT(t->nreloc);
				LPUT(t->flag);
				LPUT(t->res1);	/* reserved */
				LPUT(t->res2);	/* reserved */
				LPUT(0);	/* reserved */
			} else {
				strnput(t->name, 16);
				strnput(t->segname, 16);
				LPUT(t->addr);
				LPUT(t->size);
				LPUT(t->off);
				LPUT(t->align);
				LPUT(t->reloc);
				LPUT(t->nreloc);
				LPUT(t->flag);
				LPUT(t->res1);	/* reserved */
				LPUT(t->res2);	/* reserved */
			}
		}
	}

	for(i=0; i<nload; i++) {
		l = &load[i];
		LPUT(l->type);
		LPUT(4*(l->ndata+2));
		for(j=0; j<l->ndata; j++)
			LPUT(l->data[j]);
	}

	return cpos() - o1;
}

void
domacho(void)
{
	LSym *s;

	if(debug['d'])
		return;

	// empirically, string table must begin with " \x00".
	s = linklookup(ctxt, ".machosymstr", 0);
	s->type = SMACHOSYMSTR;
	s->reachable = 1;
	adduint8(ctxt, s, ' ');
	adduint8(ctxt, s, '\0');
	
	s = linklookup(ctxt, ".machosymtab", 0);
	s->type = SMACHOSYMTAB;
	s->reachable = 1;
	
	if(linkmode != LinkExternal) {
		s = linklookup(ctxt, ".plt", 0);	// will be __symbol_stub
		s->type = SMACHOPLT;
		s->reachable = 1;
	
		s = linklookup(ctxt, ".got", 0);	// will be __nl_symbol_ptr
		s->type = SMACHOGOT;
		s->reachable = 1;
		s->align = 4;
	
		s = linklookup(ctxt, ".linkedit.plt", 0);	// indirect table for .plt
		s->type = SMACHOINDIRECTPLT;
		s->reachable = 1;
	
		s = linklookup(ctxt, ".linkedit.got", 0);	// indirect table for .got
		s->type = SMACHOINDIRECTGOT;
		s->reachable = 1;
	}
}

void
machoadddynlib(char *lib)
{
	// Will need to store the library name rounded up
	// and 24 bytes of header metadata.  If not enough
	// space, grab another page of initial space at the
	// beginning of the output file.
	load_budget -= (strlen(lib)+7)/8*8 + 24;
	if(load_budget < 0) {
		HEADR += 4096;
		INITTEXT += 4096;
		load_budget += 4096;
	}

	if(ndylib%32 == 0)
		dylib = erealloc(dylib, (ndylib+32)*sizeof dylib[0]);
	dylib[ndylib++] = lib;
}

static void
machoshbits(MachoSeg *mseg, Section *sect, char *segname)
{
	MachoSect *msect;
	char buf[40];
	char *p;
	
	snprint(buf, sizeof buf, "__%s", sect->name+1);
	for(p=buf; *p; p++)
		if(*p == '.')
			*p = '_';

	msect = newMachoSect(mseg, estrdup(buf), segname);
	if(sect->rellen > 0) {
		msect->reloc = sect->reloff;
		msect->nreloc = sect->rellen / 8;
	}

	while(1<<msect->align < sect->align)
		msect->align++;
	msect->addr = sect->vaddr;
	msect->size = sect->len;
	
	if(sect->vaddr < sect->seg->vaddr + sect->seg->filelen) {
		// data in file
		if(sect->len > sect->seg->vaddr + sect->seg->filelen - sect->vaddr)
			diag("macho cannot represent section %s crossing data and bss", sect->name);
		msect->off = sect->seg->fileoff + sect->vaddr - sect->seg->vaddr;
	} else {
		// zero fill
		msect->off = 0;
		msect->flag |= 1;
	}

	if(sect->rwx & 1)
		msect->flag |= 0x400; /* has instructions */
	
	if(strcmp(sect->name, ".plt") == 0) {
		msect->name = "__symbol_stub1";
		msect->flag = 0x80000408; /* only instructions, code, symbol stubs */
		msect->res1 = 0;//nkind[SymKindLocal];
		msect->res2 = 6;
	}

	if(strcmp(sect->name, ".got") == 0) {
		msect->name = "__nl_symbol_ptr";
		msect->flag = 6;	/* section with nonlazy symbol pointers */
		msect->res1 = linklookup(ctxt, ".linkedit.plt", 0)->size / 4;	/* offset into indirect symbol table */
	}
}

void
asmbmacho(void)
{
	vlong v, w;
	vlong va;
	int a, i;
	MachoHdr *mh;
	MachoSeg *ms;
	MachoLoad *ml;
	Section *sect;

	/* apple MACH */
	va = INITTEXT - HEADR;
	mh = getMachoHdr();
	switch(thechar){
	default:
		diag("unknown mach architecture");
		errorexit();
	case '6':
		mh->cpu = MACHO_CPU_AMD64;
		mh->subcpu = MACHO_SUBCPU_X86;
		break;
	case '8':
		mh->cpu = MACHO_CPU_386;
		mh->subcpu = MACHO_SUBCPU_X86;
		break;
	}
	
	ms = nil;
	if(linkmode == LinkExternal) {
		/* segment for entire file */
		ms = newMachoSeg("", 40);
		ms->fileoffset = segtext.fileoff;
		ms->filesize = segdata.fileoff + segdata.filelen - segtext.fileoff;
	}

	/* segment for zero page */
	if(linkmode != LinkExternal) {
		ms = newMachoSeg("__PAGEZERO", 0);
		ms->vsize = va;
	}

	/* text */
	v = rnd(HEADR+segtext.len, INITRND);
	if(linkmode != LinkExternal) {
		ms = newMachoSeg("__TEXT", 20);
		ms->vaddr = va;
		ms->vsize = v;
		ms->fileoffset = 0;
		ms->filesize = v;
		ms->prot1 = 7;
		ms->prot2 = 5;
	}

	for(sect=segtext.sect; sect!=nil; sect=sect->next)
		machoshbits(ms, sect, "__TEXT");

	/* data */
	if(linkmode != LinkExternal) {
		w = segdata.len;
		ms = newMachoSeg("__DATA", 20);
		ms->vaddr = va+v;
		ms->vsize = w;
		ms->fileoffset = v;
		ms->filesize = segdata.filelen;
		ms->prot1 = 3;
		ms->prot2 = 3;
	}

	for(sect=segdata.sect; sect!=nil; sect=sect->next)
		machoshbits(ms, sect, "__DATA");

	if(linkmode != LinkExternal) {
		switch(thechar) {
		default:
			diag("unknown macho architecture");
			errorexit();
		case '6':
			ml = newMachoLoad(5, 42+2);	/* unix thread */
			ml->data[0] = 4;	/* thread type */
			ml->data[1] = 42;	/* word count */
			ml->data[2+32] = entryvalue();	/* start pc */
			ml->data[2+32+1] = entryvalue()>>16>>16;	// hide >>32 for 8l
			break;
		case '8':
			ml = newMachoLoad(5, 16+2);	/* unix thread */
			ml->data[0] = 1;	/* thread type */
			ml->data[1] = 16;	/* word count */
			ml->data[2+10] = entryvalue();	/* start pc */
			break;
		}
	}
	
	if(!debug['d']) {
		LSym *s1, *s2, *s3, *s4;

		// must match domacholink below
		s1 = linklookup(ctxt, ".machosymtab", 0);
		s2 = linklookup(ctxt, ".linkedit.plt", 0);
		s3 = linklookup(ctxt, ".linkedit.got", 0);
		s4 = linklookup(ctxt, ".machosymstr", 0);

		if(linkmode != LinkExternal) {
			ms = newMachoSeg("__LINKEDIT", 0);
			ms->vaddr = va+v+rnd(segdata.len, INITRND);
			ms->vsize = s1->size + s2->size + s3->size + s4->size;
			ms->fileoffset = linkoff;
			ms->filesize = ms->vsize;
			ms->prot1 = 7;
			ms->prot2 = 3;
		}

		ml = newMachoLoad(2, 4);	/* LC_SYMTAB */
		ml->data[0] = linkoff;	/* symoff */
		ml->data[1] = nsortsym;	/* nsyms */
		ml->data[2] = linkoff + s1->size + s2->size + s3->size;	/* stroff */
		ml->data[3] = s4->size;	/* strsize */

		machodysymtab();

		if(linkmode != LinkExternal) {
			ml = newMachoLoad(14, 6);	/* LC_LOAD_DYLINKER */
			ml->data[0] = 12;	/* offset to string */
			strcpy((char*)&ml->data[1], "/usr/lib/dyld");
	
			for(i=0; i<ndylib; i++) {
				ml = newMachoLoad(12, 4+(strlen(dylib[i])+1+7)/8*2);	/* LC_LOAD_DYLIB */
				ml->data[0] = 24;	/* offset of string from beginning of load */
				ml->data[1] = 0;	/* time stamp */
				ml->data[2] = 0;	/* version */
				ml->data[3] = 0;	/* compatibility version */
				strcpy((char*)&ml->data[4], dylib[i]);
			}
		}
	}

	// TODO: dwarf headers go in ms too
	if(!debug['s'] && linkmode != LinkExternal)
		dwarfaddmachoheaders();

	a = machowrite();
	if(a > HEADR)
		diag("HEADR too small: %d > %d", a, HEADR);
}

static int
symkind(LSym *s)
{
	if(s->type == SDYNIMPORT)
		return SymKindUndef;
	if(s->cgoexport)
		return SymKindExtdef;
	return SymKindLocal;
}

static void
addsym(LSym *s, char *name, int type, vlong addr, vlong size, int ver, LSym *gotype)
{
	USED(name);
	USED(addr);
	USED(size);
	USED(ver);
	USED(gotype);

	if(s == nil)
		return;

	switch(type) {
	default:
		return;
	case 'D':
	case 'B':
	case 'T':
		break;
	}
	
	if(sortsym) {
		sortsym[nsortsym] = s;
		nkind[symkind(s)]++;
	}
	nsortsym++;
}
	
static int
scmp(const void *p1, const void *p2)
{
	LSym *s1, *s2;
	int k1, k2;

	s1 = *(LSym**)p1;
	s2 = *(LSym**)p2;
	
	k1 = symkind(s1);
	k2 = symkind(s2);
	if(k1 != k2)
		return k1 - k2;

	return strcmp(s1->extname, s2->extname);
}

static void
machogenasmsym(void (*put)(LSym*, char*, int, vlong, vlong, int, LSym*))
{
	LSym *s;

	genasmsym(put);
	for(s=ctxt->allsym; s; s=s->allsym)
		if(s->type == SDYNIMPORT || s->type == SHOSTOBJ)
		if(s->reachable)
			put(s, nil, 'D', 0, 0, 0, nil);
}
			
void
machosymorder(void)
{
	int i;

	// On Mac OS X Mountain Lion, we must sort exported symbols
	// So we sort them here and pre-allocate dynid for them
	// See http://golang.org/issue/4029
	for(i=0; i<ndynexp; i++)
		dynexp[i]->reachable = 1;
	machogenasmsym(addsym);
	sortsym = mal(nsortsym * sizeof sortsym[0]);
	nsortsym = 0;
	machogenasmsym(addsym);
	qsort(sortsym, nsortsym, sizeof sortsym[0], scmp);
	for(i=0; i<nsortsym; i++)
		sortsym[i]->dynid = i;
}

static void
machosymtab(void)
{
	int i;
	LSym *symtab, *symstr, *s, *o;
	char *p;

	symtab = linklookup(ctxt, ".machosymtab", 0);
	symstr = linklookup(ctxt, ".machosymstr", 0);

	for(i=0; i<nsortsym; i++) {
		s = sortsym[i];
		adduint32(ctxt, symtab, symstr->size);
		
		// Only add _ to C symbols. Go symbols have dot in the name.
		if(strstr(s->extname, ".") == nil)
			adduint8(ctxt, symstr, '_');
		// replace "·" as ".", because DTrace cannot handle it.
		if(strstr(s->extname, "·") == nil) {
			addstring(symstr, s->extname);
		} else {
			p = s->extname;
			while (*p++ != '\0') {
				if((uchar)*p == 0xc2 && (uchar)*(p+1) == 0xb7) {
					adduint8(ctxt, symstr, '.');
					p++;
				} else {
					adduint8(ctxt, symstr, *p);
				}
			}
			adduint8(ctxt, symstr, '\0');
		}
		if(s->type == SDYNIMPORT || s->type == SHOSTOBJ) {
			adduint8(ctxt, symtab, 0x01); // type N_EXT, external symbol
			adduint8(ctxt, symtab, 0); // no section
			adduint16(ctxt, symtab, 0); // desc
			adduintxx(ctxt, symtab, 0, PtrSize); // no value
		} else {
			if(s->cgoexport)
				adduint8(ctxt, symtab, 0x0f);
			else
				adduint8(ctxt, symtab, 0x0e);
			o = s;
			while(o->outer != nil)
				o = o->outer;
			if(o->sect == nil) {
				diag("missing section for %s", s->name);
				adduint8(ctxt, symtab, 0);
			} else
				adduint8(ctxt, symtab, o->sect->extnum);
			adduint16(ctxt, symtab, 0); // desc
			adduintxx(ctxt, symtab, symaddr(s), PtrSize);
		}
	}
}

static void
machodysymtab(void)
{
	int n;
	MachoLoad *ml;
	LSym *s1, *s2, *s3;

	ml = newMachoLoad(11, 18);	/* LC_DYSYMTAB */

	n = 0;
	ml->data[0] = n;	/* ilocalsym */
	ml->data[1] = nkind[SymKindLocal];	/* nlocalsym */
	n += nkind[SymKindLocal];

	ml->data[2] = n;	/* iextdefsym */
	ml->data[3] = nkind[SymKindExtdef];	/* nextdefsym */
	n += nkind[SymKindExtdef];

	ml->data[4] = n;	/* iundefsym */
	ml->data[5] = nkind[SymKindUndef];	/* nundefsym */

	ml->data[6] = 0;	/* tocoffset */
	ml->data[7] = 0;	/* ntoc */
	ml->data[8] = 0;	/* modtaboff */
	ml->data[9] = 0;	/* nmodtab */
	ml->data[10] = 0;	/* extrefsymoff */
	ml->data[11] = 0;	/* nextrefsyms */

	// must match domacholink below
	s1 = linklookup(ctxt, ".machosymtab", 0);
	s2 = linklookup(ctxt, ".linkedit.plt", 0);
	s3 = linklookup(ctxt, ".linkedit.got", 0);
	ml->data[12] = linkoff + s1->size;	/* indirectsymoff */
	ml->data[13] = (s2->size + s3->size) / 4;	/* nindirectsyms */

	ml->data[14] = 0;	/* extreloff */
	ml->data[15] = 0;	/* nextrel */
	ml->data[16] = 0;	/* locreloff */
	ml->data[17] = 0;	/* nlocrel */
}

vlong
domacholink(void)
{
	int size;
	LSym *s1, *s2, *s3, *s4;

	machosymtab();

	// write data that will be linkedit section
	s1 = linklookup(ctxt, ".machosymtab", 0);
	s2 = linklookup(ctxt, ".linkedit.plt", 0);
	s3 = linklookup(ctxt, ".linkedit.got", 0);
	s4 = linklookup(ctxt, ".machosymstr", 0);

	// Force the linkedit section to end on a 16-byte
	// boundary.  This allows pure (non-cgo) Go binaries
	// to be code signed correctly.
	//
	// Apple's codesign_allocate (a helper utility for
	// the codesign utility) can do this fine itself if
	// it is run on a dynamic Mach-O binary.  However,
	// when it is run on a pure (non-cgo) Go binary, where
	// the linkedit section is mostly empty, it fails to
	// account for the extra padding that it itself adds
	// when adding the LC_CODE_SIGNATURE load command
	// (which must be aligned on a 16-byte boundary).
	//
	// By forcing the linkedit section to end on a 16-byte
	// boundary, codesign_allocate will not need to apply
	// any alignment padding itself, working around the
	// issue.
	while(s4->size%16)
		adduint8(ctxt, s4, 0);
	
	size = s1->size + s2->size + s3->size + s4->size;

	if(size > 0) {
		linkoff = rnd(HEADR+segtext.len, INITRND) + rnd(segdata.filelen, INITRND) + rnd(segdwarf.filelen, INITRND);
		cseek(linkoff);

		cwrite(s1->p, s1->size);
		cwrite(s2->p, s2->size);
		cwrite(s3->p, s3->size);
		cwrite(s4->p, s4->size);
	}

	return rnd(size, INITRND);
}


void
machorelocsect(Section *sect, LSym *first)
{
	LSym *sym;
	int32 eaddr;
	Reloc *r;

	// If main section has no bits, nothing to relocate.
	if(sect->vaddr >= sect->seg->vaddr + sect->seg->filelen)
		return;
	
	sect->reloff = cpos();
	for(sym = first; sym != nil; sym = sym->next) {
		if(!sym->reachable)
			continue;
		if(sym->value >= sect->vaddr)
			break;
	}
	
	eaddr = sect->vaddr + sect->len;
	for(; sym != nil; sym = sym->next) {
		if(!sym->reachable)
			continue;
		if(sym->value >= eaddr)
			break;
		ctxt->cursym = sym;
		
		for(r = sym->r; r < sym->r+sym->nr; r++) {
			if(r->done)
				continue;
			if(machoreloc1(r, sym->value+r->off - sect->vaddr) < 0)
				diag("unsupported obj reloc %d/%d to %s", r->type, r->siz, r->sym->name);
		}
	}
		
	sect->rellen = cpos() - sect->reloff;
}

void
machoemitreloc(void)
{
	Section *sect;

	while(cpos()&7)
		cput(0);

	machorelocsect(segtext.sect, ctxt->textp);
	for(sect=segtext.sect->next; sect!=nil; sect=sect->next)
		machorelocsect(sect, datap);	
	for(sect=segdata.sect; sect!=nil; sect=sect->next)
		machorelocsect(sect, datap);	
}
