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

#include	"l.h"
#include	"lib.h"
#include	"../ld/elf.h"

/*
 * We use the 64-bit data structures on both 32- and 64-bit machines
 * in order to write the code just once.  The 64-bit data structure is
 * written in the 32-bit format on the 32-bit machines.
 */
#define	NSECT	48

int	iself;

static	int	elf64;
static	ElfEhdr	hdr;
static	ElfPhdr	*phdr[NSECT];
static	ElfShdr	*shdr[NSECT];
static	char	*interp;

typedef struct Elfstring Elfstring;
struct Elfstring
{
	char *s;
	int off;
};

static Elfstring elfstr[100];
static int nelfstr;

static char buildinfo[32];

/*
 Initialize the global variable that describes the ELF header. It will be updated as
 we write section and prog headers.
 */
void
elfinit(void)
{
	iself = 1;

	switch(thechar) {
	// 64-bit architectures
	case '6':
		elf64 = 1;
		hdr.phoff = ELF64HDRSIZE;	/* Must be be ELF64HDRSIZE: first PHdr must follow ELF header */
		hdr.shoff = ELF64HDRSIZE;	/* Will move as we add PHeaders */
		hdr.ehsize = ELF64HDRSIZE;	/* Must be ELF64HDRSIZE */
		hdr.phentsize = ELF64PHDRSIZE;	/* Must be ELF64PHDRSIZE */
		hdr.shentsize = ELF64SHDRSIZE;	/* Must be ELF64SHDRSIZE */
		break;

	// 32-bit architectures
	case '5':
		// we use EABI on both linux/arm and freebsd/arm.
		if(HEADTYPE == Hlinux || HEADTYPE == Hfreebsd)
			hdr.flags = 0x5000002; // has entry point, Version5 EABI
		// fallthrough
	default:
		hdr.phoff = ELF32HDRSIZE;	/* Must be be ELF32HDRSIZE: first PHdr must follow ELF header */
		hdr.shoff = ELF32HDRSIZE;	/* Will move as we add PHeaders */
		hdr.ehsize = ELF32HDRSIZE;	/* Must be ELF32HDRSIZE */
		hdr.phentsize = ELF32PHDRSIZE;	/* Must be ELF32PHDRSIZE */
		hdr.shentsize = ELF32SHDRSIZE;	/* Must be ELF32SHDRSIZE */
	}
}

void
elf64phdr(ElfPhdr *e)
{
	LPUT(e->type);
	LPUT(e->flags);
	VPUT(e->off);
	VPUT(e->vaddr);
	VPUT(e->paddr);
	VPUT(e->filesz);
	VPUT(e->memsz);
	VPUT(e->align);
}

void
elf32phdr(ElfPhdr *e)
{
	int frag;
	
	if(e->type == PT_LOAD) {
		// Correct ELF loaders will do this implicitly,
		// but buggy ELF loaders like the one in some
		// versions of QEMU won't.
		frag = e->vaddr&(e->align-1);
		e->off -= frag;
		e->vaddr -= frag;
		e->paddr -= frag;
		e->filesz += frag;
		e->memsz += frag;
	}
	LPUT(e->type);
	LPUT(e->off);
	LPUT(e->vaddr);
	LPUT(e->paddr);
	LPUT(e->filesz);
	LPUT(e->memsz);
	LPUT(e->flags);
	LPUT(e->align);
}

void
elf64shdr(ElfShdr *e)
{
	LPUT(e->name);
	LPUT(e->type);
	VPUT(e->flags);
	VPUT(e->addr);
	VPUT(e->off);
	VPUT(e->size);
	LPUT(e->link);
	LPUT(e->info);
	VPUT(e->addralign);
	VPUT(e->entsize);
}

void
elf32shdr(ElfShdr *e)
{
	LPUT(e->name);
	LPUT(e->type);
	LPUT(e->flags);
	LPUT(e->addr);
	LPUT(e->off);
	LPUT(e->size);
	LPUT(e->link);
	LPUT(e->info);
	LPUT(e->addralign);
	LPUT(e->entsize);
}

uint32
elfwriteshdrs(void)
{
	int i;

	if (elf64) {
		for (i = 0; i < hdr.shnum; i++)
			elf64shdr(shdr[i]);
		return hdr.shnum * ELF64SHDRSIZE;
	}
	for (i = 0; i < hdr.shnum; i++)
		elf32shdr(shdr[i]);
	return hdr.shnum * ELF32SHDRSIZE;
}

void
elfsetstring(char *s, int off)
{
	if(nelfstr >= nelem(elfstr)) {
		diag("too many elf strings");
		errorexit();
	}
	elfstr[nelfstr].s = s;
	elfstr[nelfstr].off = off;
	nelfstr++;
}

uint32
elfwritephdrs(void)
{
	int i;

	if (elf64) {
		for (i = 0; i < hdr.phnum; i++)
			elf64phdr(phdr[i]);
		return hdr.phnum * ELF64PHDRSIZE;
	}
	for (i = 0; i < hdr.phnum; i++)
		elf32phdr(phdr[i]);
	return hdr.phnum * ELF32PHDRSIZE;
}

ElfPhdr*
newElfPhdr(void)
{
	ElfPhdr *e;

	e = mal(sizeof *e);
	if (hdr.phnum >= NSECT)
		diag("too many phdrs");
	else
		phdr[hdr.phnum++] = e;
	if (elf64)
		hdr.shoff += ELF64PHDRSIZE;
	else
		hdr.shoff += ELF32PHDRSIZE;
	return e;
}

ElfShdr*
newElfShdr(vlong name)
{
	ElfShdr *e;

	e = mal(sizeof *e);
	e->name = name;
	e->shnum = hdr.shnum;
	if (hdr.shnum >= NSECT) {
		diag("too many shdrs");
	} else {
		shdr[hdr.shnum++] = e;
	}
	return e;
}

ElfEhdr*
getElfEhdr(void)
{
	return &hdr;
}

uint32
elf64writehdr(void)
{
	int i;

	for (i = 0; i < EI_NIDENT; i++)
		cput(hdr.ident[i]);
	WPUT(hdr.type);
	WPUT(hdr.machine);
	LPUT(hdr.version);
	VPUT(hdr.entry);
	VPUT(hdr.phoff);
	VPUT(hdr.shoff);
	LPUT(hdr.flags);
	WPUT(hdr.ehsize);
	WPUT(hdr.phentsize);
	WPUT(hdr.phnum);
	WPUT(hdr.shentsize);
	WPUT(hdr.shnum);
	WPUT(hdr.shstrndx);
	return ELF64HDRSIZE;
}

uint32
elf32writehdr(void)
{
	int i;

	for (i = 0; i < EI_NIDENT; i++)
		cput(hdr.ident[i]);
	WPUT(hdr.type);
	WPUT(hdr.machine);
	LPUT(hdr.version);
	LPUT(hdr.entry);
	LPUT(hdr.phoff);
	LPUT(hdr.shoff);
	LPUT(hdr.flags);
	WPUT(hdr.ehsize);
	WPUT(hdr.phentsize);
	WPUT(hdr.phnum);
	WPUT(hdr.shentsize);
	WPUT(hdr.shnum);
	WPUT(hdr.shstrndx);
	return ELF32HDRSIZE;
}

uint32
elfwritehdr(void)
{
	if(elf64)
		return elf64writehdr();
	return elf32writehdr();
}

/* Taken directly from the definition document for ELF64 */
uint32
elfhash(uchar *name)
{
	uint32 h = 0, g;
	while (*name) {
		h = (h << 4) + *name++;
		if (g = h & 0xf0000000)
			h ^= g >> 24;
		h &= 0x0fffffff;
	}
	return h;
}

void
elfwritedynent(LSym *s, int tag, uint64 val)
{
	if(elf64) {
		adduint64(ctxt, s, tag);
		adduint64(ctxt, s, val);
	} else {
		adduint32(ctxt, s, tag);
		adduint32(ctxt, s, val);
	}
}

void
elfwritedynentsym(LSym *s, int tag, LSym *t)
{
	if(elf64)
		adduint64(ctxt, s, tag);
	else
		adduint32(ctxt, s, tag);
	addaddr(ctxt, s, t);
}

void
elfwritedynentsymsize(LSym *s, int tag, LSym *t)
{
	if(elf64)
		adduint64(ctxt, s, tag);
	else
		adduint32(ctxt, s, tag);
	addsize(ctxt, s, t);
}

int
elfinterp(ElfShdr *sh, uint64 startva, uint64 resoff, char *p)
{
	int n;

	interp = p;
	n = strlen(interp)+1;
	sh->addr = startva + resoff - n;
	sh->off = resoff - n;
	sh->size = n;

	return n;
}

int
elfwriteinterp(void)
{
	ElfShdr *sh;
	
	sh = elfshname(".interp");
	cseek(sh->off);
	cwrite(interp, sh->size);
	return sh->size;
}

int
elfnote(ElfShdr *sh, uint64 startva, uint64 resoff, int sz)
{
	uint64 n;

	n = sizeof(Elf_Note) + sz + resoff % 4;

	sh->type = SHT_NOTE;
	sh->flags = SHF_ALLOC;
	sh->addralign = 4;
	sh->addr = startva + resoff - n;
	sh->off = resoff - n;
	sh->size = n - resoff % 4;

	return n;
}

ElfShdr *
elfwritenotehdr(char *str, uint32 namesz, uint32 descsz, uint32 tag)
{
	ElfShdr *sh;
	
	sh = elfshname(str);

	// Write Elf_Note header.
	cseek(sh->off);
	LPUT(namesz);
	LPUT(descsz);
	LPUT(tag);

	return sh;
}

// NetBSD Signature (as per sys/exec_elf.h)
#define ELF_NOTE_NETBSD_NAMESZ		7
#define ELF_NOTE_NETBSD_DESCSZ		4
#define ELF_NOTE_NETBSD_TAG		1
#define ELF_NOTE_NETBSD_NAME		"NetBSD\0\0"
#define ELF_NOTE_NETBSD_VERSION		599000000	/* NetBSD 5.99 */

int
elfnetbsdsig(ElfShdr *sh, uint64 startva, uint64 resoff)
{
	int n;

	n = rnd(ELF_NOTE_NETBSD_NAMESZ, 4) + rnd(ELF_NOTE_NETBSD_DESCSZ, 4);
	return elfnote(sh, startva, resoff, n);
}

int
elfwritenetbsdsig(void)
{
	ElfShdr *sh;

	// Write Elf_Note header.
	sh = elfwritenotehdr(".note.netbsd.ident", ELF_NOTE_NETBSD_NAMESZ, ELF_NOTE_NETBSD_DESCSZ, ELF_NOTE_NETBSD_TAG);
	if(sh == nil)
		return 0;

	// Followed by NetBSD string and version.
	cwrite(ELF_NOTE_NETBSD_NAME, ELF_NOTE_NETBSD_NAMESZ + 1);
	LPUT(ELF_NOTE_NETBSD_VERSION);

	return sh->size;
}

// OpenBSD Signature
#define ELF_NOTE_OPENBSD_NAMESZ		8
#define ELF_NOTE_OPENBSD_DESCSZ		4
#define ELF_NOTE_OPENBSD_TAG		1
#define ELF_NOTE_OPENBSD_NAME		"OpenBSD\0"
#define ELF_NOTE_OPENBSD_VERSION	0

int
elfopenbsdsig(ElfShdr *sh, uint64 startva, uint64 resoff)
{
	int n;

	n = ELF_NOTE_OPENBSD_NAMESZ + ELF_NOTE_OPENBSD_DESCSZ;
	return elfnote(sh, startva, resoff, n);
}

int
elfwriteopenbsdsig(void)
{
	ElfShdr *sh;

	// Write Elf_Note header.
	sh = elfwritenotehdr(".note.openbsd.ident", ELF_NOTE_OPENBSD_NAMESZ, ELF_NOTE_OPENBSD_DESCSZ, ELF_NOTE_OPENBSD_TAG);
	if(sh == nil)
		return 0;

	// Followed by OpenBSD string and version.
	cwrite(ELF_NOTE_OPENBSD_NAME, ELF_NOTE_OPENBSD_NAMESZ);
	LPUT(ELF_NOTE_OPENBSD_VERSION);

	return sh->size;
}

void
addbuildinfo(char *val)
{
	char *ov;
	int i, b, j;

	if(val[0] != '0' || val[1] != 'x') {
		fprint(2, "%s: -B argument must start with 0x: %s\n", argv0, val);
		exits("usage");
	}
	ov = val;
	val += 2;
	i = 0;
	while(*val != '\0') {
		if(val[1] == '\0') {
			fprint(2, "%s: -B argument must have even number of digits: %s\n", argv0, ov);
			exits("usage");
		}
		b = 0;
		for(j = 0; j < 2; j++, val++) {
			b *= 16;
		  	if(*val >= '0' && *val <= '9')
				b += *val - '0';
			else if(*val >= 'a' && *val <= 'f')
				b += *val - 'a' + 10;
			else if(*val >= 'A' && *val <= 'F')
				b += *val - 'A' + 10;
			else {
				fprint(2, "%s: -B argument contains invalid hex digit %c: %s\n", argv0, *val, ov);
				exits("usage");
			}
		}
		if(i >= nelem(buildinfo)) {
			fprint(2, "%s: -B option too long (max %d digits): %s\n", argv0, (int)nelem(buildinfo), ov);
			exits("usage");
		}
		buildinfo[i++] = b;
	}
	buildinfolen = i;
}

// Build info note
#define ELF_NOTE_BUILDINFO_NAMESZ	4
#define ELF_NOTE_BUILDINFO_TAG		3
#define ELF_NOTE_BUILDINFO_NAME		"GNU\0"

int
elfbuildinfo(ElfShdr *sh, uint64 startva, uint64 resoff)
{
	int n;

	n = ELF_NOTE_BUILDINFO_NAMESZ + rnd(buildinfolen, 4);
	return elfnote(sh, startva, resoff, n);
}

int
elfwritebuildinfo(void)
{
	ElfShdr *sh;

	sh = elfwritenotehdr(".note.gnu.build-id", ELF_NOTE_BUILDINFO_NAMESZ, buildinfolen, ELF_NOTE_BUILDINFO_TAG);
	if(sh == nil)
		return 0;

	cwrite(ELF_NOTE_BUILDINFO_NAME, ELF_NOTE_BUILDINFO_NAMESZ);
	cwrite(buildinfo, buildinfolen);
	cwrite("\0\0\0", rnd(buildinfolen, 4) - buildinfolen);

	return sh->size;
}

extern int nelfsym;
int elfverneed;

typedef struct Elfaux Elfaux;
typedef struct Elflib Elflib;

struct Elflib
{
	Elflib *next;
	Elfaux *aux;
	char *file;
};

struct Elfaux
{
	Elfaux *next;
	int num;
	char *vers;
};

Elfaux*
addelflib(Elflib **list, char *file, char *vers)
{
	Elflib *lib;
	Elfaux *aux;
	
	for(lib=*list; lib; lib=lib->next)
		if(strcmp(lib->file, file) == 0)
			goto havelib;
	lib = mal(sizeof *lib);
	lib->next = *list;
	lib->file = file;
	*list = lib;
havelib:
	for(aux=lib->aux; aux; aux=aux->next)
		if(strcmp(aux->vers, vers) == 0)
			goto haveaux;
	aux = mal(sizeof *aux);
	aux->next = lib->aux;
	aux->vers = vers;
	lib->aux = aux;
haveaux:
	return aux;
}

void
elfdynhash(void)
{
	LSym *s, *sy, *dynstr;
	int i, j, nbucket, b, nfile;
	uint32 hc, *chain, *buckets;
	int nsym;
	char *name;
	Elfaux **need;
	Elflib *needlib;
	Elflib *l;
	Elfaux *x;
	
	if(!iself)
		return;

	nsym = nelfsym;
	s = linklookup(ctxt, ".hash", 0);
	s->type = SELFROSECT;
	s->reachable = 1;

	i = nsym;
	nbucket = 1;
	while(i > 0) {
		++nbucket;
		i >>= 1;
	}

	needlib = nil;
	need = malloc(nsym * sizeof need[0]);
	chain = malloc(nsym * sizeof chain[0]);
	buckets = malloc(nbucket * sizeof buckets[0]);
	if(need == nil || chain == nil || buckets == nil) {
		ctxt->cursym = nil;
		diag("out of memory");
		errorexit();
	}
	memset(need, 0, nsym * sizeof need[0]);
	memset(chain, 0, nsym * sizeof chain[0]);
	memset(buckets, 0, nbucket * sizeof buckets[0]);
	for(sy=ctxt->allsym; sy!=S; sy=sy->allsym) {
		if (sy->dynid <= 0)
			continue;

		if(sy->dynimpvers)
			need[sy->dynid] = addelflib(&needlib, sy->dynimplib, sy->dynimpvers);

		name = sy->extname;
		hc = elfhash((uchar*)name);

		b = hc % nbucket;
		chain[sy->dynid] = buckets[b];
		buckets[b] = sy->dynid;
	}

	adduint32(ctxt, s, nbucket);
	adduint32(ctxt, s, nsym);
	for(i = 0; i<nbucket; i++)
		adduint32(ctxt, s, buckets[i]);
	for(i = 0; i<nsym; i++)
		adduint32(ctxt, s, chain[i]);

	free(chain);
	free(buckets);
	
	// version symbols
	dynstr = linklookup(ctxt, ".dynstr", 0);
	s = linklookup(ctxt, ".gnu.version_r", 0);
	i = 2;
	nfile = 0;
	for(l=needlib; l; l=l->next) {
		nfile++;
		// header
		adduint16(ctxt, s, 1);  // table version
		j = 0;
		for(x=l->aux; x; x=x->next)
			j++;
		adduint16(ctxt, s, j);	// aux count
		adduint32(ctxt, s, addstring(dynstr, l->file));  // file string offset
		adduint32(ctxt, s, 16);  // offset from header to first aux
		if(l->next)
			adduint32(ctxt, s, 16+j*16);  // offset from this header to next
		else
			adduint32(ctxt, s, 0);
		
		for(x=l->aux; x; x=x->next) {
			x->num = i++;
			// aux struct
			adduint32(ctxt, s, elfhash((uchar*)x->vers));  // hash
			adduint16(ctxt, s, 0);  // flags
			adduint16(ctxt, s, x->num);  // other - index we refer to this by
			adduint32(ctxt, s, addstring(dynstr, x->vers));  // version string offset
			if(x->next)
				adduint32(ctxt, s, 16);  // offset from this aux to next
			else
				adduint32(ctxt, s, 0);
		}
	}

	// version references
	s = linklookup(ctxt, ".gnu.version", 0);
	for(i=0; i<nsym; i++) {
		if(i == 0)
			adduint16(ctxt, s, 0); // first entry - no symbol
		else if(need[i] == nil)
			adduint16(ctxt, s, 1); // global
		else
			adduint16(ctxt, s, need[i]->num);
	}

	free(need);

	s = linklookup(ctxt, ".dynamic", 0);
	elfverneed = nfile;
	if(elfverneed) {
		elfwritedynentsym(s, DT_VERNEED, linklookup(ctxt, ".gnu.version_r", 0));
		elfwritedynent(s, DT_VERNEEDNUM, nfile);
		elfwritedynentsym(s, DT_VERSYM, linklookup(ctxt, ".gnu.version", 0));
	}

	if(thechar == '6') {
		sy = linklookup(ctxt, ".rela.plt", 0);
		if(sy->size > 0) {
			elfwritedynent(s, DT_PLTREL, DT_RELA);
			elfwritedynentsymsize(s, DT_PLTRELSZ, sy);
			elfwritedynentsym(s, DT_JMPREL, sy);
		}
	} else {
		sy = linklookup(ctxt, ".rel.plt", 0);
		if(sy->size > 0) {
			elfwritedynent(s, DT_PLTREL, DT_REL);
			elfwritedynentsymsize(s, DT_PLTRELSZ, sy);
			elfwritedynentsym(s, DT_JMPREL, sy);
		}
	}

	elfwritedynent(s, DT_NULL, 0);
}

ElfPhdr*
elfphload(Segment *seg)
{
	ElfPhdr *ph;
	
	ph = newElfPhdr();
	ph->type = PT_LOAD;
	if(seg->rwx & 4)
		ph->flags |= PF_R;
	if(seg->rwx & 2)
		ph->flags |= PF_W;
	if(seg->rwx & 1)
		ph->flags |= PF_X;
	ph->vaddr = seg->vaddr;
	ph->paddr = seg->vaddr;
	ph->memsz = seg->len;
	ph->off = seg->fileoff;
	ph->filesz = seg->filelen;
	ph->align = INITRND;
	
	return ph;
}

ElfShdr*
elfshname(char *name)
{
	int i, off;
	ElfShdr *sh;
	
	for(i=0; i<nelfstr; i++) {
		if(strcmp(name, elfstr[i].s) == 0) {
			off = elfstr[i].off;
			goto found;
		}
	}
	diag("cannot find elf name %s", name);
	errorexit();
	return nil;

found:
	for(i=0; i<hdr.shnum; i++) {
		sh = shdr[i];
		if(sh->name == off)
			return sh;
	}
	
	sh = newElfShdr(off);
	return sh;
}

ElfShdr*
elfshalloc(Section *sect)
{
	ElfShdr *sh;
	
	sh = elfshname(sect->name);
	sect->elfsect = sh;
	return sh;
}

ElfShdr*
elfshbits(Section *sect)
{
	ElfShdr *sh;
	
	sh = elfshalloc(sect);
	if(sh->type > 0)
		return sh;

	if(sect->vaddr < sect->seg->vaddr + sect->seg->filelen)
		sh->type = SHT_PROGBITS;
	else
		sh->type = SHT_NOBITS;
	sh->flags = SHF_ALLOC;
	if(sect->rwx & 1)
		sh->flags |= SHF_EXECINSTR;
	if(sect->rwx & 2)
		sh->flags |= SHF_WRITE;
	if(strcmp(sect->name, ".tbss") == 0) {
		sh->flags |= SHF_TLS;
		sh->type = SHT_NOBITS;
	}
	if(linkmode != LinkExternal)
		sh->addr = sect->vaddr;
	sh->addralign = sect->align;
	sh->size = sect->len;
	sh->off = sect->seg->fileoff + sect->vaddr - sect->seg->vaddr;

	return sh;
}

ElfShdr*
elfshreloc(Section *sect)
{
	int typ;
	ElfShdr *sh;
	char *prefix;
	char buf[100];
	
	// If main section is SHT_NOBITS, nothing to relocate.
	// Also nothing to relocate in .shstrtab.
	if(sect->vaddr >= sect->seg->vaddr + sect->seg->filelen)
		return nil;
	if(strcmp(sect->name, ".shstrtab") == 0 || strcmp(sect->name, ".tbss") == 0)
		return nil;

	if(thechar == '6') {
		prefix = ".rela";
		typ = SHT_RELA;
	} else {
		prefix = ".rel";
		typ = SHT_REL;
	}

	snprint(buf, sizeof buf, "%s%s", prefix, sect->name);
	sh = elfshname(buf);
	sh->type = typ;
	sh->entsize = RegSize*(2+(typ==SHT_RELA));
	sh->link = elfshname(".symtab")->shnum;
	sh->info = sect->elfsect->shnum;
	sh->off = sect->reloff;
	sh->size = sect->rellen;
	sh->addralign = RegSize;
	return sh;
}

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

	// If main section is SHT_NOBITS, nothing to relocate.
	// Also nothing to relocate in .shstrtab.
	if(sect->vaddr >= sect->seg->vaddr + sect->seg->filelen)
		return;
	if(strcmp(sect->name, ".shstrtab") == 0)
		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(r->xsym == nil) {
				diag("missing xsym in relocation");
				continue;
			}
			if(r->xsym->elfsym == 0)
				diag("reloc %d to non-elf symbol %s (outer=%s) %d", r->type, r->sym->name, r->xsym->name, r->sym->type);
			if(elfreloc1(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
elfemitreloc(void)
{
	Section *sect;

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

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

void
doelf(void)
{
	LSym *s, *shstrtab, *dynstr;

	if(!iself)
		return;

	/* predefine strings we need for section headers */
	shstrtab = linklookup(ctxt, ".shstrtab", 0);
	shstrtab->type = SELFROSECT;
	shstrtab->reachable = 1;

	addstring(shstrtab, "");
	addstring(shstrtab, ".text");
	addstring(shstrtab, ".noptrdata");
	addstring(shstrtab, ".data");
	addstring(shstrtab, ".bss");
	addstring(shstrtab, ".noptrbss");
	// generate .tbss section (except for OpenBSD where it's not supported)
	// for dynamic internal linker or external linking, so that various
	// binutils could correctly calculate PT_TLS size.
	// see http://golang.org/issue/5200.
	if(HEADTYPE != Hopenbsd)
	if(!debug['d'] || linkmode == LinkExternal)
		addstring(shstrtab, ".tbss");
	if(HEADTYPE == Hnetbsd)
		addstring(shstrtab, ".note.netbsd.ident");
	if(HEADTYPE == Hopenbsd)
		addstring(shstrtab, ".note.openbsd.ident");
	if(buildinfolen > 0)
		addstring(shstrtab, ".note.gnu.build-id");
	addstring(shstrtab, ".elfdata");
	addstring(shstrtab, ".rodata");
	addstring(shstrtab, ".typelink");
	addstring(shstrtab, ".gosymtab");
	addstring(shstrtab, ".gopclntab");
	
	if(linkmode == LinkExternal) {
		debug_s = debug['s'];
		debug['s'] = 0;
		debug['d'] = 1;

		if(thechar == '6') {
			addstring(shstrtab, ".rela.text");
			addstring(shstrtab, ".rela.rodata");
			addstring(shstrtab, ".rela.typelink");
			addstring(shstrtab, ".rela.gosymtab");
			addstring(shstrtab, ".rela.gopclntab");
			addstring(shstrtab, ".rela.noptrdata");
			addstring(shstrtab, ".rela.data");
		} else {
			addstring(shstrtab, ".rel.text");
			addstring(shstrtab, ".rel.rodata");
			addstring(shstrtab, ".rel.typelink");
			addstring(shstrtab, ".rel.gosymtab");
			addstring(shstrtab, ".rel.gopclntab");
			addstring(shstrtab, ".rel.noptrdata");
			addstring(shstrtab, ".rel.data");
		}
		// add a .note.GNU-stack section to mark the stack as non-executable
		addstring(shstrtab, ".note.GNU-stack");
	}

	if(flag_shared) {
		addstring(shstrtab, ".init_array");
		if(thechar == '6')
			addstring(shstrtab, ".rela.init_array");
		else
			addstring(shstrtab, ".rel.init_array");
	}

	if(!debug['s']) {
		addstring(shstrtab, ".symtab");
		addstring(shstrtab, ".strtab");
		dwarfaddshstrings(shstrtab);
	}
	addstring(shstrtab, ".shstrtab");

	if(!debug['d']) {	/* -d suppresses dynamic loader format */
		addstring(shstrtab, ".interp");
		addstring(shstrtab, ".hash");
		addstring(shstrtab, ".got");
		addstring(shstrtab, ".got.plt");
		addstring(shstrtab, ".dynamic");
		addstring(shstrtab, ".dynsym");
		addstring(shstrtab, ".dynstr");
		if(thechar == '6') {
			addstring(shstrtab, ".rela");
			addstring(shstrtab, ".rela.plt");
		} else {
			addstring(shstrtab, ".rel");
			addstring(shstrtab, ".rel.plt");
		}
		addstring(shstrtab, ".plt");
		addstring(shstrtab, ".gnu.version");
		addstring(shstrtab, ".gnu.version_r");

		/* dynamic symbol table - first entry all zeros */
		s = linklookup(ctxt, ".dynsym", 0);
		s->type = SELFROSECT;
		s->reachable = 1;
		if(thechar == '6')
			s->size += ELF64SYMSIZE;
		else
			s->size += ELF32SYMSIZE;

		/* dynamic string table */
		s = linklookup(ctxt, ".dynstr", 0);
		s->type = SELFROSECT;
		s->reachable = 1;
		if(s->size == 0)
			addstring(s, "");
		dynstr = s;

		/* relocation table */
		if(thechar == '6')
			s = linklookup(ctxt, ".rela", 0);
		else
			s = linklookup(ctxt, ".rel", 0);
		s->reachable = 1;
		s->type = SELFROSECT;

		/* global offset table */
		s = linklookup(ctxt, ".got", 0);
		s->reachable = 1;
		s->type = SELFSECT; // writable

		/* hash */
		s = linklookup(ctxt, ".hash", 0);
		s->reachable = 1;
		s->type = SELFROSECT;

		s = linklookup(ctxt, ".got.plt", 0);
		s->reachable = 1;
		s->type = SELFSECT; // writable

		s = linklookup(ctxt, ".plt", 0);
		s->reachable = 1;
		s->type = SELFRXSECT;
		
		elfsetupplt();
		
		if(thechar == '6')
			s = linklookup(ctxt, ".rela.plt", 0);
		else
			s = linklookup(ctxt, ".rel.plt", 0);
		s->reachable = 1;
		s->type = SELFROSECT;
		
		s = linklookup(ctxt, ".gnu.version", 0);
		s->reachable = 1;
		s->type = SELFROSECT;
		
		s = linklookup(ctxt, ".gnu.version_r", 0);
		s->reachable = 1;
		s->type = SELFROSECT;

		/* define dynamic elf table */
		s = linklookup(ctxt, ".dynamic", 0);
		s->reachable = 1;
		s->type = SELFSECT; // writable

		/*
		 * .dynamic table
		 */
		elfwritedynentsym(s, DT_HASH, linklookup(ctxt, ".hash", 0));
		elfwritedynentsym(s, DT_SYMTAB, linklookup(ctxt, ".dynsym", 0));
		if(thechar == '6')
			elfwritedynent(s, DT_SYMENT, ELF64SYMSIZE);
		else
			elfwritedynent(s, DT_SYMENT, ELF32SYMSIZE);
		elfwritedynentsym(s, DT_STRTAB, linklookup(ctxt, ".dynstr", 0));
		elfwritedynentsymsize(s, DT_STRSZ, linklookup(ctxt, ".dynstr", 0));
		if(thechar == '6') {
			elfwritedynentsym(s, DT_RELA, linklookup(ctxt, ".rela", 0));
			elfwritedynentsymsize(s, DT_RELASZ, linklookup(ctxt, ".rela", 0));
			elfwritedynent(s, DT_RELAENT, ELF64RELASIZE);
		} else {
			elfwritedynentsym(s, DT_REL, linklookup(ctxt, ".rel", 0));
			elfwritedynentsymsize(s, DT_RELSZ, linklookup(ctxt, ".rel", 0));
			elfwritedynent(s, DT_RELENT, ELF32RELSIZE);
		}
		if(rpath)
			elfwritedynent(s, DT_RUNPATH, addstring(dynstr, rpath));
		
		elfwritedynentsym(s, DT_PLTGOT, linklookup(ctxt, ".got.plt", 0));

		// Solaris dynamic linker can't handle an empty .rela.plt if
		// DT_JMPREL is emitted so we have to defer generation of DT_PLTREL,
		// DT_PLTRELSZ, and DT_JMPREL dynamic entries until after we know the
		// size of .rel(a).plt section.
		elfwritedynent(s, DT_DEBUG, 0);

		// Do not write DT_NULL.  elfdynhash will finish it.
	}
}

void
shsym(ElfShdr *sh, LSym *s)
{
	vlong addr;
	addr = symaddr(s);
	if(sh->flags&SHF_ALLOC)
		sh->addr = addr;
	sh->off = datoff(addr);
	sh->size = s->size;
}

void
phsh(ElfPhdr *ph, ElfShdr *sh)
{
	ph->vaddr = sh->addr;
	ph->paddr = ph->vaddr;
	ph->off = sh->off;
	ph->filesz = sh->size;
	ph->memsz = sh->size;
	ph->align = sh->addralign;
}

void
asmbelfsetup(void)
{
	Section *sect;

	/* This null SHdr must appear before all others */
	elfshname("");
	
	for(sect=segtext.sect; sect!=nil; sect=sect->next)
		elfshalloc(sect);
	for(sect=segrodata.sect; sect!=nil; sect=sect->next)
		elfshalloc(sect);
	for(sect=segdata.sect; sect!=nil; sect=sect->next)
		elfshalloc(sect);
}

void
asmbelf(vlong symo)
{
	vlong a, o;
	vlong startva, resoff;
	ElfEhdr *eh;
	ElfPhdr *ph, *pph, *pnote;
	ElfShdr *sh;
	Section *sect;

	eh = getElfEhdr();
	switch(thechar) {
	default:
		diag("unknown architecture in asmbelf");
		errorexit();
	case '5':
		eh->machine = EM_ARM;
		break;
	case '6':
		eh->machine = EM_X86_64;
		break;
	case '8':
		eh->machine = EM_386;
		break;
	}

	startva = INITTEXT - HEADR;
	resoff = ELFRESERVE;
	
	pph = nil;
	if(linkmode == LinkExternal) {
		/* skip program headers */
		eh->phoff = 0;
		eh->phentsize = 0;
		goto elfobj;
	}

	/* program header info */
	pph = newElfPhdr();
	pph->type = PT_PHDR;
	pph->flags = PF_R;
	pph->off = eh->ehsize;
	pph->vaddr = INITTEXT - HEADR + pph->off;
	pph->paddr = INITTEXT - HEADR + pph->off;
	pph->align = INITRND;

	/*
	 * PHDR must be in a loaded segment. Adjust the text
	 * segment boundaries downwards to include it.
	 * Except on NaCl where it must not be loaded.
	 */
	if(HEADTYPE != Hnacl) {
		o = segtext.vaddr - pph->vaddr;
		segtext.vaddr -= o;
		segtext.len += o;
		o = segtext.fileoff - pph->off;
		segtext.fileoff -= o;
		segtext.filelen += o;
	}

	if(!debug['d']) {
		/* interpreter */
		sh = elfshname(".interp");
		sh->type = SHT_PROGBITS;
		sh->flags = SHF_ALLOC;
		sh->addralign = 1;
		if(interpreter == nil) {
			switch(HEADTYPE) {
			case Hlinux:
				interpreter = linuxdynld;
				break;
			case Hfreebsd:
				interpreter = freebsddynld;
				break;
			case Hnetbsd:
				interpreter = netbsddynld;
				break;
			case Hopenbsd:
				interpreter = openbsddynld;
				break;
			case Hdragonfly:
				interpreter = dragonflydynld;
				break;
			case Hsolaris:
				interpreter = solarisdynld;
				break;
			}
		}
		resoff -= elfinterp(sh, startva, resoff, interpreter);

		ph = newElfPhdr();
		ph->type = PT_INTERP;
		ph->flags = PF_R;
		phsh(ph, sh);
	}

	pnote = nil;
	if(HEADTYPE == Hnetbsd || HEADTYPE == Hopenbsd) {
		sh = nil;
		switch(HEADTYPE) {
		case Hnetbsd:
			sh = elfshname(".note.netbsd.ident");
			resoff -= elfnetbsdsig(sh, startva, resoff);
			break;
		case Hopenbsd:
			sh = elfshname(".note.openbsd.ident");
			resoff -= elfopenbsdsig(sh, startva, resoff);
			break;
		}

		pnote = newElfPhdr();
		pnote->type = PT_NOTE;
		pnote->flags = PF_R;
		phsh(pnote, sh);
	}

	if(buildinfolen > 0) {
		sh = elfshname(".note.gnu.build-id");
		resoff -= elfbuildinfo(sh, startva, resoff);

		if(pnote == nil) {
			pnote = newElfPhdr();
			pnote->type = PT_NOTE;
			pnote->flags = PF_R;
		}
		phsh(pnote, sh);
	}

	// Additions to the reserved area must be above this line.
	USED(resoff);

	elfphload(&segtext);
	if(segrodata.sect != nil)
		elfphload(&segrodata);
	elfphload(&segdata);

	/* Dynamic linking sections */
	if(!debug['d']) {	/* -d suppresses dynamic loader format */
		sh = elfshname(".dynsym");
		sh->type = SHT_DYNSYM;
		sh->flags = SHF_ALLOC;
		if(elf64)
			sh->entsize = ELF64SYMSIZE;
		else
			sh->entsize = ELF32SYMSIZE;
		sh->addralign = RegSize;
		sh->link = elfshname(".dynstr")->shnum;
		// sh->info = index of first non-local symbol (number of local symbols)
		shsym(sh, linklookup(ctxt, ".dynsym", 0));

		sh = elfshname(".dynstr");
		sh->type = SHT_STRTAB;
		sh->flags = SHF_ALLOC;
		sh->addralign = 1;
		shsym(sh, linklookup(ctxt, ".dynstr", 0));

		if(elfverneed) {
			sh = elfshname(".gnu.version");
			sh->type = SHT_GNU_VERSYM;
			sh->flags = SHF_ALLOC;
			sh->addralign = 2;
			sh->link = elfshname(".dynsym")->shnum;
			sh->entsize = 2;
			shsym(sh, linklookup(ctxt, ".gnu.version", 0));
			
			sh = elfshname(".gnu.version_r");
			sh->type = SHT_GNU_VERNEED;
			sh->flags = SHF_ALLOC;
			sh->addralign = RegSize;
			sh->info = elfverneed;
			sh->link = elfshname(".dynstr")->shnum;
			shsym(sh, linklookup(ctxt, ".gnu.version_r", 0));
		}

		switch(eh->machine) {
		case EM_X86_64:
			sh = elfshname(".rela.plt");
			sh->type = SHT_RELA;
			sh->flags = SHF_ALLOC;
			sh->entsize = ELF64RELASIZE;
			sh->addralign = RegSize;
			sh->link = elfshname(".dynsym")->shnum;
			sh->info = elfshname(".plt")->shnum;
			shsym(sh, linklookup(ctxt, ".rela.plt", 0));

			sh = elfshname(".rela");
			sh->type = SHT_RELA;
			sh->flags = SHF_ALLOC;
			sh->entsize = ELF64RELASIZE;
			sh->addralign = 8;
			sh->link = elfshname(".dynsym")->shnum;
			shsym(sh, linklookup(ctxt, ".rela", 0));
			break;
		
		default:
			sh = elfshname(".rel.plt");
			sh->type = SHT_REL;
			sh->flags = SHF_ALLOC;
			sh->entsize = ELF32RELSIZE;
			sh->link = elfshname(".dynsym")->shnum;
			shsym(sh, linklookup(ctxt, ".rel.plt", 0));

			sh = elfshname(".rel");
			sh->type = SHT_REL;
			sh->flags = SHF_ALLOC;
			sh->entsize = ELF32RELSIZE;
			sh->addralign = 4;
			sh->link = elfshname(".dynsym")->shnum;
			shsym(sh, linklookup(ctxt, ".rel", 0));
			break;
		}

		sh = elfshname(".plt");
		sh->type = SHT_PROGBITS;
		sh->flags = SHF_ALLOC+SHF_EXECINSTR;
		if(eh->machine == EM_X86_64)
			sh->entsize = 16;
		else
			sh->entsize = 4;
		sh->addralign = 4;
		shsym(sh, linklookup(ctxt, ".plt", 0));

		sh = elfshname(".got");
		sh->type = SHT_PROGBITS;
		sh->flags = SHF_ALLOC+SHF_WRITE;
		sh->entsize = RegSize;
		sh->addralign = RegSize;
		shsym(sh, linklookup(ctxt, ".got", 0));

		sh = elfshname(".got.plt");
		sh->type = SHT_PROGBITS;
		sh->flags = SHF_ALLOC+SHF_WRITE;
		sh->entsize = RegSize;
		sh->addralign = RegSize;
		shsym(sh, linklookup(ctxt, ".got.plt", 0));
		
		sh = elfshname(".hash");
		sh->type = SHT_HASH;
		sh->flags = SHF_ALLOC;
		sh->entsize = 4;
		sh->addralign = RegSize;
		sh->link = elfshname(".dynsym")->shnum;
		shsym(sh, linklookup(ctxt, ".hash", 0));

		/* sh and PT_DYNAMIC for .dynamic section */
		sh = elfshname(".dynamic");
		sh->type = SHT_DYNAMIC;
		sh->flags = SHF_ALLOC+SHF_WRITE;
		sh->entsize = 2*RegSize;
		sh->addralign = RegSize;
		sh->link = elfshname(".dynstr")->shnum;
		shsym(sh, linklookup(ctxt, ".dynamic", 0));
		ph = newElfPhdr();
		ph->type = PT_DYNAMIC;
		ph->flags = PF_R + PF_W;
		phsh(ph, sh);
		
		/*
		 * Thread-local storage segment (really just size).
		 */
		// Do not emit PT_TLS for OpenBSD since ld.so(1) does
		// not currently support it. This is handled
		// appropriately in runtime/cgo.
		if(ctxt->tlsoffset != 0 && HEADTYPE != Hopenbsd) {
			ph = newElfPhdr();
			ph->type = PT_TLS;
			ph->flags = PF_R;
			ph->memsz = -ctxt->tlsoffset;
			ph->align = RegSize;
		}
	}

	if(HEADTYPE == Hlinux) {
		ph = newElfPhdr();
		ph->type = PT_GNU_STACK;
		ph->flags = PF_W+PF_R;
		ph->align = RegSize;
		
		ph = newElfPhdr();
		ph->type = PT_PAX_FLAGS;
		ph->flags = 0x2a00; // mprotect, randexec, emutramp disabled
		ph->align = RegSize;
	}

elfobj:
	sh = elfshname(".shstrtab");
	sh->type = SHT_STRTAB;
	sh->addralign = 1;
	shsym(sh, linklookup(ctxt, ".shstrtab", 0));
	eh->shstrndx = sh->shnum;

	// put these sections early in the list
	if(!debug['s']) {
		elfshname(".symtab");
		elfshname(".strtab");
	}

	for(sect=segtext.sect; sect!=nil; sect=sect->next)
		elfshbits(sect);
	for(sect=segrodata.sect; sect!=nil; sect=sect->next)
		elfshbits(sect);
	for(sect=segdata.sect; sect!=nil; sect=sect->next)
		elfshbits(sect);

	if(linkmode == LinkExternal) {
		for(sect=segtext.sect; sect!=nil; sect=sect->next)
			elfshreloc(sect);
		for(sect=segrodata.sect; sect!=nil; sect=sect->next)
			elfshreloc(sect);
		for(sect=segdata.sect; sect!=nil; sect=sect->next)
			elfshreloc(sect);
		// add a .note.GNU-stack section to mark the stack as non-executable
		sh = elfshname(".note.GNU-stack");
		sh->type = SHT_PROGBITS;
		sh->addralign = 1;
		sh->flags = 0;
	}

	// generate .tbss section for dynamic internal linking (except for OpenBSD)
	// external linking generates .tbss in data.c
	if(linkmode == LinkInternal && !debug['d'] && HEADTYPE != Hopenbsd) {
		sh = elfshname(".tbss");
		sh->type = SHT_NOBITS;
		sh->addralign = RegSize;
		sh->size = -ctxt->tlsoffset;
		sh->flags = SHF_ALLOC | SHF_TLS | SHF_WRITE;
	}

	if(!debug['s']) {
		sh = elfshname(".symtab");
		sh->type = SHT_SYMTAB;
		sh->off = symo;
		sh->size = symsize;
		sh->addralign = RegSize;
		sh->entsize = 8+2*RegSize;
		sh->link = elfshname(".strtab")->shnum;
		sh->info = elfglobalsymndx;

		sh = elfshname(".strtab");
		sh->type = SHT_STRTAB;
		sh->off = symo+symsize;
		sh->size = elfstrsize;
		sh->addralign = 1;

		dwarfaddelfheaders();
	}

	/* Main header */
	eh->ident[EI_MAG0] = '\177';
	eh->ident[EI_MAG1] = 'E';
	eh->ident[EI_MAG2] = 'L';
	eh->ident[EI_MAG3] = 'F';
	if(HEADTYPE == Hfreebsd)
		eh->ident[EI_OSABI] = ELFOSABI_FREEBSD;
	else if(HEADTYPE == Hnetbsd)
		eh->ident[EI_OSABI] = ELFOSABI_NETBSD;
	else if(HEADTYPE == Hopenbsd)
		eh->ident[EI_OSABI] = ELFOSABI_OPENBSD;
	else if(HEADTYPE == Hdragonfly)
		eh->ident[EI_OSABI] = ELFOSABI_NONE;
	if(elf64)
		eh->ident[EI_CLASS] = ELFCLASS64;
	else
		eh->ident[EI_CLASS] = ELFCLASS32;
	eh->ident[EI_DATA] = ELFDATA2LSB;
	eh->ident[EI_VERSION] = EV_CURRENT;

	if(linkmode == LinkExternal)
		eh->type = ET_REL;
	else
		eh->type = ET_EXEC;

	if(linkmode != LinkExternal)
		eh->entry = entryvalue();

	eh->version = EV_CURRENT;

	if(pph != nil) {
		pph->filesz = eh->phnum * eh->phentsize;
		pph->memsz = pph->filesz;
	}

	cseek(0);
	a = 0;
	a += elfwritehdr();
	a += elfwritephdrs();
	a += elfwriteshdrs();
	if(!debug['d'])
		a += elfwriteinterp();
	if(linkmode != LinkExternal) {
		if(HEADTYPE == Hnetbsd)
			a += elfwritenetbsdsig();
		if(HEADTYPE == Hopenbsd)
			a += elfwriteopenbsdsig();
		if(buildinfolen > 0)
			a += elfwritebuildinfo();
	}
	if(a > ELFRESERVE)	
		diag("ELFRESERVE too small: %lld > %d", a, ELFRESERVE);
}
