// 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	MachoDebug	xdebug[16];
static	int	nload, mload, nseg, ndebug, nsect;

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

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 = realloc(load, mload*sizeof load[0]);
		if(load == nil) {
			diag("out of memory");
			errorexit();
		}
	}

	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)
{
	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;
	nsect++;
	return s;
}

MachoDebug*
newMachoDebug(void)
{
	if(ndebug >= nelem(xdebug)) {
		diag("too many debugs");
		errorexit();
	}
	return &xdebug[ndebug++];
}


// 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;
	MachoDebug *d;
	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);
	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(s->name, 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(s->name, 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]);
	}

	for(i=0; i<ndebug; i++) {
		d = &xdebug[i];
		LPUT(3);	/* obsolete gdb debug info */
		LPUT(16);	/* size of symseg command */
		LPUT(d->fileoffset);
		LPUT(d->filesize);
	}

	return cpos() - o1;
}

void
domacho(void)
{
	Sym *s;

	if(debug['d'])
		return;

	// empirically, string table must begin with " \x00".
	s = lookup(".dynstr", 0);
	s->type = SMACHODYNSTR;
	s->reachable = 1;
	adduint8(s, ' ');
	adduint8(s, '\0');
	
	s = lookup(".dynsym", 0);
	s->type = SMACHODYNSYM;
	s->reachable = 1;
	
	s = lookup(".plt", 0);	// will be __symbol_stub
	s->type = SMACHOPLT;
	s->reachable = 1;
	
	s = lookup(".got", 0);	// will be __nl_symbol_ptr
	s->type = SMACHOGOT;
	s->reachable = 1;
	
	s = lookup(".linkedit.plt", 0);	// indirect table for .plt
	s->type = SMACHOINDIRECTPLT;
	s->reachable = 1;
	
	s = lookup(".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 = realloc(dylib, (ndylib+32)*sizeof dylib[0]);
		if(dylib == nil) {
			diag("out of memory");
			errorexit();
		}
	}
	dylib[ndylib++] = lib;
}

void
asmbmacho(void)
{
	vlong v, w;
	vlong va;
	int a, i;
	MachoHdr *mh;
	MachoSect *msect;
	MachoSeg *ms;
	MachoDebug *md;
	MachoLoad *ml;
	Sym *s;

	/* 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;
	}

	/* segment for zero page */
	ms = newMachoSeg("__PAGEZERO", 0);
	ms->vsize = va;

	/* text */
	v = rnd(HEADR+segtext.len, INITRND);
	ms = newMachoSeg("__TEXT", 2);
	ms->vaddr = va;
	ms->vsize = v;
	ms->filesize = v;
	ms->prot1 = 7;
	ms->prot2 = 5;

	msect = newMachoSect(ms, "__text");
	msect->addr = INITTEXT;
	msect->size = segtext.sect->len;
	msect->off = INITTEXT - va;
	msect->flag = 0x400;	/* flag - some instructions */
	
	s = lookup(".plt", 0);
	if(s->size > 0) {
		msect = newMachoSect(ms, "__symbol_stub1");
		msect->addr = symaddr(s);
		msect->size = s->size;
		msect->off = ms->fileoffset + msect->addr - ms->vaddr;
		msect->flag = 0x80000408;	/* flag */
		msect->res1 = 0;	/* index into indirect symbol table */
		msect->res2 = 6;	/* size of stubs */
	}

	/* data */
	w = segdata.len;
	ms = newMachoSeg("__DATA", 3);
	ms->vaddr = va+v;
	ms->vsize = w;
	ms->fileoffset = v;
	ms->filesize = segdata.filelen;
	ms->prot1 = 7;
	ms->prot2 = 3;

	msect = newMachoSect(ms, "__data");
	msect->addr = va+v;
	msect->off = v;
	msect->size = segdata.filelen;

	s = lookup(".got", 0);
	if(s->size > 0) {
		msect->size = symaddr(s) - msect->addr;

		msect = newMachoSect(ms, "__nl_symbol_ptr");
		msect->addr = symaddr(s);
		msect->size = s->size;
		msect->off = datoff(msect->addr);
		msect->align = 2;
		msect->flag = 6;	/* section with nonlazy symbol pointers */
		msect->res1 = lookup(".linkedit.plt", 0)->size / 4;	/* offset into indirect symbol table */
	}

	msect = newMachoSect(ms, "__bss");
	msect->addr = va+v+segdata.filelen;
	msect->size = segdata.len - segdata.filelen;
	msect->flag = 1;	/* flag - zero fill */

	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']) {
		Sym *s1, *s2, *s3, *s4;

		// must match domacholink below
		s1 = lookup(".dynsym", 0);
		s2 = lookup(".dynstr", 0);
		s3 = lookup(".linkedit.plt", 0);
		s4 = lookup(".linkedit.got", 0);

		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] = s1->size / (macho64 ? 16 : 12);	/* nsyms */
		ml->data[2] = linkoff + s1->size;	/* stroff */
		ml->data[3] = s2->size;	/* strsize */

		ml = newMachoLoad(11, 18);	/* LC_DYSYMTAB */
		ml->data[0] = 0;	/* ilocalsym */
		ml->data[1] = 0;	/* nlocalsym */
		ml->data[2] = 0;	/* iextdefsym */
		ml->data[3] = ndynexp;	/* nextdefsym */
		ml->data[4] = ndynexp;	/* iundefsym */
		ml->data[5] = (s1->size / (macho64 ? 16 : 12)) - ndynexp;	/* 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 */
		ml->data[12] = linkoff + s1->size + s2->size;	/* indirectsymoff */
		ml->data[13] = (s3->size + s4->size) / 4;	/* nindirectsyms */
		ml->data[14] = 0;	/* extreloff */
		ml->data[15] = 0;	/* nextrel */
		ml->data[16] = 0;	/* locreloff */
		ml->data[17] = 0;	/* nlocrel */

		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]);
		}
	}

	if(!debug['s']) {
		Sym *s;

		md = newMachoDebug();
		s = lookup("symtab", 0);
		md->fileoffset = datoff(s->value);
		md->filesize = s->size;

		md = newMachoDebug();
		s = lookup("pclntab", 0);
		md->fileoffset = datoff(s->value);
		md->filesize = s->size;

		dwarfaddmachoheaders();
	}

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

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

	// write data that will be linkedit section
	s1 = lookup(".dynsym", 0);
	relocsym(s1);
	s2 = lookup(".dynstr", 0);
	s3 = lookup(".linkedit.plt", 0);
	s4 = lookup(".linkedit.got", 0);

	while(s2->size%4)
		adduint8(s2, 0);
	
	size = s1->size + s2->size + s3->size + s4->size;

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

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

	return rnd(size, INITRND);
}
