/* $VER: vlink linker.c V0.6a (19.12.98)
 *
 * This file is part of vlink, a portable linker for multiple
 * object formats.
 * Copyright (c) 1997-99  Frank Wille
 *
 * vlink is freeware and part of the portable and retargetable ANSI C
 * compiler vbcc, copyright (c) 1995-99 by Volker Barthelmann.
 * vlink may be freely redistributed as long as no modifications are
 * made and nothing is charged for it. Non-commercial usage is allowed
 * without any restrictions.
 * EVERY PRODUCT OR PROGRAM DERIVED DIRECTLY FROM MY SOURCE MAY NOT BE
 * SOLD COMMERCIALLY WITHOUT PERMISSION FROM THE AUTHOR.
 *
 *
 * v0.6a (19.12.98) phx
 *       Endianess of linking process will be determined by the type
 *       of the first object.
 *       Support for little endian object file formats. All read/write
 *       operation on the object data is done in current endianess.
 * v0.6  (24.10.98) phx
 *       Take base register section offset from FFFuncs.baseoff.
 * v0.5e (05.10.98) phx
 *       Linking a ID_LIBARCH object requires its PriPointers to be
 *       added to the global list: add_priptrs().
 *       Call make_priptr_objects() after processing all objects in
 *       linker_resolve has finished. If there are entries in the Pri-
 *       Pointers list, then create some artificial sections (usually
 *       constructor and destructor lists).
 *       insertnode() <-> insertbehind().
 * v0.5d (22.08.98) phx
 *       Faster memory allocation can be activated by #define FASTALLOC.
 *       Directories are only scanned if needed (AmigaOS filesystem is
 *       too slow).
 * v0.5c (08.07.98) phx
 *       ID_ARTIFICIAL objects, i.e. objects created by the linker
 *       are supported.
 * v0.5  (27.06.98) phx
 *       Target-specific linker symbol support.
 * v0.4  (05.06.98) phx
 *       Rewrote linker_join(). Now it can be guaranteed, that first
 *       all code sections will be linked, then the data and finally
 *       the bss sections.
 *       Rewrote find_lnksect(). A lot more factors are now involved
 *       in the decision if two sections can be coalesced: relative
 *       references between the two, both are addressed base-relative,
 *       or target-specific factors.
 *       New functions for detection of relative references:
 *       addrelref(), copyrelrefs(), checkrelrefs().
 *       assign_common() finds a bss section for common symbols.
 * v0.3b (02.05.98) phx
 *       There were still infinite loops, if an object comes without
 *       any section.
 * v0.3  (16.04.98) phx
 *       Resolving ADDR16_HA/HI/LO references to a relocatable symbol
 *       didn't work (2 bytes of the next instructions were overwritten).
 *       Fixed print_function_name(). Didn't work, if symbols had
 *       a known size. Additionally, it can differentiate between
 *       'no type', 'function' and 'object' now.
 *       Avoid infinite loops for object units without section in
 *       linker_join().
 * v0.2  (07.03.98) phx
 *       Base relative relocations must not be resolved, if the output
 *       file is a relocatable object again. Base relative xrefs were
 *       even completely corrupted and overwrote the following
 *       instruction.
 *       Library units are linked immediately and are no longer always
 *       the last units in an output file.
 *       Base relative relocation didn't work for baseoff=0x8000.
 * v0.1  (27.02.98) phx
 *       First version that seems to link AmigaOS ADOS and EHF
 *       objects and libraries. Many common features, like linking
 *       sections together which have relative references, are
 *       still missing. Also, PowerPC-ELF32 support is about to come.
 * v0.0  (02.12.97) phx
 *       File created.
 */


#define LINKER_C
#include "vlink.h"


static char namebuf[FNAMEBUFSIZE];

static const char *filetypes[] = {
  "unknown",
  "object",
  "executable",NULL,
  "shared object",NULL,NULL,NULL,
  "library"
};

static const char *sec_names[] = {
  "undefined","code","data","bss",NULL
};

void linker_init(struct GlobalVars *);
void linker_load(struct GlobalVars *);
void linker_relrefs(struct GlobalVars *);
void linker_resolve(struct GlobalVars *);
void linker_join(struct GlobalVars *);
void linker_copy(struct GlobalVars *);
void linker_write(struct GlobalVars *);
void linker_cleanup(struct GlobalVars *);
char *getobjname(struct ObjectUnit *);
void print_function_name(struct Section *,unsigned long);
bool trace_sym_access(struct GlobalVars *,char *);

static void resolve_reloc(struct GlobalVars *,struct LinkedSection *,
                          struct Section *,struct Reloc *);
static void resolve_xref(struct GlobalVars *,struct LinkedSection *,
                         struct Section *,struct XReference *);
static void newreloc(struct LinkedSection *,struct LinkedSection *,
                     unsigned long,int32,uint8);
static void assign_common(struct GlobalVars *);
static char *maplibrary(struct GlobalVars *,struct InputFile *);
static char *searchlib(struct GlobalVars *,char *,int);
static char *scan_directory(char *,char *,int);
static void addrelref(struct RelRef **,struct Section *);
static void copyrelrefs(struct RelRef **,struct Section *);
static bool checkrelrefs(struct RelRef *,struct Section *);
static struct LinkedSection *create_lnksect(struct GlobalVars *,char *,
                                            uint8,uint8,uint8,uint8);
static struct LinkedSection *find_lnksect(struct GlobalVars *,
                                          struct Section *);
static void combine_sections(struct LinkedSection *,struct Section *,uint8);
static void print_symbol(FILE *,struct Symbol *);
static char *protstring(uint8);



void linker_init(struct GlobalVars *gv)
{
  initlist(&gv->linkfiles);
  initlist(&gv->selobjects);
  initlist(&gv->libobjects);
  initlist(&gv->sharedobjects);
  gv->symbols = alloc_hashtable(SYMHTABSIZE);
  initlist(&gv->pripointers);
  gv->big_endian = -1;
}


void linker_load(struct GlobalVars *gv)
/* load all objects and libraries into memory, identify their */
/* format, then read all symbols and convert into internal format */
{
  struct InputFile *ifn = (struct InputFile *)gv->inputlist.first;
  struct InputFile *nextifn;
  struct LinkFile *lf;
  uint8 *objptr;
  char *objname;
  unsigned long objlen;
  int i,ff;

  if (gv->trace_file)
    fprintf(gv->trace_file,"\nLoading files:\n\n");

  while (nextifn = (struct InputFile *)ifn->n.next) {
    if (ifn->lib) {
      if (!(objptr = (uint8 *)maplibrary(gv,ifn))) {
        sprintf(namebuf,"-l%s",ifn->name);
        error(8,namebuf);  /* cannot open -lxxx */
      }
    }
    else {
      if (objptr = (uint8 *)mapfile(ifn->name))
        strcpy(namebuf,ifn->name);
      else
        error(8,ifn->name);  /* cannot open xxx */
    }
    objlen = *(size_t *)(objptr - sizeof(size_t));
    objname = base_name(namebuf);

    /* determine the object's file format */
    for (i=0,ff=ID_UNKNOWN; fff[i]; i++) {
      if ((ff = (fff[i]->identify)(objname,objptr,objlen)) != ID_UNKNOWN)
        break;
    }
    if (ff == ID_UNKNOWN)
      error(11,objname);  /* File format not recognized */

    /* use endianess of first object read */
    if (gv->big_endian < 0)
      gv->big_endian = fff[i]->big_endian;
    else if (gv->big_endian != fff[i]->big_endian)
      error(61,objname);  /* endianess differs from previous objects */

    /* create new link file node */
    lf = (struct LinkFile *)alloc(sizeof(struct LinkFile));
    lf->pathname = allocstring(namebuf);
    lf->filename = base_name(lf->pathname);
    lf->data = objptr;
    lf->length = objlen;
    lf->format = (uint8)i;
    lf->type = (uint8)ff;
    if (gv->trace_file)
      fprintf(gv->trace_file,"%s (%s %s)\n",namebuf,fff[i]->tname,
                                            filetypes[ff]);

    /* read the file and convert into internal format */
    fff[i]->readconv(gv,lf);
    addtail(&gv->linkfiles,&lf->n);  /* not really needed */

    ifn = nextifn;  /* next input file */
  }
}


void linker_resolve(struct GlobalVars *gv)
/* Resolve all symbol references and pull the required objects into */
/* the gv->selobjects list. */
{
  bool priptrs_made = FALSE;
  struct ObjectUnit *obj = (struct ObjectUnit *)gv->selobjects.first;
  static const char *pulltxt = " needed due to ";

  if (gv->trace_file)
    fprintf(gv->trace_file,"\nDigesting symbol information:\n\n");

  if (obj->n.next == NULL)
    return;  /* no objects in list */

  do {
    struct Section *sec = (struct Section *)obj->sections.first;
    struct Section *nextsec;
    struct XReference *xref,*nextxref;
    struct Symbol *xdef;
    struct ObjectUnit *pull_unit;

    /* all sections of this object are checked for external references */
    while (nextsec = (struct Section *)sec->n.next) {
      xref = (struct XReference *)sec->xrefs.first;
      while (nextxref = (struct XReference *)xref->n.next) {

        if (!(xdef = findsymbol(gv,xref->name))) {
          if (!(xdef = fff[gv->dest_format]->lnksymbol(gv,sec,xref))) {
            /* undefined reference */
            if (!gv->dest_object) {
              print_function_name(sec,xref->offset);
              error(21,getobjname(sec->obj),sec->name,xref->offset,
                    xref->name);
            }
            xref = nextxref;
            continue;
          }
        }

        /* reference resolved */
        if (xdef->relsect) {
          pull_unit = xdef->relsect->obj;

          switch (pull_unit->lnkfile->type) {
            case ID_ARTIFICIAL:
              /* linker-generated object - insert it behind the curr. obj. */
              if (gv->map_file) {
                fprintf(gv->map_file,
                        "artificial object (%s) created due to %s\n",
                        pull_unit->objname,xref->name);
                /* "artificial object (name.o) created due to @__name" */
              }
              insertbehind(&obj->n,&pull_unit->n);
              pull_unit->flags |= OUF_LINKED;
              /* turn into a normal object */
              pull_unit->lnkfile->type = ID_OBJECT;
              xref->symbol = xdef;  /* unknown reference was resolved */
              break;

            case ID_SHAREDOBJ:
              if (!(pull_unit->flags & OUF_LINKED)) {
                if (gv->map_file) {
                  fprintf(gv->map_file,"%s%s%s\n",
                          pull_unit->lnkfile->pathname,pulltxt,xref->name);
                  /* Example: "/usr/lib/libc.so.12.0 needed due to _atexit" */
                }
                insertbehind(&obj->n,remnode(&pull_unit->n));
                add_priptrs(gv,pull_unit);
                pull_unit->flags |= OUF_LINKED;
              }
              break;

            case ID_LIBARCH:
              if (!(pull_unit->flags & OUF_LINKED)) {
                if (gv->map_file) {
                  fprintf(gv->map_file,"%s (%s)%s%s\n",
                          pull_unit->lnkfile->pathname,pull_unit->objname,
                          pulltxt,xref->name);
                  /* "/usr/lib/libc.a (atexit.o) needed due to _atexit" */
                }
                insertbehind(&obj->n,remnode(&pull_unit->n));
                add_priptrs(gv,pull_unit);
                pull_unit->flags |= OUF_LINKED;
              }
              /* fall through */

            default:
              xref->symbol = xdef;  /* unknown reference was resolved */
              break;
          }
        }
        else
          xref->symbol = xdef;

        xref = nextxref;
      }

      sec = nextsec;  /* resolve ext. references of next section */
    }

    if (obj->n.next->next == NULL && !priptrs_made) {
      make_priptr_objects(gv);  /* PriPointer objects always at last */
      priptrs_made = TRUE;
    }
    obj = (struct ObjectUnit *)obj->n.next;
  }
  while (obj->n.next);
}


void linker_relrefs(struct GlobalVars *gv)
/* All relocations and unresolved xrefs with a relative reference */
/* to other sections are collected. A second task is to detect */
/* base-relative references and set the approriate flag of the */
/* referenced section. */
{
  struct ObjectUnit *obj = (struct ObjectUnit *)gv->selobjects.first;
  struct ObjectUnit *nextobj;

  /*@@@ find a better place for this: */
  assign_common(gv);  /* find a bss section for common symbols */

  while (nextobj = (struct ObjectUnit *)obj->n.next) {
    struct Section *sec = (struct Section *)obj->sections.first;
    struct Section *nextsec;

    while (nextsec = (struct Section *)sec->n.next) {
      struct Symbol *xdef;
      struct XReference *xref = (struct XReference *)sec->xrefs.first;
      struct XReference *nextxref;
      struct Reloc *reloc = (struct Reloc *)sec->relocs.first;
      struct Reloc *nextreloc;
      struct RelRef **rr = &sec->relrefs;

      *rr = NULL;
      while (nextxref = (struct XReference *)xref->n.next) {
        if (xdef = xref->symbol) {
          if (xdef->relsect!=NULL && xdef->relsect!=sec &&
              (xdef->type==SYM_RELOC || xdef->type==SYM_COMMON)) {

            if ((xref->type & R_MASK) == R_REL)
              /* relative reference to different section */
              addrelref(rr,xdef->relsect);

            else if ((xref->type & R_MASK) == R_BASEREL)
              /* other section is accessed base relative from this one */
              xdef->relsect->flags |= SF_SMALLDATA;
          }
        }
        xref = nextxref;
      }

      while (nextreloc = (struct Reloc *)reloc->n.next) {
        if (reloc->relocsect.ptr != sec) {

          if ((reloc->type & R_MASK) == R_REL)
            /* relative reference to different section */
            addrelref(rr,reloc->relocsect.ptr);

          else if ((reloc->type & R_MASK) == R_BASEREL)
            /* other section is accessed base relative from this one */
            reloc->relocsect.ptr->flags |= SF_SMALLDATA;
        } 
       reloc = nextreloc;
      }

      sec = nextsec;
    }
    obj = nextobj;
  }
}


void linker_join(struct GlobalVars *gv)
/* Join the sections with same name and type. Calculate their */
/* virtual address and size. */
{
  struct ObjectUnit *obj,*nextobj;
  uint8 stype;

  if (gv->trace_file)
    fprintf(gv->trace_file,"Joining selected sections:\n\n");

  for (stype=0; stype<=ST_LAST; stype++) {
    /* join sections, beginning with ST_CODE, then ST_DATA and ST_UDATA */
    obj = (struct ObjectUnit *)gv->selobjects.first;

    while (nextobj = (struct ObjectUnit *)obj->n.next) {
      struct Section *sec = (struct Section *)obj->sections.first;
      struct Section *nextsec;

      if (obj->lnkfile->type != ID_SHAREDOBJ) {
        while (nextsec = (struct Section *)sec->n.next) {
          struct LinkedSection *ls;

          if (sec->type == stype) {
            unsigned long abytes;

            if (!(ls = find_lnksect(gv,sec))) {
              ls = create_lnksect(gv,sec->name,sec->type,sec->flags,
                                  sec->protection,sec->alignment);
            }
            abytes = align(ls->base+ls->size,sec->alignment);
            sec->lnksec = ls;
            sec->offset = ls->size + abytes;
            sec->va = ls->base + sec->offset;
            ls->size += sec->size + abytes;
            if (!(sec->flags & SF_UNINITIALIZED))
              ls->filesize += sec->size + abytes;
            /* move into LinkedSection */
            copyrelrefs(&ls->relrefs,sec);
            addtail(&ls->sections,remnode(&sec->n));
          }
          sec = nextsec;
        }
      }
      obj = nextobj;
    }
  }

  if (gv->map_file) {
    /* print file names and the new addresses of their sections */

    fprintf(gv->map_file,"\nFiles:\n\n");
    obj = (struct ObjectUnit *)gv->selobjects.first;

    while (nextobj = (struct ObjectUnit *)obj->n.next) {
      struct LinkFile *lfile = obj->lnkfile;
      struct LinkedSection *ls = (struct LinkedSection *)gv->lnksec.first;
      struct LinkedSection *nextls;
      char sep = ' ';

      if (lfile->type == ID_LIBARCH)
        fprintf(gv->map_file,"  %s (%s)",lfile->pathname,obj->objname);
      else
        fprintf(gv->map_file,"  %s",obj->objname);

      if (lfile->type != ID_SHAREDOBJ) {
        while (nextls = (struct LinkedSection *)ls->n.next) {
          struct Section *sec = (struct Section *)ls->sections.first;
          struct Section *nextsec;

          while (nextsec = (struct Section *)sec->n.next) {
            if (sec->obj == obj) {  /* section came from this object? */
              fprintf(gv->map_file,"%c %s %lx(%lx)",sep,ls->name,
                      sec->va,sec->size);
              sep = ',';
            }
            sec = nextsec;
          }
          ls = nextls;
        }
      }

      if (sep == ',')  /* any sections listed? */
        fprintf(gv->map_file," hex\n");
      else
        fprintf(gv->map_file,"  symbols only\n"); /* empty or shared obj. */

      obj = nextobj;
    }
  }
}


void linker_copy(struct GlobalVars *gv)
/* Merge contents of linked sections, fix symbol offsets and */
/* allocate common symbol data. */
{
  struct LinkedSection *ls = (struct LinkedSection *)gv->lnksec.first;
  struct LinkedSection *nextls;
  struct Section *sec,*nextsec;

  if (gv->trace_file)
    fprintf(gv->trace_file,"\n");
  if (gv->map_file)
    fprintf(gv->map_file,"\n");
  while (nextls = (struct LinkedSection *)ls->n.next) {
    struct Section *sec = (struct Section *)ls->sections.first;
    struct Section *nextsec;

    if (gv->trace_file)
      fprintf(gv->trace_file,"Copying %s:\n\n",ls->name);
    if (gv->map_file)
      fprintf(gv->map_file,"\nSymbols of %s:\n\n",ls->name);
    if (!(ls->flags&SF_UNINITIALIZED))
      ls->data = alloczero(ls->size);

    while (nextsec = (struct Section *)sec->n.next) {
      struct Symbol *sym;
      int i;
      unsigned long n;

      if (ls->data && sec->data) {
        /* copy section contents */
        memcpy(ls->data+sec->offset,sec->data,sec->size);
      }

      /* find section symbols and fix their offsets */
      for (i=0; i<OBJSYMHTABSIZE; i++) {
        sym = sec->obj->objsyms[i];
        while (sym) {
          if (sym->relsect == sec) {  /* symbol defined in this section */
            switch (sym->type) {
              case SYM_RELOC:
                sym->value += sec->offset;  /* fix offset for reloc symbols */
                break;
              case SYM_COMMON:
                if (sec->obj->lnkfile->type != ID_SHAREDOBJ) {
                  /* allocate common symbol and transform into SYM_RELOC */
                  if (!(sec->type&SF_UNINITIALIZED))
                    ierror("linker_relocate(): Common %s was defined in "
                           "%s of %s, which is not a bss-type section",
                           sym->name,sec->name,sec->obj->objname);
                  n = align(ls->base+ls->size,sym->value);
                  sym->value = ls->size + n;
                  sym->type = SYM_RELOC;
                  ls->size += n + sym->size;
                  if (gv->map_file)
                    fprintf(gv->map_file,
                            "Allocating common %s: %x at %x\n",
                            sym->name,(int)sym->size,
                            (int)(ls->base+sym->value));
                }
                break;
            }
            addtail(&ls->symbols,&sym->n);
            if (gv->map_file)
              print_symbol(gv->map_file,sym);
          }
          sym = sym->obj_chain;
        }
      }
      sec = nextsec;
    }
    ls = nextls;
  }
}


void linker_relocate(struct GlobalVars *gv)
/* Fix relocations, resolve x-references and create more relocations, */
/* if required. */
{
  struct LinkedSection *ls = (struct LinkedSection *)gv->lnksec.first;
  struct LinkedSection *nextls;
  struct Section *sec,*nextsec;

  while (nextls = (struct LinkedSection *)ls->n.next) {
    struct Section *sec = (struct Section *)ls->sections.first;
    struct Section *nextsec;

    if (!(ls->flags&SF_UNINITIALIZED)) {
      if (gv->trace_file)
        fprintf(gv->trace_file,"Relocating %s:\n\n",ls->name);

      while (nextsec = (struct Section *)sec->n.next) {
        struct Reloc *rel;
        struct XReference *xref;

        /* copy and fix relocations */
        while (rel = (struct Reloc *)remhead(&sec->relocs)) {
          rel->offset += sec->offset;
          rel->addend += rel->relocsect.ptr->offset;
          rel->relocsect.lnk = rel->relocsect.ptr->lnksec;
          if ((rel->type&R_MASK) != R_ADDR) {
            /* relative relocations can be resolved */
            resolve_reloc(gv,ls,sec,rel);
          }
          else {
            /* absolute relocations remain */
            writesection(gv,ls->data+rel->offset,rel->type,
                         (uint32)rel->addend);  /* write new addend */
            addtail(&ls->relocs,&rel->n);
          }
        }

        /* resolve, fix and copy x-references */
        while (xref = (struct XReference *)remhead(&sec->xrefs)) {
          xref->offset += sec->offset;
          if (xref->symbol)
            /* resolve xref - turn into relocation, if required */
            resolve_xref(gv,ls,sec,xref);
          else
            /* xref remains in output file */
            addtail(&ls->xrefs,&xref->n);
        }
        sec = nextsec;
      }      
    }
    ls = nextls;
  }
}


void linker_write(struct GlobalVars *gv)
{
  FILE *f;

  if (!gv->errflag) {  /* no error? */
    if (gv->trace_file)
      fprintf(gv->trace_file,"\nCreating output file %s (%s).\n",
              gv->dest_name,fff[gv->dest_format]->tname);

    /* write output file */
    if (f = fopen(gv->dest_name,"w")) {
      if (gv->dest_sharedobj)
        fff[gv->dest_format]->writeshared(gv,f);
      else if (gv->dest_object)
        fff[gv->dest_format]->writeobject(gv,f);
      else
        fff[gv->dest_format]->writeexec(gv,f);
      fclose(f);
    }
    else  /* Can't create output file */
      error(29,gv->dest_name);
  }
}


void linker_cleanup(struct GlobalVars *gv)
{
}


static void resolve_reloc(struct GlobalVars *gv,struct LinkedSection *ls,
                          struct Section *sec,struct Reloc *r)
/* Resolve relative and base-relative relocations. */
{
  char be = gv->big_endian;
  uint8 *p = ls->data + r->offset;
  int biterr = 0;

  if ((r->type&R_MASK) == R_REL) {
    /*********************************/
    /* Normal, PC-relative reference */
    /*********************************/

    if (r->relocsect.lnk == ls) {
      /* Relative reloc offset must be from the same section */
      int32 d = r->addend - r->offset;
  
      switch (r->type) {
        case R_REL32:
          write32(be,p,(uint32)d);
          break;
  
        case R_REL26:
          biterr = checkrange(d,3,TRUE);
          write32(be,p,(read32(be,p) & 0xfc000003)
                       | (((uint32)d)&0x3fffffc));
          break;
  
        case R_REL16:
          biterr = checkrange(d,2,TRUE);
          write16(be,p,(uint16)d);
          break;

        /* @@@ currently no differentiation between the following */
        case R_REL14:
        case R_REL14_BRTAKEN:
        case R_REL14_BRNTAKEN:
          biterr = checkrange(d,2,TRUE);
          write16(be,p,(read16(be,p) & 3) | (((uint16)d)&0xfffc));
          break;
  
        case R_REL8:
          biterr = checkrange(d,1,TRUE);
          *p = (uint8)d;
          break;
  
        default:  /* relative relocation type not supported */
          ierror("resolve_reloc(): Unsupported relative relocation "
                 "type %d",(int)r->type);
          break;
      }
    }  
    else {  /* error: Illegal relative reference on a different section */
      print_function_name(sec,r->offset);
      error(24,getobjname(sec->obj),sec->name,r->offset-sec->offset,
            r->relocsect.lnk->name,r->addend);
    }
  }

  /***************************/
  /* Base relative reference */
  /***************************/
  else {
    if (!gv->dest_object) {
      /* resolve base-relative relocation for executable file */
      int32 d = r->addend - fff[gv->dest_format]->baseoff;

      switch (r->type) {
        case R_BASEREL32:
          write32(be,p,(uint32)d);
          break;

        case R_BASEREL26:
          biterr = checkrange(d,3,TRUE);
          write32(be,p,(read32(be,p) & 0xfc000003)
                       | (((uint32)d)&0x3fffffc));
          break;

        case R_BASEREL16:
          biterr = checkrange(d,2,TRUE);
          write16(be,p,(uint16)d);
          break;
  
        case R_BASEREL8:
          biterr = checkrange(d,1,TRUE);
          *p = (uint8)d;
          break;

        default:  /* base-relative relocation type not supported */
          ierror("resolve_reloc(): Unsupported base relative relocation "
                 "type %d",(int)r->type);
          break;
      }
    }

    else {  
      /* destination is a relocatable object file, */
      /* so create a new base-relative relocation entry */
      int32 d = r->addend;

      switch (r->type) {
        case R_BASEREL32:
          write32(be,p,(uint32)d);
          break;

        case R_BASEREL26:
          biterr = checkrange(d,3,TRUE);
          write32(be,p,(read32(be,p) & 0xfc000003)
                       | (((uint32)d) & 0x3fffffc));
          break;

        case R_BASEREL16:
          biterr = checkrange(d,2,TRUE);
          write16(be,p,(uint16)d);
          break;
  
        case R_BASEREL8:
          biterr = checkrange(d,1,TRUE);
          *p = (uint8)d;
          break;

        default:  /* base-relative relocation type not supported */
          ierror("resolve_reloc(): Unsupported base relative relocation "
                 "type %d",(int)r->type);
          break;
      }
      newreloc(ls,r->relocsect.lnk,r->offset,r->addend,r->type);
    }

    if (biterr) {  /* relative reference out of range! */
      print_function_name(sec,r->offset);
      error(25,getobjname(sec->obj),sec->name,r->offset-sec->offset,
            biterr,r->relocsect.lnk->name,r->addend);
    }
  }
}


static void resolve_xref(struct GlobalVars *gv,struct LinkedSection *ls,
                         struct Section *sec,struct XReference *xref)
/* The external reference xref can be resolved by the symbol definition */
/* xdef = xref->symbol. The assignment of a relocatable symbol requires */
/* the creation of a new Reloc entry for the section. */
{
  static char *ierr_func = "resolve_xref(): ";
  static char *ierr_rtype = "Unsupported xref relocation type ";
  struct Symbol *xdef = xref->symbol;
  char be = gv->big_endian;
  uint8 *p = ls->data + xref->offset;
  int32 x;
  int biterr = 0;

  /* last chance to initialize a target-specific linker symbol */
  if (xdef->flags & SYMF_LNKSYM)
    fff[gv->dest_format]->setlnksym(gv,xdef,xref);

  /**************************************************/
  /* Relative/absolute reference to absolute symbol */
  /**************************************************/
  if (xdef->type == SYM_ABS) {
    /* resolve x-reference to an absolute symbol */
    x = (int32)xdef->value + xref->addend;

    switch (xref->type) {
      case R_ADDR32:
      case R_REL32:
      case R_BASEREL32:
        write32(be,p,(uint32)x);
        break;

      case R_ADDR26:
        biterr = checkrange(x,3,FALSE);
        write32(be,p,(read32(be,p) & 0xfc000003)
                     | (((uint32)x)&0x3fffffc));
        break;

      case R_REL26:
      case R_BASEREL26:
        biterr = checkrange(x,3,TRUE);
        write32(be,p,(read32(be,p) & 0xfc000003)
                     | (((uint32)x)&0x3fffffc));
        break;

      case R_ADDR16:
        biterr = checkrange(x,2,FALSE);
        write16(be,p,(uint16)x);
        break;

      /* @@@ currently no differentiation between the following */
      case R_ADDR14:
      case R_ADDR14_BRTAKEN:
      case R_ADDR14_BRNTAKEN:
        biterr = checkrange(x,2,FALSE);
        write16(be,p,(read16(be,p) & 3) | (((uint16)x)&0xfffc));
        break;

      case R_REL16:
      case R_BASEREL16:
        biterr = checkrange(x,2,TRUE);
        write16(be,p,(uint16)x);
        break;

      /* @@@ currently no differentiation between the following */
      case R_REL14:
      case R_REL14_BRTAKEN:
      case R_REL14_BRNTAKEN:
        biterr = checkrange(x,2,TRUE);
        write16(be,p,(read16(be,p) & 3) | (((uint32)x)&0xfffc));
        break;

      case R_ADDR16_LO:
        write16(be,p,(uint16)((uint32)x&0xffff));
        break;

      case R_ADDR16_HI:
        write16(be,p,(uint16)(((uint32)x>>16)&0xffff));
        break;

      case R_ADDR16_HA:
        write16(be,p,(uint16)((((uint32)x+0x8000)>>16)&0xffff));
        break;

      case R_ADDR8:
        biterr = checkrange(x,1,FALSE);
        *p = (uint8)x;
        break;

      case R_REL8:
      case R_BASEREL8:
        biterr = checkrange(x,1,TRUE);
        *p = (uint8)x;
        break;

      default:  /* xref relocation type not supported */
        ierror("%s%s%d",ierr_func,ierr_rtype,(int)xref->type);
        break;
    }
    if (biterr) {  /* referenced absolute symbol out of range! */
      print_function_name(sec,xref->offset);
      error(26,getobjname(sec->obj),sec->name,xref->offset-sec->offset,
            xdef->name,xdef->value,(uint32)xref->addend,biterr);
    }
  }

  else if (xdef->type == SYM_RELOC) {
    /* resolve x-reference to a relocatable symbol */

    /********************************************/
    /* Relative reference to relocatable symbol */
    /********************************************/
    if ((xref->type & R_MASK) == R_REL) {
      /* relative xrefs must be resolved within the same section */
      if (xdef->relsect->lnksec != ls) {
        /* Illegal relative reference to symbol */
        print_function_name(sec,xref->offset);
        error(27,getobjname(sec->obj),sec->name,xref->offset-sec->offset,
              xdef->name);
        return;
      }
      x = (int32)xdef->value - (int32)xref->offset + xref->addend;
      switch (xref->type) {
        case R_REL32:
          write32(be,p,(uint32)x);
          break;
  
        case R_REL26:
          biterr = checkrange(x,3,TRUE);
          write32(be,p,(read32(be,p) & 0xfc000003)
                       | (((uint32)x)&0x3fffffc));
          break;
  
        case R_REL16:
          biterr = checkrange(x,2,TRUE);
          write16(be,p,(uint16)x);
          break;

        /* @@@ currently no differentiation between the following */
        case R_REL14:
        case R_REL14_BRTAKEN:
        case R_REL14_BRNTAKEN:
          biterr = checkrange(x,2,TRUE);
          write16(be,p,(read16(be,p) & 3) | (((uint16)x)&0xfffc));
          break;
  
        case R_REL8:
          biterr = checkrange(x,1,TRUE);
          *p = (uint8)x;
          break;
  
        default:  /* xref relocation type not supported */
          ierror("%s%s%d",ierr_func,ierr_rtype,(int)xref->type);
          break;
      }
      if (biterr) {  /* Relative reference to reloc symbol out of range! */
        print_function_name(sec,xref->offset);
        error(28,getobjname(sec->obj),sec->name,xref->offset-sec->offset,
              xdef->name,xdef->value,(uint32)xref->addend,biterr);
      }
    }

    /*************************************************/
    /* Base relative reference to relocatable symbol */
    /*************************************************/
    else if ((xref->type&R_MASK)==R_BASEREL) {
      if (!gv->dest_object) {  /* destination is executable file? */
        int32 d = ((int32)xdef->value + xref->addend) -
                  fff[gv->dest_format]->baseoff;

        switch (xref->type) {
          case R_BASEREL32:
            write32(be,p,(uint32)d);
            break;
  
          case R_BASEREL26:
            biterr = checkrange(d,3,TRUE);
            write32(be,p,(read32(be,p) & 0xfc000003)
                         | (((uint32)d)&0x3fffffc));
            break;
  
          case R_BASEREL16:
            biterr = checkrange(d,2,TRUE);
            write16(be,p,(uint16)d);
            break;
    
          case R_BASEREL8:
            biterr = checkrange(d,1,TRUE);
            *p = (uint8)d;
            break;

          default:  /* xref relocation type not supported */
            ierror("%s%s%d",ierr_func,ierr_rtype,(int)xref->type);
            break;
        }
      }

      else {  
        /* destination is a relocatable object file, */
        /* so create a new base-relative relocation entry */
        int32 d = (int32)xdef->value + xref->addend;

        switch (xref->type) {
          case R_BASEREL32:
            write32(be,p,(uint32)d);
            break;

          case R_BASEREL26:
            biterr = checkrange(d,3,TRUE);
            write32(be,p,(read32(be,p) & 0xfc000003)
                         | (((uint32)d) & 0x3fffffc));
            break;

          case R_BASEREL16:
            biterr = checkrange(d,2,TRUE);
            write16(be,p,(uint16)d);
            break;
  
          case R_BASEREL8:
            biterr = checkrange(d,1,TRUE);
            *p = (uint8)d;
            break;

          default:  /* xref relocation type not supported */
            ierror("%s%s%d",ierr_func,ierr_rtype,(int)xref->type);
            break;
        }
        newreloc(ls,xdef->relsect->lnksec,xref->offset,d,xref->type);
      }

      if (biterr) {  /* Base relative ref. to reloc symbol out of range! */
        print_function_name(sec,xref->offset);
        error(36,getobjname(sec->obj),sec->name,xref->offset-sec->offset,
              xdef->name,xdef->value,(uint32)xref->addend,biterr);
      }
    }

    /********************************************/
    /* Absolute reference to relocatable symbol */
    /********************************************/
    else {
      /* generates a new relocation entry */
      int32 x = (int32)xdef->value + xref->addend;

      switch (xref->type) {
        case R_ADDR16_LO:
          write16(be,p,(uint16)((uint32)x&0xffff));
          break;

        case R_ADDR16_HI:
          write16(be,p,(uint16)(((uint32)x>>16)&0xffff));
          break;

        case R_ADDR16_HA:
          write16(be,p,(uint16)((((uint32)x+0x8000)>>16)&0xffff));
          break;

        default:
          write32(be,p,(uint32)x);
          break;
      }
      newreloc(ls,xdef->relsect->lnksec,xref->offset,x,xref->type);
    }
  }

  else
    ierror("%sIllegal xdef-symbol type: %d",ierr_func,(int)xdef->type);
}


static void newreloc(struct LinkedSection *current_ls,
                     struct LinkedSection *base_ls,
                     unsigned long offs,int32 addend,uint8 type)
/* Create a new relocation node and insert it into the relocation */
/* list of the current LinkedSection. */
{
  struct Reloc *r = alloc(sizeof(struct Reloc));

  r->relocsect.lnk = base_ls;
  r->offset = offs;
  r->addend = addend;
  r->type = type;
  addtail(&current_ls->relocs,&r->n);
}


static void assign_common(struct GlobalVars *gv)
/* Assign common symbols to a BSS section */
{
  struct ObjectUnit *obj = (struct ObjectUnit *)gv->selobjects.first;
  struct ObjectUnit *nextobj;
  struct Symbol *sym;
  int i;

  while (nextobj = (struct ObjectUnit *)obj->n.next) {
    if (obj->lnkfile->type != ID_SHAREDOBJ) {
      for (i=0; i<OBJSYMHTABSIZE; i++) {
        sym = obj->objsyms[i];
        while (sym) {

          if (sym->type == SYM_COMMON) {
            if (!(sym->relsect = find_sect_type(obj,ST_UDATA,
                                                SP_READ|SP_WRITE))) {
              /* No bss section? Then create a new one. */
              /* @@@ maybe it would be better to use the object's */
              /*  file format rather than the dest. file format?  */
              sym->relsect = fff[gv->dest_format]->bssdefault(obj);
            }
          }

          sym = sym->obj_chain;
        }
      }
    }
    obj = nextobj;
  }
}


char *getobjname(struct ObjectUnit *obj)
/* if library: return "file name(object name)" */
/* else: return "file name" */
{
  char *fn = obj->lnkfile->filename;

  if (obj->lnkfile->type == ID_LIBARCH) {
    char *on = obj->objname;
    if (strlen(fn)+strlen(on)+2 < FNAMEBUFSIZE) {
      sprintf(namebuf,"%s(%s)",fn,on);
      return (namebuf);
    }
  }
  return (fn);
}


void print_function_name(struct Section *sec,unsigned long offs)
/* Try to determine the function to which the section offset */
/* belongs, by comparing with SYMI_FUNC-type symbol definitions. */
/* If this was successful and the current function is different */
/* from the last one printed, make an output to stderr. */
{
  static const char *infoname[] = { "", "object ", "function " };
  static struct Symbol *last_func=NULL;
  struct Symbol *sym,*func=NULL;
  int i;

  for (i=0; i<OBJSYMHTABSIZE; i++) {
    sym = sec->obj->objsyms[i];  /* scan all hash chains */
    while (sym) {
      if (sym->relsect == sec) {
        if (sym->info <= SYMI_FUNC) {
          if (sym->type == SYM_RELOC) {
            if ((unsigned long)sym->value <= offs) {
              if (sym->size) {  /* size of function specified? */
                if ((unsigned long)(sym->value+sym->size) > offs) {
                  func = sym;
                  i = OBJSYMHTABSIZE;
                  break;  /* function found! */
                }
              }
              else {  /* no size - find nearest... */
                if (func) {
                  if (sym->value > func->value)
                    func = sym;
                }
                else
                  func = sym;
              }
            }
          }
        }
      }
      sym = sym->obj_chain;
    }
  }

  /* print function name */
  if (func && func!=last_func) {
    last_func = func;
    fprintf(stderr,"%s: In %s\"%s\":\n",getobjname(sec->obj),
            infoname[func->info],func->name);
  }
}


static char *maplibrary(struct GlobalVars *gv,struct InputFile *ifn)
/* Map a complete file into memory and return its address. */
/* The file's length is returned in *(p-sizeof(size_t)). */
/* All defined library paths will be searched for the file, */
/* before aborting. On success, the complete path of the loaded */
/* file is stored in namebuf[]. */
{
  char *p;
  char libname[FNAMEBUFSIZE];

  if (strlen(ifn->name) < (FNAMEBUFSIZE-16)) {
    if (ifn->dynamic) {
      sprintf(libname,"lib%s.so",ifn->name);
      if (p = searchlib(gv,libname,ifn->so_ver))
        return (p);
    }
    sprintf(libname,"lib%s.a",ifn->name);
    if (p = searchlib(gv,libname,-1))
      return (p);
    sprintf(libname,"%s.lib",ifn->name);
    if (p = searchlib(gv,libname,-1))
      return (p);
  }
  return (NULL);
}


static char *searchlib(struct GlobalVars *gv,char *libname,int so_ver)
{
  struct LibPath *lpn = (struct LibPath *)gv->libpaths.first;
  struct LibPath *nextlpn;
  char *p;

  if (p = scan_directory(".",libname,so_ver))
    return (p);
  while (nextlpn = (struct LibPath *)lpn->n.next) {
    if (p = scan_directory(lpn->path,libname,so_ver))
      return (p);
    lpn = nextlpn;
  }
  return (NULL);
}


static char *scan_directory(char *dirname,char *libname,int so_ver)
{
  size_t lnlen=strlen(libname);
  char *dd,*fname=NULL;

  if (so_ver < 0) {
    /* no need to scan the directory */
    fname = libname;
  }
  else {
    if (dd = open_dir(dirname)) {
      while (fname = read_dir(dd)) {
        if (!strncmp(fname,libname,lnlen)) {
          /* found a library archive/shared object name! */
          if (so_ver > 0) {  /* check version of shared object */
            if (fname[lnlen]=='.' && fname[lnlen+1]!=0) {
              if (atoi(&fname[lnlen+1]) >= so_ver)
                break;
            }
          }
          else {
            /* otherwise the version is ignored - @@@ take highest ver. ? */
            break;
          }
        }
        else
          fname = NULL;
      }
      close_dir(dd);
    }
  }

  if (fname) {
    char *p;
    size_t dnlen;

    if ((dnlen = strlen(dirname)) < (FNAMEBUFSIZE-strlen(fname)-2)) {
      strcpy(namebuf,dirname);
      if (namebuf[dnlen-1]!='/' && namebuf[dnlen-1]!=':') {
        namebuf[dnlen++] = '/';
        namebuf[dnlen] = 0;
      }
      strncat(namebuf,fname,FNAMEBUFSIZE-dnlen-1);
      if (p = mapfile(namebuf))
        return (p);
    }
  }
  return (NULL);
}


static void addrelref(struct RelRef **rrptr,struct Section *sec)
/* if not already exists, add a new relative reference to another sect. */
{
  struct RelRef *rr;

  if (rr = *rrptr) {
    while (rr->next) {
      if (rr->refsec == sec)
        return;  /* reference already exists */
      rr = rr->next;
    }
  }
  else
    rr = (struct RelRef *)rrptr;

  rr->next = alloczero(sizeof(struct RelRef));
  rr->next->refsec = sec;
}


static void copyrelrefs(struct RelRef **rrptr,struct Section *sec)
/* transfer all rel. references from a single section to a linked-section */
{
  struct RelRef *old,*srr = sec->relrefs;

  while (old = srr) {
    addrelref(rrptr,srr->refsec);
    srr = srr->next;
#ifndef FASTALLOC
    free(old);
#endif
  }
  sec->relrefs = NULL;
}


static bool checkrelrefs(struct RelRef *rr,struct Section *sec)
/* Check, if there is a relative reference to the specified section. */
{
  while (rr) {
    if (rr->refsec == sec)
      return (TRUE);
    rr = rr->next;
  }
  return (FALSE);
}


bool trace_sym_access(struct GlobalVars *gv,char *name)
/* check if the symbol with this name should be traced */
{
  struct SymNames *sn;

  if (gv->trace_syms) {
    sn = gv->trace_syms[elf_hash(name)%TRSYMHTABSIZE];
    while (sn) {
      if (!strcmp(name,sn->name))
        return (TRUE);  /* symbol found! */
      sn = sn->next;
    }
  }
  return (FALSE);
}


static struct LinkedSection *create_lnksect(struct GlobalVars *gv,char *name,
                                            uint8 type,uint8 flags,
                                            uint8 protection,uint8 alignment)
/* create and initialize a LinkedSection node and include */
/* it in the global list */
{
  struct LinkedSection *ls = alloc(sizeof(struct LinkedSection));

  ls->index = gv->nsecs++;
  ls->name = name;
  ls->type = type;
  ls->flags = flags;
  ls->protection = protection;
  ls->alignment = alignment;
  ls->base = fff[gv->dest_format]->secbase(gv,ls);
  ls->size = ls->filesize = 0;
  initlist(&ls->sections);
  ls->relrefs = NULL;
  ls->data = NULL;
  initlist(&ls->relocs);
  initlist(&ls->xrefs);
  initlist(&ls->symbols);
  addtail(&gv->lnksec,&ls->n);
  return (ls);
}


static struct LinkedSection *find_lnksect(struct GlobalVars *gv,
                                          struct Section *sec)
/* find a LinkedSection node which fits the specified section */
{
  struct LinkedSection *lsn = (struct LinkedSection *)gv->lnksec.first;
  struct LinkedSection *nextlsn;
  uint8 f;
  int tl;

  while (nextlsn = (struct LinkedSection *)lsn->n.next) {
    if ((f = fff[gv->dest_format]->cmpsecflags(lsn->flags,sec->flags))
        != 0xff) {
      f &= ~SF_PORTABLE_MASK;

      /* target-specific linking */
      if ((tl = fff[gv->dest_format]->targetlink(gv,lsn,sec)) > 0) {
        /* target requires combining of sections */
        combine_sections(lsn,sec,f);
        return (lsn);
      }

      if (tl == 0) {  /* target wants to use the default rules */

        if (!gv->dest_object) {
          /* only, if an executable file will be created: */

          if (gv->small_code && lsn->type==sec->type==ST_CODE) {
            /* merge all code sections */
            combine_sections(lsn,sec,f);
            return (lsn);
          }

          if (gv->small_data) {
            if ((lsn->type==ST_DATA || lsn->type==ST_UDATA) &&
                (sec->type==ST_DATA || sec->type==ST_UDATA)) {
              /* merge all data and bss sections */
              combine_sections(lsn,sec,f);
              return (lsn);
            }
          }

          if (checkrelrefs(lsn->relrefs,sec)) {
            /* we must link them together, because there are rel. refs */
            if ((lsn->type==ST_CODE || sec->type==ST_CODE) &&
                lsn->type != sec->type)
              /* induces a maybe unwanted combination of code and data */
              error(58,sec_names[lsn->type],lsn->name,sec_names[sec->type],
                    sec->name,getobjname(sec->obj));
            combine_sections(lsn,sec,f);
            return (lsn);
          }

          if (!gv->multibase && (lsn->flags & sec->flags & SF_SMALLDATA)) {
            combine_sections(lsn,sec,f);
            return (lsn);
          }
        }

        /* standard check, if sections could be coalesced */
        if (!strcmp(sec->name,lsn->name) || /* same name or no name */
            *(sec->name)==0) {
          if (lsn->type == sec->type) {     /* same type */
            combine_sections(lsn,sec,f);
            return (lsn);
          }
        }

      } /* default rules */
    }
    lsn = nextlsn;
  }
  return (NULL);
}


static void combine_sections(struct LinkedSection *lsn,struct Section *sec,
                             uint8 target_flags)
{
  if (lsn->type == ST_UNDEFINED) {
    lsn->type = sec->type;
    lsn->flags = sec->flags;
  }
  else {
    lsn->flags &= SF_PORTABLE_MASK;
    lsn->flags |= (sec->flags&SF_SMALLDATA) | target_flags;
  }

  if (lsn->protection != sec->protection) {
    if (lsn->protection &&
        (lsn->protection & sec->protection) != sec->protection) {
      /* combine protection-flags of the two sections */
      char prot1[5],prot2[5];
      strncpy(prot1,protstring(lsn->protection),5);
      strncpy(prot2,protstring(lsn->protection|sec->protection),5);
      error(22,lsn->name,prot1,prot2);
    }
    lsn->protection |= sec->protection;
  }

  if (sec->alignment > lsn->alignment) {
    /* increase alignment to fit the needs of the new section */
    if (lsn->alignment)
      error(23,lsn->name,lsn->alignment,sec->alignment);
    lsn->alignment = sec->alignment;
  }
}


static void print_symbol(FILE *f,struct Symbol *sym)
/* print symbol name, type, value, etc. */
{
  fprintf(f,"  %s: %s%s%s, value 0x%x, size %d\n",sym->name,
          sym_bind[sym->bind],sym_type[sym->type],sym_info[sym->info],
          (int)sym->value,(int)sym->size);
}


static char *protstring(uint8 prot)
/* return pointer to protection string - example: "r---" or "rwxs" */
{
  static char *ps="----";
  char *p = ps;

  *p++ = (prot & SP_READ) ? 'r' : '-';
  *p++ = (prot & SP_WRITE) ? 'w' : '-';
  *p++ = (prot & SP_EXEC) ? 'x' : '-';
  *p++ = (prot & SP_SHARE) ? 's' : '-';
  return (ps);
}
