blob: 61306bb7ca71989cf1993f946935bc22c0a63b5e [file] [log] [blame]
// 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);
}