root/src/cmd/ld/macho.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. machoinit
  2. getMachoHdr
  3. newMachoLoad
  4. newMachoSeg
  5. newMachoSect
  6. machowrite
  7. domacho
  8. machoadddynlib
  9. machoshbits
  10. asmbmacho
  11. symkind
  12. addsym
  13. scmp
  14. machogenasmsym
  15. machosymorder
  16. machosymtab
  17. machodysymtab
  18. domacholink
  19. machorelocsect
  20. machoemitreloc

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

/* [<][>][^][v][top][bottom][index][help] */