// 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; insect); 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; jnsect; 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; itype); LPUT(4*(l->ndata+2)); for(j=0; jndata; 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<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; idata[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; ireachable = 1; machogenasmsym(addsym); sortsym = mal(nsortsym * sizeof sortsym[0]); nsortsym = 0; machogenasmsym(addsym); qsort(sortsym, nsortsym, sizeof sortsym[0], scmp); for(i=0; idynid = 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; isize); // 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); }