root/src/cmd/ld/elf.c

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

DEFINITIONS

This source file includes following definitions.
  1. elfinit
  2. elf64phdr
  3. elf32phdr
  4. elf64shdr
  5. elf32shdr
  6. elfwriteshdrs
  7. elfsetstring
  8. elfwritephdrs
  9. newElfPhdr
  10. newElfShdr
  11. getElfEhdr
  12. elf64writehdr
  13. elf32writehdr
  14. elfwritehdr
  15. elfhash
  16. elfwritedynent
  17. elfwritedynentsym
  18. elfwritedynentsymsize
  19. elfinterp
  20. elfwriteinterp
  21. elfnote
  22. elfwritenotehdr
  23. elfnetbsdsig
  24. elfwritenetbsdsig
  25. elfopenbsdsig
  26. elfwriteopenbsdsig
  27. addbuildinfo
  28. elfbuildinfo
  29. elfwritebuildinfo
  30. addelflib
  31. elfdynhash
  32. elfphload
  33. elfshname
  34. elfshalloc
  35. elfshbits
  36. elfshreloc
  37. elfrelocsect
  38. elfemitreloc
  39. doelf
  40. shsym
  41. phsh
  42. asmbelfsetup
  43. asmbelf

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

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