root/src/cmd/5l/asm.c

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

DEFINITIONS

This source file includes following definitions.
  1. needlib
  2. braddoff
  3. adddynrela
  4. adddynrel
  5. elfreloc1
  6. elfsetupplt
  7. machoreloc1
  8. archreloc
  9. addpltreloc
  10. addpltsym
  11. addgotsyminternal
  12. addgotsym
  13. adddynsym
  14. adddynlib
  15. asmb
  16. wput
  17. hput
  18. lput
  19. nopstat
  20. rnd

// Inferno utils/5l/asm.c
// http://code.google.com/p/inferno-os/source/browse/utils/5l/asm.c
//
//      Copyright © 1994-1999 Lucent Technologies Inc.  All rights reserved.
//      Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
//      Portions Copyright © 1997-1999 Vita Nuova Limited
//      Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
//      Portions Copyright © 2004,2006 Bruce Ellis
//      Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
//      Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
//      Portions Copyright © 2009 The Go Authors.  All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Writing object files.

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


char linuxdynld[] = "/lib/ld-linux.so.3"; // 2 for OABI, 3 for EABI
char freebsddynld[] = "/usr/libexec/ld-elf.so.1";
char openbsddynld[] = "XXX";
char netbsddynld[] = "/libexec/ld.elf_so";
char dragonflydynld[] = "XXX";
char solarisdynld[] = "XXX";

static int
needlib(char *name)
{
        char *p;
        LSym *s;

        if(*name == '\0')
                return 0;

        /* reuse hash code in symbol table */
        p = smprint(".dynlib.%s", name);
        s = linklookup(ctxt, p, 0);
        free(p);
        if(s->type == 0) {
                s->type = 100;  // avoid SDATA, etc.
                return 1;
        }
        return 0;
}

int     nelfsym = 1;

static void     addpltsym(Link*, LSym*);
static void     addgotsym(Link*, LSym*);
static void     addgotsyminternal(Link*, LSym*);

// Preserve highest 8 bits of a, and do addition to lower 24-bit
// of a and b; used to adjust ARM branch intruction's target
static int32
braddoff(int32 a, int32 b)
{
        return (((uint32)a) & 0xff000000U) | (0x00ffffffU & (uint32)(a + b));
}

void
adddynrela(LSym *rel, LSym *s, Reloc *r)
{
        addaddrplus(ctxt, rel, s, r->off);
        adduint32(ctxt, rel, R_ARM_RELATIVE);
}

void
adddynrel(LSym *s, Reloc *r)
{
        LSym *targ, *rel;

        targ = r->sym;
        ctxt->cursym = s;

        switch(r->type) {
        default:
                if(r->type >= 256) {
                        diag("unexpected relocation type %d", r->type);
                        return;
                }
                break;

        // Handle relocations found in ELF object files.
        case 256 + R_ARM_PLT32:
                r->type = R_CALLARM;
                if(targ->type == SDYNIMPORT) {
                        addpltsym(ctxt, targ);
                        r->sym = linklookup(ctxt, ".plt", 0);
                        r->add = braddoff(r->add, targ->plt / 4);
                }
                return;

        case 256 + R_ARM_THM_PC22: // R_ARM_THM_CALL
                diag("R_ARM_THM_CALL, are you using -marm?");
                errorexit();
                return;

        case 256 + R_ARM_GOT32: // R_ARM_GOT_BREL
                if(targ->type != SDYNIMPORT) {
                        addgotsyminternal(ctxt, targ);
                } else {
                        addgotsym(ctxt, targ);
                }
                r->type = R_CONST;      // write r->add during relocsym
                r->sym = S;
                r->add += targ->got;
                return;

        case 256 + R_ARM_GOT_PREL: // GOT(S) + A - P
                if(targ->type != SDYNIMPORT) {
                        addgotsyminternal(ctxt, targ);
                } else {
                        addgotsym(ctxt, targ);
                }
                r->type = R_PCREL;
                r->sym = linklookup(ctxt, ".got", 0);
                r->add += targ->got + 4;
                return;

        case 256 + R_ARM_GOTOFF: // R_ARM_GOTOFF32
                r->type = R_GOTOFF;
                return;

        case 256 + R_ARM_GOTPC: // R_ARM_BASE_PREL
                r->type = R_PCREL;
                r->sym = linklookup(ctxt, ".got", 0);
                r->add += 4;
                return;

        case 256 + R_ARM_CALL:
                r->type = R_CALLARM;
                if(targ->type == SDYNIMPORT) {
                        addpltsym(ctxt, targ);
                        r->sym = linklookup(ctxt, ".plt", 0);
                        r->add = braddoff(r->add, targ->plt / 4);
                }
                return;

        case 256 + R_ARM_REL32: // R_ARM_REL32
                r->type = R_PCREL;
                r->add += 4;
                return;

        case 256 + R_ARM_ABS32: 
                if(targ->type == SDYNIMPORT)
                        diag("unexpected R_ARM_ABS32 relocation for dynamic symbol %s", targ->name);
                r->type = R_ADDR;
                return;

        case 256 + R_ARM_V4BX:
                // we can just ignore this, because we are targeting ARM V5+ anyway
                if(r->sym) {
                        // R_ARM_V4BX is ABS relocation, so this symbol is a dummy symbol, ignore it
                        r->sym->type = 0;
                }
                r->sym = S;
                return;

        case 256 + R_ARM_PC24:
        case 256 + R_ARM_JUMP24:
                r->type = R_CALLARM;
                if(targ->type == SDYNIMPORT) {
                        addpltsym(ctxt, targ);
                        r->sym = linklookup(ctxt, ".plt", 0);
                        r->add = braddoff(r->add, targ->plt / 4);
                }
                return;
        }
        
        // Handle references to ELF symbols from our own object files.
        if(targ->type != SDYNIMPORT)
                return;

        switch(r->type) {
        case R_CALLARM:
                addpltsym(ctxt, targ);
                r->sym = linklookup(ctxt, ".plt", 0);
                r->add = targ->plt;
                return;
        
        case R_ADDR:
                if(s->type != SDATA)
                        break;
                if(iself) {
                        adddynsym(ctxt, targ);
                        rel = linklookup(ctxt, ".rel", 0);
                        addaddrplus(ctxt, rel, s, r->off);
                        adduint32(ctxt, rel, ELF32_R_INFO(targ->dynid, R_ARM_GLOB_DAT)); // we need a S + A dynmic reloc
                        r->type = R_CONST;      // write r->add during relocsym
                        r->sym = S;
                        return;
                }
                break;
        }

        ctxt->cursym = s;
        diag("unsupported relocation for dynamic symbol %s (type=%d stype=%d)", targ->name, r->type, targ->type);
}

int
elfreloc1(Reloc *r, vlong sectoff)
{
        int32 elfsym;
        
        LPUT(sectoff);

        elfsym = r->xsym->elfsym;
        switch(r->type) {
        default:
                return -1;

        case R_ADDR:
                if(r->siz == 4)
                        LPUT(R_ARM_ABS32 | elfsym<<8);
                else
                        return -1;
                break;

        case R_PCREL:
                if(r->siz == 4)
                        LPUT(R_ARM_REL32 | elfsym<<8);
                else
                        return -1;
                break;

        case R_CALLARM:
                if(r->siz == 4) {
                        if((r->add & 0xff000000) == 0xeb000000) // BL
                                LPUT(R_ARM_CALL | elfsym<<8);
                        else
                                LPUT(R_ARM_JUMP24 | elfsym<<8);
                } else
                        return -1;
                break;

        case R_TLS:
                if(r->siz == 4) {
                        if(flag_shared)
                                LPUT(R_ARM_TLS_IE32 | elfsym<<8);
                        else
                                LPUT(R_ARM_TLS_LE32 | elfsym<<8);
                } else
                        return -1;
                break;
        }

        return 0;
}

void
elfsetupplt(void)
{
        LSym *plt, *got;
        
        plt = linklookup(ctxt, ".plt", 0);
        got = linklookup(ctxt, ".got.plt", 0);
        if(plt->size == 0) {
                // str lr, [sp, #-4]!
                adduint32(ctxt, plt, 0xe52de004);
                // ldr lr, [pc, #4]
                adduint32(ctxt, plt, 0xe59fe004);
                // add lr, pc, lr
                adduint32(ctxt, plt, 0xe08fe00e);
                // ldr pc, [lr, #8]!
                adduint32(ctxt, plt, 0xe5bef008);
                // .word &GLOBAL_OFFSET_TABLE[0] - .
                addpcrelplus(ctxt, plt, got, 4);

                // the first .plt entry requires 3 .plt.got entries
                adduint32(ctxt, got, 0);
                adduint32(ctxt, got, 0);
                adduint32(ctxt, got, 0);
        }
}

int
machoreloc1(Reloc *r, vlong sectoff)
{
        USED(r);
        USED(sectoff);

        return -1;
}


int
archreloc(Reloc *r, LSym *s, vlong *val)
{
        LSym *rs;

        if(linkmode == LinkExternal) {
                switch(r->type) {
                case R_CALLARM:
                        r->done = 0;

                        // set up addend for eventual relocation via outer symbol.
                        rs = r->sym;
                        r->xadd = r->add;
                        if(r->xadd & 0x800000)
                                r->xadd |= ~0xffffff;
                        r->xadd *= 4;
                        while(rs->outer != nil) {
                                r->xadd += symaddr(rs) - symaddr(rs->outer);
                                rs = rs->outer;
                        }

                        if(rs->type != SHOSTOBJ && rs->sect == nil)
                                diag("missing section for %s", rs->name);
                        r->xsym = rs;

                        *val = braddoff((0xff000000U & (uint32)r->add), 
                                                        (0xffffff & (uint32)(r->xadd / 4)));
                        return 0;
                }
                return -1;
        }
        switch(r->type) {
        case R_CONST:
                *val = r->add;
                return 0;
        case R_GOTOFF:
                *val = symaddr(r->sym) + r->add - symaddr(linklookup(ctxt, ".got", 0));
                return 0;
        // The following three arch specific relocations are only for generation of 
        // Linux/ARM ELF's PLT entry (3 assembler instruction)
        case R_PLT0: // add ip, pc, #0xXX00000
                if (symaddr(linklookup(ctxt, ".got.plt", 0)) < symaddr(linklookup(ctxt, ".plt", 0)))
                        diag(".got.plt should be placed after .plt section.");
                *val = 0xe28fc600U +
                        (0xff & ((uint32)(symaddr(r->sym) - (symaddr(linklookup(ctxt, ".plt", 0)) + r->off) + r->add) >> 20));
                return 0;
        case R_PLT1: // add ip, ip, #0xYY000
                *val = 0xe28cca00U +
                        (0xff & ((uint32)(symaddr(r->sym) - (symaddr(linklookup(ctxt, ".plt", 0)) + r->off) + r->add + 4) >> 12));
                return 0;
        case R_PLT2: // ldr pc, [ip, #0xZZZ]!
                *val = 0xe5bcf000U +
                        (0xfff & (uint32)(symaddr(r->sym) - (symaddr(linklookup(ctxt, ".plt", 0)) + r->off) + r->add + 8));
                return 0;
        case R_CALLARM: // bl XXXXXX or b YYYYYY
                *val = braddoff((0xff000000U & (uint32)r->add), 
                                (0xffffff & (uint32)
                                   ((symaddr(r->sym) + ((uint32)r->add) * 4 - (s->value + r->off)) / 4)));
                return 0;
        }
        return -1;
}

static Reloc *
addpltreloc(Link *ctxt, LSym *plt, LSym *got, LSym *sym, int typ)
{
        Reloc *r;

        r = addrel(plt);
        r->sym = got;
        r->off = plt->size;
        r->siz = 4;
        r->type = typ;
        r->add = sym->got - 8;

        plt->reachable = 1;
        plt->size += 4;
        symgrow(ctxt, plt, plt->size);

        return r;
}

static void
addpltsym(Link *ctxt, LSym *s)
{
        LSym *plt, *got, *rel;
        
        if(s->plt >= 0)
                return;

        adddynsym(ctxt, s);
        
        if(iself) {
                plt = linklookup(ctxt, ".plt", 0);
                got = linklookup(ctxt, ".got.plt", 0);
                rel = linklookup(ctxt, ".rel.plt", 0);
                if(plt->size == 0)
                        elfsetupplt();
                
                // .got entry
                s->got = got->size;
                // In theory, all GOT should point to the first PLT entry,
                // Linux/ARM's dynamic linker will do that for us, but FreeBSD/ARM's
                // dynamic linker won't, so we'd better do it ourselves.
                addaddrplus(ctxt, got, plt, 0);

                // .plt entry, this depends on the .got entry
                s->plt = plt->size;
                addpltreloc(ctxt, plt, got, s, R_PLT0); // add lr, pc, #0xXX00000
                addpltreloc(ctxt, plt, got, s, R_PLT1); // add lr, lr, #0xYY000
                addpltreloc(ctxt, plt, got, s, R_PLT2); // ldr pc, [lr, #0xZZZ]!

                // rel
                addaddrplus(ctxt, rel, got, s->got);
                adduint32(ctxt, rel, ELF32_R_INFO(s->dynid, R_ARM_JUMP_SLOT));
        } else {
                diag("addpltsym: unsupported binary format");
        }
}

static void
addgotsyminternal(Link *ctxt, LSym *s)
{
        LSym *got;
        
        if(s->got >= 0)
                return;

        got = linklookup(ctxt, ".got", 0);
        s->got = got->size;

        addaddrplus(ctxt, got, s, 0);

        if(iself) {
                ;
        } else {
                diag("addgotsyminternal: unsupported binary format");
        }
}

static void
addgotsym(Link *ctxt, LSym *s)
{
        LSym *got, *rel;
        
        if(s->got >= 0)
                return;
        
        adddynsym(ctxt, s);
        got = linklookup(ctxt, ".got", 0);
        s->got = got->size;
        adduint32(ctxt, got, 0);
        
        if(iself) {
                rel = linklookup(ctxt, ".rel", 0);
                addaddrplus(ctxt, rel, got, s->got);
                adduint32(ctxt, rel, ELF32_R_INFO(s->dynid, R_ARM_GLOB_DAT));
        } else {
                diag("addgotsym: unsupported binary format");
        }
}

void
adddynsym(Link *ctxt, LSym *s)
{
        LSym *d;
        int t;
        char *name;

        if(s->dynid >= 0)
                return;

        if(iself) {
                s->dynid = nelfsym++;

                d = linklookup(ctxt, ".dynsym", 0);

                /* name */
                name = s->extname;
                adduint32(ctxt, d, addstring(linklookup(ctxt, ".dynstr", 0), name));

                /* value */
                if(s->type == SDYNIMPORT)
                        adduint32(ctxt, d, 0);
                else
                        addaddr(ctxt, d, s);

                /* size */
                adduint32(ctxt, d, 0);

                /* type */
                t = STB_GLOBAL << 4;
                if((s->cgoexport & CgoExportDynamic) && (s->type&SMASK) == STEXT)
                        t |= STT_FUNC;
                else
                        t |= STT_OBJECT;
                adduint8(ctxt, d, t);
                adduint8(ctxt, d, 0);

                /* shndx */
                if(s->type == SDYNIMPORT)
                        adduint16(ctxt, d, SHN_UNDEF);
                else {
                        switch(s->type) {
                        default:
                        case STEXT:
                                t = 11;
                                break;
                        case SRODATA:
                                t = 12;
                                break;
                        case SDATA:
                                t = 13;
                                break;
                        case SBSS:
                                t = 14;
                                break;
                        }
                        adduint16(ctxt, d, t);
                }
        } else {
                diag("adddynsym: unsupported binary format");
        }
}

void
adddynlib(char *lib)
{
        LSym *s;
        
        if(!needlib(lib))
                return;
        
        if(iself) {
                s = linklookup(ctxt, ".dynstr", 0);
                if(s->size == 0)
                        addstring(s, "");
                elfwritedynent(linklookup(ctxt, ".dynamic", 0), DT_NEEDED, addstring(s, lib));
        } else {
                diag("adddynlib: unsupported binary format");
        }
}

void
asmb(void)
{
        uint32 symo;
        Section *sect;
        LSym *sym;
        int i;

        if(debug['v'])
                Bprint(&bso, "%5.2f asmb\n", cputime());
        Bflush(&bso);

        if(iself)
                asmbelfsetup();

        sect = segtext.sect;
        cseek(sect->vaddr - segtext.vaddr + segtext.fileoff);
        codeblk(sect->vaddr, sect->len);
        for(sect = sect->next; sect != nil; sect = sect->next) {
                cseek(sect->vaddr - segtext.vaddr + segtext.fileoff);
                datblk(sect->vaddr, sect->len);
        }

        if(segrodata.filelen > 0) {
                if(debug['v'])
                        Bprint(&bso, "%5.2f rodatblk\n", cputime());
                Bflush(&bso);

                cseek(segrodata.fileoff);
                datblk(segrodata.vaddr, segrodata.filelen);
        }

        if(debug['v'])
                Bprint(&bso, "%5.2f datblk\n", cputime());
        Bflush(&bso);

        cseek(segdata.fileoff);
        datblk(segdata.vaddr, segdata.filelen);

        /* output symbol table */
        symsize = 0;
        lcsize = 0;
        symo = 0;
        if(!debug['s']) {
                // TODO: rationalize
                if(debug['v'])
                        Bprint(&bso, "%5.2f sym\n", cputime());
                Bflush(&bso);
                switch(HEADTYPE) {
                default:
                        if(iself)
                                goto ElfSym;
                case Hplan9:
                        symo = HEADR+segtext.len+segdata.filelen;
                        break;
                ElfSym:
                        symo = rnd(HEADR+segtext.filelen, INITRND)+rnd(HEADR+segrodata.filelen, INITRND)+segdata.filelen;
                        symo = rnd(symo, INITRND);
                        break;
                }
                cseek(symo);
                switch(HEADTYPE) {
                default:
                        if(iself) {
                                if(debug['v'])
                                        Bprint(&bso, "%5.2f elfsym\n", cputime());
                                asmelfsym();
                                cflush();
                                cwrite(elfstrdat, elfstrsize);
        
                                if(debug['v'])
                                        Bprint(&bso, "%5.2f dwarf\n", cputime());
                                dwarfemitdebugsections();
                                
                                if(linkmode == LinkExternal)
                                        elfemitreloc();
                        }
                        break;
                case Hplan9:
                        asmplan9sym();
                        cflush();

                        sym = linklookup(ctxt, "pclntab", 0);
                        if(sym != nil) {
                                lcsize = sym->np;
                                for(i=0; i < lcsize; i++)
                                        cput(sym->p[i]);

                                cflush();
                        }
                        break;
                }
        }

        ctxt->cursym = nil;
        if(debug['v'])
                Bprint(&bso, "%5.2f header\n", cputime());
        Bflush(&bso);
        cseek(0L);
        switch(HEADTYPE) {
        default:
        case Hplan9:    /* plan 9 */
                lput(0x647);                    /* magic */
                lput(segtext.filelen);                  /* sizes */
                lput(segdata.filelen);
                lput(segdata.len - segdata.filelen);
                lput(symsize);                  /* nsyms */
                lput(entryvalue());             /* va of entry */
                lput(0L);
                lput(lcsize);
                break;
        case Hlinux:
        case Hfreebsd:
        case Hnetbsd:
        case Hopenbsd:
                asmbelf(symo);
                break;
        }
        cflush();
        if(debug['c']){
                print("textsize=%ulld\n", segtext.filelen);
                print("datsize=%ulld\n", segdata.filelen);
                print("bsssize=%ulld\n", segdata.len - segdata.filelen);
                print("symsize=%d\n", symsize);
                print("lcsize=%d\n", lcsize);
                print("total=%lld\n", segtext.filelen+segdata.len+symsize+lcsize);
        }
}

/*
void
cput(int32 c)
{
        *cbp++ = c;
        if(--cbc <= 0)
                cflush();
}
*/

void
wput(int32 l)
{

        cbp[0] = l>>8;
        cbp[1] = l;
        cbp += 2;
        cbc -= 2;
        if(cbc <= 0)
                cflush();
}


void
hput(int32 l)
{

        cbp[0] = l>>8;
        cbp[1] = l;
        cbp += 2;
        cbc -= 2;
        if(cbc <= 0)
                cflush();
}

void
lput(int32 l)
{

        cbp[0] = l>>24;
        cbp[1] = l>>16;
        cbp[2] = l>>8;
        cbp[3] = l;
        cbp += 4;
        cbc -= 4;
        if(cbc <= 0)
                cflush();
}

void
nopstat(char *f, Count *c)
{
        if(c->outof)
        Bprint(&bso, "%s delay %d/%d (%.2f)\n", f,
                c->outof - c->count, c->outof,
                (double)(c->outof - c->count)/c->outof);
}

int32
rnd(int32 v, int32 r)
{
        int32 c;

        if(r <= 0)
                return v;
        v += r - 1;
        c = v % r;
        if(c < 0)
                c += r;
        v -= c;
        return v;
}

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