/* $VER: pasm output_elf.c V0.5 (12.10.97)
 *
 * This file is part of pasm, a portable PowerPC assembler.
 * Copyright (c) 1997-98  Frank Wille
 *
 * pasm is freeware and part of the portable and retargetable ANSI C
 * compiler vbcc, copyright (c) 1995-98 by Volker Barthelmann.
 * pasm 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.5 (12.10.97) phx
 *      Removed "pasm V0.x" from .comment section.
 * v0.3 (10.04.97) phx
 *      Fixed enforcer-hit, when external references are present.
 *      Some vbcc-specific changes.
 *      Support for little endian architectures.
 * v0.2 (25.03.97) phx
 *      Writes ELF object for 32-bit PowerPC big-endian. Either absolute
 *      or ELF output format may be selected. ELF is default for all
 *      currently supported platforms. PPCasm supports nine different
 *      relocation types (there are much more...).
 *      Compiles and works also under NetBSD/amiga (68k).
 *      Changed function declaration to 'new style' in all sources
 *      (to avoid problems with '...' for example).
 *      File created.
 */


#define OUTPUT_ELF_C
#include "ppcasm.h"
#include "elf.h"
#include <time.h>


struct StrTabList {
  struct list l;
  uint32 index;
};

struct StrTabNode {
  struct node n;
  char *str;
};

struct ShdrNode {
  struct node n;
  struct Elf32_Shdr s;
};

struct SymbolNode {
  struct node n;
  char *name;
  struct Elf32_Sym s;
};

struct RelaNode {
  struct node n;
  struct Elf32_Rela r;
};


static struct Elf32_Ehdr elf_header = {
  { 0x7f,'E','L','F',ELFCLASS32,0,EV_CURRENT,0,0,0,0,0,0,0,0,0 },
  ECH(ET_REL),ECH(EM_POWERPC),ECW(EV_CURRENT),0,0,0,0,
  ECH(sizeof(struct Elf32_Ehdr)),
  0,0,ECH(sizeof(struct Elf32_Shdr)),0,0
};
static char *output_name;


void output_elf32msb(struct GlobalVars *);

static struct ShdrNode *addShdr(struct list *);
static struct SymbolNode *addSymbol(struct list *,struct StrTabList *,char *);
static uint32 addString(struct StrTabList *,char *);
static void addRela(struct list *,uint32,uint32,uint32);
static void fw(FILE *,void *,size_t);



void output_elf32msb(struct GlobalVars *gv)
{
  struct list shdrlist,symlist,relalist;
  struct StrTabList shstrlist,strlist;
  struct ShdrNode *shn;
  struct SymbolNode *sym;
  uint32 symtabidx,strtabidx,shstrtabidx,firstglobal,align1,align2;
  uint32 roffset=0,soffset=sizeof(struct Elf32_Ehdr);
  uint32 shdrindex=0,symindex=0;
  struct Section *nextsec,*sec=(struct Section *)gv->sectionlist.first;
  struct Symbol *symchain;
  int i;
  FILE *fp;

  /* init */
  output_name = gv->dest_name;
  elf_header.e_ident[EI_DATA] = ELFDATA2MSB;
  initlist(&shdrlist);
  initlist(&symlist);
  initlist(&relalist);
  shstrlist.index = strlist.index = 0;
  initlist(&shstrlist.l);
  initlist(&strlist.l);
  addString(&shstrlist,"");  /* first string is always "" */
  symtabidx = addString(&shstrlist,".symtab");
  strtabidx = addString(&shstrlist,".strtab");
  shstrtabidx = addString(&shstrlist,".shstrtab");
  addShdr(&shdrlist);  /* first Shdr is always zero */
  addString(&strlist,"");
  addSymbol(&symlist,NULL,NULL);  /* first symbol table entry always empty */

  /* source file name symbol */
  if (gv->file) {
    ++symindex;
    sym = addSymbol(&symlist,&strlist,gv->file);
    sym->s.st_info = ELF32_ST_INFO(STB_LOCAL,STT_FILE);
    sym->s.st_shndx = ECH(SHN_ABS);
  }

  /* generate section headers for program sections */
  while (nextsec = (struct Section *)sec->n.next) {
    if (!(sec->flags & SF_DISCARD) && sec->size > 0) {
      sec->index = ++shdrindex;
      shn = addShdr(&shdrlist);
      shn->s.sh_name = ECVW(addString(&shstrlist,sec->name));
      if (sec->type==ST_UDATA || (sec->flags&SF_UNINITIALIZED))
        shn->s.sh_type = ECW(SHT_NOBITS);
      else
        shn->s.sh_type = ECW(SHT_PROGBITS);
      shn->s.sh_flags = ECW(SHF_ALLOC);
      if (sec->protection & SP_WRITE)
        shn->s.sh_flags |= ECW(SHF_WRITE);
      if (sec->protection & SP_EXEC)
        shn->s.sh_flags |= ECW(SHF_EXECINSTR);
      shn->s.sh_offset = ECVW(soffset);
      shn->s.sh_size = ECVW(sec->size);
      if (shn->s.sh_type == ECW(SHT_PROGBITS))
        soffset += sec->size;
      shn->s.sh_addralign = ECVW(1<<(uint32)sec->alignment);
      /* add section symbol */
      sym = addSymbol(&symlist,NULL,NULL);
      sym->s.st_info = ELF32_ST_INFO(STB_LOCAL,STT_SECTION);
      sym->s.st_shndx = ECVH((uint16)shdrindex);
      ++symindex;
    }
    sec = nextsec;
  }

  /* comment section */
  if (gv->ident || gv->vc) {
    ++shdrindex;
    shn = addShdr(&shdrlist);
    shn->s.sh_name = ECVW(addString(&shstrlist,".comment"));
    shn->s.sh_type = ECW(SHT_PROGBITS);
    shn->s.sh_offset = ECVW(soffset);
    shn->s.sh_size = 1;  /* zero-byte */
    if (gv->ident)
      shn->s.sh_size += (uint32)strlen(gv->ident)+1;
    if (gv->vc)
      shn->s.sh_size += 12;
    soffset += shn->s.sh_size;
#ifdef LITTLEENDIAN
    shn->s.sh_size = l2bw(shn->s.sh_size);
#endif
    shn->s.sh_addralign = ECW(1);
    /* add section symbol */
    sym = addSymbol(&symlist,NULL,NULL);
    sym->s.st_info = ELF32_ST_INFO(STB_LOCAL,STT_SECTION);
    sym->s.st_shndx = ECVH((uint16)shdrindex);
    ++symindex;
  }

  /* build symbol table */
  for (i=0; i<SYMHTABSIZE; i++) {  /* first, symbols with local binding */
    symchain = gv->symbols[i];
    while (symchain) {
      if (*(symchain->name) != '.') {  /* '.symbols' are ignored */
        if (symchain->type==SYM_ABS || symchain->type==SYM_RELOC) {
          if (symchain->bind == SYMB_LOCAL) {
            ++symindex;
            sym = addSymbol(&symlist,&strlist,symchain->name);
            sym->s.st_value = ECVW(symchain->value);
            sym->s.st_size = ECVW(symchain->size);
            sym->s.st_info = ELF32_ST_INFO(STB_LOCAL,symchain->type);
            if (symchain->type == SYM_ABS)
              sym->s.st_shndx = ECH(SHN_ABS);
            else
              sym->s.st_shndx = ECVH((uint16)symchain->relsect->index);
          }
        }
      }
      symchain = symchain->hash_chain;
    }
  }
  firstglobal = symindex + 1;
  for (i=0; i<SYMHTABSIZE; i++) {  /* then, global and weak symbols */
    symchain = gv->symbols[i];
    while (symchain) {
      if (*(symchain->name) != '.') {  /* '.symbols' are ignored */
        if (symchain->type==SYM_ABS || symchain->type==SYM_RELOC) {
          if (symchain->bind > SYMB_LOCAL) {
            ++symindex;
            sym = addSymbol(&symlist,&strlist,symchain->name);
            sym->s.st_value = ECVW(symchain->value);
            sym->s.st_size = ECVW(symchain->size);
            sym->s.st_info = ELF32_ST_INFO(symchain->bind,symchain->type);
            if (symchain->type == SYM_ABS)
              sym->s.st_shndx = ECH(SHN_ABS);
            else
              sym->s.st_shndx = ECVH((uint16)symchain->relsect->index);
          }
        }
      }
      symchain = symchain->hash_chain;
    }
  }

  /* ".rela.xxx" relocation sections */
  sec = (struct Section *)gv->sectionlist.first;
  while (nextsec = (struct Section *)sec->n.next) {
    if (!(sec->flags & SF_DISCARD) && sec->size > 0) {
      struct Reloc *nextrel,*rel=(struct Reloc *)sec->reloclist.first;
      struct XReference *nextxref;
      struct XReference *xref=(struct XReference *)sec->xreflist.first;
      uint32 ro=roffset;
      char *sptr;

      while (nextrel = (struct Reloc *)rel->n.next) {
        addRela(&relalist,rel->offset,rel->addend,
                ELF32_R_INFO(rel->relocsect->index,rel->type));
        roffset += sizeof(struct Elf32_Rela);
        rel = nextrel;
      }

      while (nextxref = (struct XReference *)xref->n.next) {
        struct SymbolNode *nextsym;
        char *xname = xref->xsymbol->name;
        uint32 sidx=0;

        /* check if referenced symbol is already in symbol table */
        sym = (struct SymbolNode *)symlist.first;
        while (nextsym = (struct SymbolNode *)sym->n.next) {
          if (sym->name)
            if (!strcmp(xname,sym->name))
              break;
          ++sidx;
          sym = nextsym;
        }
        if (nextsym==NULL) {
          sidx = ++symindex;
          sym = addSymbol(&symlist,&strlist,xname);
          sym->s.st_info = ELF32_ST_INFO(STB_GLOBAL,STT_NOTYPE);
        }
        addRela(&relalist,xref->offset,xref->addend,
                ELF32_R_INFO(sidx,xref->type));
        roffset += sizeof(struct Elf32_Rela);
        xref = nextxref;
      }

      if (ro != roffset) {  /* were there any relocations? */
        sptr = (char *)alloc(strlen(sec->name) + 5);
        sprintf(sptr,".rela%s",sec->name);
        ++shdrindex;
        shn = addShdr(&shdrlist);
        shn->s.sh_name = ECVW(addString(&shstrlist,sptr));
        shn->s.sh_type = ECW(SHT_RELA);
        shn->s.sh_offset = ro;  /* relative offset, corrected later */
        shn->s.sh_size = ECVW(roffset - ro);
        /* sh_link will be set later, when .symtab exists */
        shn->s.sh_info = ECVW(sec->index); /* shdr idx to which reloc applies */
        shn->s.sh_addralign = ECW(4);
        shn->s.sh_entsize = ECW(sizeof(struct Elf32_Rela));
      }
    }
    sec = nextsec;
  }

  /* ".shstrtab" section header string table */
  ++shdrindex;
  shn = addShdr(&shdrlist);
  shn->s.sh_name = ECVW(shstrtabidx);
  shn->s.sh_type = ECW(SHT_STRTAB);
  shn->s.sh_offset = ECVW(soffset);
  shn->s.sh_size = ECVW(shstrlist.index);
  shn->s.sh_addralign = ECW(1);
  soffset += shstrlist.index;
  align1 = ((soffset + 3) & ~3) - soffset;
  soffset += align1;

  elf_header.e_shoff = ECVW(soffset);
  soffset += (shdrindex+3)*sizeof(struct Elf32_Shdr);
  elf_header.e_shstrndx = ECVH((uint16)shdrindex);
  elf_header.e_shnum = ECVH((uint16)shdrindex+3);

  /* ".symtab" symbol table */
  ++shdrindex;
  ++symindex;  /* number of symbol in symbol table */
  shn = addShdr(&shdrlist);
  shn->s.sh_name = ECVW(symtabidx);
  shn->s.sh_type = ECW(SHT_SYMTAB);
  shn->s.sh_offset = ECVW(soffset);
  shn->s.sh_size = symindex*sizeof(struct Elf32_Sym);
  shn->s.sh_link = ECVW(shdrindex+1);  /* associated .strtab section */
  shn->s.sh_info = ECVW(firstglobal);  /* first non-local symbol index */
  shn->s.sh_addralign = ECW(4);
  shn->s.sh_entsize = ECW(sizeof(struct Elf32_Sym));
  soffset += shn->s.sh_size;
#ifdef LITTLEENDIAN
  shn->s.sh_size = l2bw(shn->s.sh_size);
#endif

  /* ".strtab" string table */
  shn = addShdr(&shdrlist);
  shn->s.sh_name = ECVW(strtabidx);
  shn->s.sh_type = ECW(SHT_STRTAB);
  shn->s.sh_offset = ECVW(soffset);
  shn->s.sh_size = ECVW(strlist.index);
  shn->s.sh_addralign = ECW(1);
  soffset += strlist.index;
  align2 = ((soffset + 3) & ~3) - soffset;
  soffset += align2;  /* offset for first Rela-entry */

  /* create output file */
  if (fp = fopen(output_name,"w")) {
    struct StrTabNode *stn;
    struct RelaNode *rn;

    fw(fp,&elf_header,sizeof(struct Elf32_Ehdr));  /* write header */

    /* write initialized section contents */
    sec = (struct Section *)gv->sectionlist.first;
    while (nextsec = (struct Section *)sec->n.next) {
      if (!(sec->flags & (SF_DISCARD|SF_UNINITIALIZED)))
        fw(fp,sec->contents,sec->size);
      sec = nextsec;
    }

    /* write comment section */
    if (gv->ident || gv->vc) {
      fw(fp,gv->alignment_bytes,1);  /* write leading 0-byte */
      if (gv->ident)
        fw(fp,gv->ident,strlen(gv->ident)+1);
      if (gv->vc) {
        uint32 v = ECW(0x16020303);

        fw(fp,&v,4);
        v = ECVW((uint32)time(NULL));
        fw(fp,&v,4);
        v = ECW(0x03030216);
        fw(fp,&v,4);
      }
    }

    /* write .shstrtab string table */
    while (stn = (struct StrTabNode *)remhead(&shstrlist.l))
      fw(fp,stn->str,strlen(stn->str)+1);

    /* write section headers */
    fw(fp,gv->alignment_bytes,align1);
    while (shn = (struct ShdrNode *)remhead(&shdrlist)) {
      if (shn->s.sh_type == SHT_RELA) {
        shn->s.sh_offset += soffset;  /* set correct offset */
#ifdef LITTLEENDIAN
        shn->s.sh_offset = l2bw(shn->s.sh_offset);
#endif
        shn->s.sh_link = ECVW(shdrindex); /* index of associated symbol table */
      }
      fw(fp,&(shn->s),sizeof(struct Elf32_Shdr));
    }

    /* write symbol table */
    while (sym = (struct SymbolNode *)remhead(&symlist))
      fw(fp,&(sym->s),sizeof(struct Elf32_Sym));

    /* write .strtab string table */
    while (stn = (struct StrTabNode *)remhead(&strlist.l))
      fw(fp,stn->str,strlen(stn->str)+1);

    /* write relocations */
    fw(fp,gv->alignment_bytes,align2);
    while (rn = (struct RelaNode *)remhead(&relalist))
      fw(fp,&(rn->r),sizeof(struct Elf32_Rela));

    fclose(fp);
  }
  else
    error(25,output_name);  /* unable to create output file */
}


static struct ShdrNode *addShdr(struct list *l)
{
  struct ShdrNode *s = alloczero(sizeof(struct ShdrNode));

  addtail(l,&(s->n));
  return (s);
}


static struct SymbolNode *addSymbol(struct list *l,struct StrTabList *sl,
                                    char *name)
{
  struct SymbolNode *sn = alloczero(sizeof(struct SymbolNode));

  addtail(l,&(sn->n));
  if (name) {
    sn->name = name;
    sn->s.st_name = ECVW(addString(sl,name));
  }
  return (sn);
}


static uint32 addString(struct StrTabList *sl,char *s)
{
  struct StrTabNode *sn = alloc(sizeof(struct StrTabNode));
  uint32 idx = sl->index;

  sn->str = s;
  addtail(&(sl->l),&(sn->n));
  sl->index += (uint32)strlen(s) + 1;
  return (idx);
}


static void addRela(struct list *l,uint32 o,uint32 a,uint32 i)
{
  struct RelaNode *rn = alloc(sizeof(struct RelaNode));

  rn->r.r_offset = ECVW(o);
  rn->r.r_addend = ECVW(a);
  rn->r.r_info = ECVW(i);
  addtail(l,&(rn->n));
}


static void fw(FILE *fp,void *buf,size_t len)
{
  if (len) {
    if (!fwrite(buf,1,len,fp)) {
      fclose(fp);
      error(26,output_name);  /* write error */
    }
  }
}
