/*
     RUN_ELF - Load and execute big ending ix86 ELF binaries
     Copyright (C) 2001-2002 Martin Blom <martin@blom.org>
     
     This program is free software; you can redistribute it and/or
     modify it under the terms of the GNU General Public License
     as published by the Free Software Foundation; either version 2
     of the License, or (at your option) any later version.
     
     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
     
     You should have received a copy of the GNU General Public License
     along with this program; if not, write to the Free Software
     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

/*
** Based on code from the ppc.library emulator by Frank Wille:
** ppc.library emulation
** (c)1998-99 Frank Wille <frank@phoenix.owl.de>
*/


#include <exec/types.h>
#include <exec/memory.h>
#include <exec/nodes.h>
#include <dos/dos.h>
#include <proto/dos.h>
#include <proto/exec.h>

#include <string.h>

#include "elfloader.h"

void
KPrintFArgs( UBYTE* fmt, 
             ULONG* args );

#define KPrintF( fmt, ... )        \
({                                 \
  ULONG _args[] = { __VA_ARGS__ }; \
  KPrintFArgs( (fmt), _args );     \
})

void
ReqA( const char* text, APTR args );

#define Req( fmt, ... )            \
({                                 \
  ULONG _args[] = { __VA_ARGS__ }; \
  ReqA( (fmt), _args );            \
})


#define bswap_long(x) \
     ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >>  8) | \
      (((x) & 0x0000ff00) <<  8) | (((x) & 0x000000ff) << 24))

#define bswap_short(x) \
     ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8))


/*** From glibc2/elf.h *******************************************************/

/* Intel 80386 specific definitions.  */

/* i386 relocs.  */

#define R_386_NONE      0               /* No reloc */
#define R_386_32        1               /* Direct 32 bit  */
#define R_386_PC32      2               /* PC relative 32 bit */
#define R_386_GOT32     3               /* 32 bit GOT entry */
#define R_386_PLT32     4               /* 32 bit PLT address */
#define R_386_COPY      5               /* Copy symbol at runtime */
#define R_386_GLOB_DAT  6               /* Create GOT entry */
#define R_386_JMP_SLOT  7               /* Create PLT entry */
#define R_386_RELATIVE  8               /* Adjust by program base */
#define R_386_GOTOFF    9               /* 32 bit offset to GOT */
#define R_386_GOTPC     10              /* 32 bit PC relative offset to GOT */
/* Keep this the last entry.  */
#define R_386_NUM       11


/*** elfcommon.h *************************************************************/

/* e_indent indexes */
#define EI_NIDENT  16
#define EI_MAG0    0
#define EI_MAG1    1
#define EI_MAG2    2
#define EI_MAG3    3
#define EI_CLASS   4
#define EI_DATA    5
#define EI_VERSION 6
#define EI_PAD     7

/* EI_CLASS */
#define ELFCLASSNONE 0
#define ELFCLASS32   1
#define ELFCLASS64   2

/* EI_DATA */
#define ELFDATANONE 0
#define ELFDATA2LSB 1
#define ELFDATA2MSB 2

/* e_type */
#define ET_NONE   0                 /* No file type */
#define ET_REL    1                 /* Relocatable file */
#define ET_EXEC   2                 /* Executable file */
#define ET_DYN    3                 /* Shared object file */
#define ET_CORE   4                 /* Core file */
#define ET_LOPROC 0xFF00            /* Processor-specific */
#define ET_HIPROC 0xFFFF            /* Processor-specific */

/* e_version */
#define EV_NONE    0
#define EV_CURRENT 1

/* e_machine */
#define EM_NONE           0
#define EM_M32            1
#define EM_SPARC          2
#define EM_386            3
#define EM_68K            4
#define EM_88K            5
#define EM_860            7
#define EM_MIPS           8
#define EM_MIPS_RS4_BE    10
#define EM_SPARC64        11
#define EM_PARISC         15
#define EM_PPC_OLD        17
#define EM_SPARC32PLUS    18
#define EM_PPC            20
#define EM_CYGNUS_POWERPC 0x9025
#define EM_ALPHA          0x9026

/* values for program header, p_type field */
#define PT_NULL    0                /* Program header table entry unused */
#define PT_LOAD    1                /* Loadable program segment */
#define PT_DYNAMIC 2                /* Dynamic linking information */
#define PT_INTERP  3                /* Program interpreter */
#define PT_NOTE    4                /* Auxiliary information */
#define PT_SHLIB   5                /* Reserved, unspecified semantics */
#define PT_PHDR    6                /* Entry for header table itself */
#define PT_LOPROC  0x70000000       /* Processor-specific */
#define PT_HIPROC  0x7FFFFFFF       /* Processor-specific */

/* Program segment permissions, in program header p_flags field */
#define PF_X        (1 << 0)        /* Segment is executable */
#define PF_W        (1 << 1)        /* Segment is writable */
#define PF_R        (1 << 2)        /* Segment is readable */
#define PF_MASKPROC 0xF0000000      /* Processor-specific reserved bits */

/* special sections indexes */
#define SHN_UNDEF 0
#define SHN_ABS 0xfff1
#define SHN_COMMON 0xfff2

/* sh_type */
#define SHT_NULL        0           /* Section header table entry unused */
#define SHT_PROGBITS    1           /* Program specific (private) data */
#define SHT_SYMTAB      2           /* Link editing symbol table */
#define SHT_STRTAB      3           /* A string table */
#define SHT_RELA        4           /* Relocation entries with addends */
#define SHT_HASH        5           /* A symbol hash table */
#define SHT_DYNAMIC     6           /* Information for dynamic linking */
#define SHT_NOTE        7           /* Information that marks file */
#define SHT_NOBITS      8           /* Section occupies no space in file */
#define SHT_REL         9           /* Relocation entries, no addends */
#define SHT_SHLIB       10          /* Reserved, unspecified semantics */
#define SHT_DYNSYM      11          /* Dynamic linking symbol table */
#define SHT_LOPROC      0x70000000  /* Processor-specific semantics, lo */
#define SHT_HIPROC      0x7FFFFFFF  /* Processor-specific semantics, hi */
#define SHT_LOUSER      0x80000000  /* Application-specific semantics */
#define SHT_HIUSER      0x8FFFFFFF  /* Application-specific semantics */

/* sh_flags */
#define SHF_WRITE     (1 << 0)      /* Writable data during execution */
#define SHF_ALLOC     (1 << 1)      /* Occupies memory during execution */
#define SHF_EXECINSTR (1 << 2)      /* Executable machine instructions */
#define SHF_MASKPROC  0xF0000000    /* Processor-specific semantics */

/* Values of note segment descriptor types for core files. */
#define NT_PRSTATUS 1               /* Contains copy of prstatus struct */
#define NT_FPREGSET 2               /* Contains copy of fpregset struct */
#define NT_PRPSINFO 3               /* Contains copy of prpsinfo struct */

#define STN_UNDEF 0                 /* undefined symbol index */

/* ST_BIND */
#define STB_LOCAL  0                /* Symbol not visible outside obj */
#define STB_GLOBAL 1                /* Symbol visible outside obj */
#define STB_WEAK   2                /* Like globals, lower precedence */
#define STB_LOPROC 13               /* Application-specific semantics */
#define STB_HIPROC 15               /* Application-specific semantics */

/* ST_TYPE */
#define STT_NOTYPE  0               /* Symbol type is unspecified */
#define STT_OBJECT  1               /* Symbol is a data object */
#define STT_FUNC    2               /* Symbol is a code object */
#define STT_SECTION 3               /* Symbol associated with a section */
#define STT_FILE    4               /* Symbol gives a file name */
#define STT_LOPROC  13              /* Application-specific semantics */
#define STT_HIPROC  15              /* Application-specific semantics */

/* Dynamic section tags */
#define DT_NULL     0
#define DT_NEEDED   1
#define DT_PLTRELSZ 2
#define DT_PLTGOT   3
#define DT_HASH     4
#define DT_STRTAB   5
#define DT_SYMTAB   6
#define DT_RELA     7
#define DT_RELASZ   8
#define DT_RELAENT  9
#define DT_STRSZ    10
#define DT_SYMENT   11
#define DT_INIT     12
#define DT_FINI     13
#define DT_SONAME   14
#define DT_RPATH    15
#define DT_SYMBOLIC 16
#define DT_REL      17
#define DT_RELSZ    18
#define DT_RELENT   19
#define DT_PLTREL   20
#define DT_DEBUG    21
#define DT_TEXTREL  22
#define DT_JMPREL   23
#define DT_LOPROC   0x70000000
#define DT_HIPROC   0x7fffffff

/*** elf32.h *****************************************************************/

typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned long uint32;


struct Elf32_Ehdr {
  unsigned char e_ident[EI_NIDENT];
  uint16 e_type;
  uint16 e_machine;
  uint32 e_version;
  uint32 e_entry;
  uint32 e_phoff;
  uint32 e_shoff;
  uint32 e_flags;
  uint16 e_ehsize;
  uint16 e_phentsize;
  uint16 e_phnum;
  uint16 e_shentsize;
  uint16 e_shnum;
  uint16 e_shstrndx;
};

struct Elf32_Shdr {
  uint32 sh_name;
  uint32 sh_type;
  uint32 sh_flags;
  uint32 sh_addr;
  uint32 sh_offset;
  uint32 sh_size;
  uint32 sh_link;
  uint32 sh_info;
  uint32 sh_addralign;
  uint32 sh_entsize;
};

struct Elf32_Sym {
  uint32 st_name;
  uint32 st_value;
  uint32 st_size;
  uint8 st_info;
  uint8 st_other;
  uint16 st_shndx;
};

/* st_info */
#define ELF32_ST_BIND(i) ((i)>>4)
#define ELF32_ST_TYPE(i) ((i)&0xf)
#define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf))

struct Elf32_Rel {
  uint32 r_offset;
  uint32 r_info;
};

struct Elf32_Rela {
  uint32 r_offset;
  uint32 r_info;
  uint32 r_addend;
};

/* r_info */
#define ELF32_R_SYM(i) ((i)>>8)
#define ELF32_R_TYPE(i) ((unsigned char)(i))
#define ELF32_R_INFO(s,t) (((s)<<8)+(unsigned char)(t))

/*** elfobject.h *************************************************************/

struct ELFSection {                     /* prog. sections for new tasks */
  char *name;
  APTR address;                         /* physical RAM address or NULL */
  UWORD flags;
  UWORD alignment;
  long offset;                          /* file offset and section size */
  ULONG size;
  int nrel;                             /* number of relocs */
  struct Elf32_Rela *relocs;            /* array of Elf32_Rela structs */
};

#define ElfSecB_NOBITS          0       /* bss section */
#define ElfSecF_NOBITS          1
#define ElfSecB_RELA            1       /* Relocs are of .rela type */
#define ElfSecF_RELA            2


struct ElfObject {
  UWORD flags;
  UWORD pad;
  const struct ELFDynSymbols* user_syms;/* User-provided symbols */
  BPTR handle;                          /* ELF file handle */
  char *secnames;                       /* .shstrtab - section names */
  char *symnames;                       /* .strtab - symbol mames */
  struct Elf32_Ehdr *header;            /* the ELF file header */
  struct Elf32_Sym *symtab;             /* .symtab - symbol table */
  uint32 nsyms;                         /* number of symbols */
  uint32 gsyms;                         /* first global symbol */
  struct ELFSection **sections;         /* ELFSection pointers */
  uint32 nsects;                        /* number of sections */
};

#define ElfObjB_Relocated       0       /* Object is relocated */
#define ElfObjF_Relocated       1

/*** relocnames.c ************************************************************/

const char *reloc_name[] = {
  "R_386_NONE",
  "R_386_32",
  "R_386_PC32",
  "R_386_GOT32",
  "R_386_PLT32",
  "R_386_COPY",
  "R_386_GLOB_DAT",
  "R_386_JMP_SLOT",
  "R_386_RELATIVE",
  "R_386_GOTOFF",
  "R_386_GOTPC"
};

/*** ppcobject.c *************************************************************/


static void
freeelfobj(struct ElfObject *elfobj);

static BOOL
loadelf32(struct ElfObject *eo,struct Elf32_Shdr *shdrs);

//static BOOL
//scanElfSymbols(struct ElfObject *eo,struct PPCObjectInfo *info,
//                      BOOL relmode);

//static BOOL
//getsyminfo(struct ElfObject *eo,struct PPCObjectInfo *info,
//           struct Elf32_Sym *stab);

static BOOL
loadprogram(struct ElfObject *eo, APTR* entry, BPTR* seg );

static void
freeprogram(struct ElfObject *eo);

static BPTR
makeseglist( struct ElfObject *eo, APTR text_section );

static BOOL
relocate(struct ElfObject *eo);

static void*
allocstr( const char* string );

static void*
alloc_dosseg( size_t size );

void
free_dosseg( void* addr );

static BPTR
opstream(struct ElfObject *eo, const char* name );

static BOOL
clstream(struct ElfObject *eo);

static BOOL
rdstream(struct ElfObject *eo,void *buf,long len);

static long
skstream(struct ElfObject *eo,long offs,long mode);

static BOOL
prstream(struct ElfObject *eo,long offs,void *buf,long len);

static void*
readsection(struct ElfObject *eo,struct Elf32_Shdr *sh);

static struct ELFSection*
progsection(struct ElfObject *eo, struct Elf32_Shdr *sh);

static BOOL
common_symbols(struct ElfObject *eo);

static BOOL
getrelocs(struct ElfObject *eo,struct Elf32_Shdr *sh);

static const char ELFid[4] = {
  0x7f,'E','L','F'
};

void*
ELFLoadObject( const char* objname, BPTR* seg, const struct ELFDynSymbols* syms )
{
  struct ElfObject *elfobj = NULL;

  /* allocate ElfObject structure */

  elfobj = AllocVec( sizeof( struct ElfObject ), MEMF_ANY );

  if( elfobj != NULL )
  {
    memset(elfobj,0,sizeof(struct ElfObject));

    elfobj->user_syms = syms;

    elfobj->header = AllocVec( sizeof( struct Elf32_Ehdr ), MEMF_ANY );

    if( elfobj->header != NULL )
    {
      /* open ELF stream for reading */

      elfobj->handle = opstream( elfobj, objname );

      if( elfobj->handle != NULL )
      {
        /* read and identify ELF 32bit PowerPC BigEndian header */

        if( rdstream( elfobj, elfobj->header, sizeof(struct Elf32_Ehdr) ) )
        {
          struct Elf32_Ehdr *hdr = elfobj->header;

          if (!strncmp(hdr->e_ident,ELFid,4) &&
              hdr->e_ident[EI_CLASS]==ELFCLASS32 &&
              hdr->e_ident[EI_DATA]==ELFDATA2MSB &&
              hdr->e_ident[EI_VERSION]==1 && hdr->e_version==1 &&
              (hdr->e_machine==EM_386) && hdr->e_type==ET_REL)
          {
            struct Elf32_Shdr *shdrs;
            ULONG shdrsize = (ULONG) hdr->e_shnum * (ULONG) hdr->e_shentsize;

            //Printf("elf32-386-be format recognized\n");

            shdrs = AllocVec( shdrsize, MEMF_ANY);

            if( shdrs != NULL )
            {
              /* read section header table and parse rest of object */

              if( prstream( elfobj, hdr->e_shoff, shdrs, shdrsize ) )
              {
                if( loadelf32( elfobj, shdrs ) )
                {
                  APTR text_entry;
                  //Printf("ELF object loaded (0x%08lx)\n", elfobj );
                  if( loadprogram( elfobj, &text_entry, seg ) ) {
                    //Printf("Start of PPC code: 0x%08lx\n", text_entry );
                    FreeVec( shdrs );
                    return (elfobj);
                  }
                }
              }
              FreeVec( shdrs );
            }
          }
          else
          {
            //KPrintF( "Not an ELF32-386-BE relocatable object.\n");
          }
        }
      }
      freeelfobj(elfobj);
    }
  }

  return (NULL);
}


void
ELFUnLoadObject( void* obj )
{
  struct ElfObject* elfobj = (struct ElfObject*) obj;

  if( obj == NULL )
  {
    return;
  }

  // Do not free the sections! That's handled by DOS' UnloadSeg() now.

//  if(elfobj->sections != NULL && elfobj->nsects > 0 )
//  {
//    freeprogram(elfobj);
//  }

  freeelfobj(elfobj);
}

#if 0
BOOL
ELFGetSymbol( void* obj,
              const char* name, 
              void** ptr )
{
  struct PPCObjectInfo oi =
  {
    0,
    NULL,
    PPCELFINFOTYPE_SYMBOL,
    STT_SECTION,
    STB_GLOBAL,
    0
  };

  BOOL rc;

  oi.Name = (char*) name;
  rc = scanElfSymbols( (struct ElfObject*) obj, &oi, FALSE );

  if( rc )
  {
    *ptr = (void*) oi.Address;
  }

  return rc;
}
#endif


static void freeelfobj(struct ElfObject *elfobj)
/* free all memory connected to an ElfObject */
{
  if (elfobj) {
    if (elfobj->handle)
      clstream(elfobj);
    FreeVec(elfobj);
  }
}


static BOOL loadelf32(struct ElfObject *eo,struct Elf32_Shdr *shdrs)
/* parse ELF object, initialize ElfObject structure */
{
  struct Elf32_Ehdr *hdr = eo->header;
  uint16 i;

  if ((eo->secnames = readsection(eo,&shdrs[hdr->e_shstrndx])) &&
      (eo->sections = AllocVec((hdr->e_shnum+1)*sizeof(void *),MEMF_ANY))) {
    memset(eo->sections,0,(hdr->e_shnum+1)*sizeof(void *));
    eo->nsects = hdr->e_shnum + 1;  /* +1 section for COMMON symbols */
    for (i=1; i<hdr->e_shnum; i++) {
      switch (shdrs[i].sh_type) {
        case SHT_PROGBITS:
        case SHT_NOBITS:
          if (!(eo->sections[i] = progsection(eo,&shdrs[i])))
            return (FALSE);
          break;
        case SHT_SYMTAB:
          if (!(eo->symnames = readsection(eo,&shdrs[shdrs[i].sh_link]))
              || !(eo->symtab = readsection(eo,&shdrs[i])))
            return (FALSE);
          eo->nsyms = shdrs[i].sh_size / sizeof(struct Elf32_Sym);
          eo->gsyms = shdrs[i].sh_info;
          break;
        default:
          break;
      }
    }
    for (i=1; i<hdr->e_shnum; i++) {
      switch (shdrs[i].sh_type) {
        case SHT_REL:
        case SHT_RELA:
          if (!getrelocs(eo,&shdrs[i]))
            return (FALSE);
          break;
        default:
          break;
      }
    }
    /* allocate space for Common symbols */
    return (common_symbols(eo));
  }
  return (FALSE);
}


#if 0
static BOOL scanElfSymbols(struct ElfObject *eo,struct PPCObjectInfo *info,
                           BOOL relmode)
/* Find an ELF symbol by its name or address and return all infos  */
/* in the supplied PPCObjectInfo structure. Return FALSE if symbol */
/* doesn't exist. */
/* ATTENTION: PPCLibBase may be locked at that stage! */
{
  ULONG addr = info->Address;
  char *name = info->Name;
  //Printf( "scanElfSymbols( 0x%08lx, 0x%08lx, %ld\n", eo, info, relmode );
  if (relmode) {
    unsigned int i;
    int j;
    struct ELFSection *es;
    struct Elf32_Rela *r;

    for (i=1; i<(eo->nsects-1); i++) {
      if( (es = eo->sections[i]) != NULL ) {
        for (j=0,r=es->relocs; j<es->nrel; j++,r++) {
          if (getsyminfo(eo,info,&eo->symtab[ELF32_R_SYM(r->r_info)])) {
            info->Address = (ULONG)es->address + r->r_offset;
            info->Type = PPCELFINFOTYPE_RELOC;
            info->SubType = (ULONG)ELF32_R_TYPE(r->r_info);
            if (info->Address == addr) {
              if (name) {
                if (!strcmp(name,info->Name))
                  return (TRUE);
              }
              else
                return (TRUE);
            }
          }
        }
      }
    }
  }

  else {
    struct Elf32_Sym *stab = eo->symtab;
    int i = eo->nsyms;
    while (--i) {
  //Printf( "i=%ld\n", i );
      if (getsyminfo(eo,info,++stab)) {
        if (!name) {
          if (info->Size) {
            if (addr>=info->Address && addr<(info->Address+info->Size))
              return (TRUE);
          }
          else {
            if (addr == info->Address)
              return (TRUE);
          }
        }
        else {
 //Printf( "comparing %s and %s\n", name,info->Name );
          if (!strcmp(name,info->Name))
            return (TRUE);
        }
      }
    }
  }
  return (FALSE);
}


static BOOL getsyminfo(struct ElfObject *eo,struct PPCObjectInfo *info,
                       struct Elf32_Sym *stab)
{
  struct ELFSection *es;
  ULONG subtype;

  subtype = (ULONG)ELF32_ST_TYPE(stab->st_info);
  if (subtype < STT_FILE) {
    info->Type = subtype==STT_SECTION ? PPCELFINFOTYPE_SECTION
                                      : PPCELFINFOTYPE_SYMBOL;
    switch (stab->st_shndx) {
      case SHN_UNDEF:
        /* undefined symbols will disappear after relocation */
      case SHN_ABS:
        info->Address = stab->st_value;
        break;
      case SHN_COMMON:
        /* @@@ common symbols should have disappeared after common_symbols() */
        info->Type = PPCELFINFOTYPE_COMSYMBOL;
        info->Address = 0;
        break;
      default:
        if( (es = eo->sections[stab->st_shndx]) != NULL )
          info->Address = (ULONG)es->address + stab->st_value;
        else
          info->Address = stab->st_value;
        break;
    }
    info->Name = eo->symnames + stab->st_name;
    info->SubType = subtype;
    info->Binding = (ULONG)ELF32_ST_BIND(stab->st_info);
    info->Size = stab->st_size;
    return (TRUE);
  }
  return (FALSE);
}
#endif

static BOOL loadprogram(struct ElfObject *eo, APTR* text_section, BPTR* seg )
/* load all sections into memory and relocate them */
{
  static const char *FN = "loadprogram(): ";
  struct ELFSection *s;
  uint8 *p;
  unsigned int i;

  for (i=0; i<(eo->nsects-1); i++) {
    if( (s = eo->sections[i]) != NULL ) {

      BOOL text   = !strcmp(s->name,".text");     /* .text section flag */
      ULONG size = s->size;

//Printf("%sSection '%s' (%ld bytes).\n", FN,s->name, s->size );
      if( size != 0 ) {
        // lcs: In order to be able to make a DOS memory segment out of
        // this later, we allocate 4 extra bytes BEFORE before the actual
        // section. This is handled by alloc_dosseg().
        
        if( (p = alloc_dosseg(size)) != NULL ) {
          s->address = (APTR)p;  /* store section's base address */
          if (!(s->flags & ElfSecF_NOBITS)) {
            /* a PROGBITS section - load it from file */

            //Printf("%sreading section %s\n",FN,s->name);
            if (prstream(eo,s->offset,p,s->size)) {
              if (text) {
                /* get start address of PPC program in .text */
		*text_section = p;
              }
            }
            else {
              *text_section = NULL;
              break;
            }
          }
        }
        else {
          Req("Failed to allocate %ld bytes "
                  "for section '%s'.\n",size,(ULONG)s->name);
          *text_section = NULL;
          break;
        }
      }
    }
  }

  if (*text_section != NULL) {
    if (!relocate(eo)) {  /* relocate sections */
      *text_section = NULL;
      freeprogram(eo);
    }
  }
  else
    freeprogram(eo);

  /* Build a DOS segment list */
  *seg = makeseglist( eo, *text_section );

  if( *seg == 0 )
  {
    *text_section = NULL;
    freeprogram(eo);
  }

  //Printf("%sreturning with entry=0x%08lx\n",FN,entry);
  return (*text_section != NULL);
}


static void freeprogram(struct ElfObject *eo)
{
  struct ELFSection *s;
  unsigned int i;

  for (i=0; i<eo->nsects; i++) {
    if( (s = eo->sections[i]) != NULL ) {
      if (s->address) {
        free_dosseg(s->address);
        s->address = NULL;
      }
    }
  }
}


static BPTR makeseglist( struct ElfObject *eo, APTR text_section )
{
  struct ELFSection *s;
  unsigned int i;
  BPTR  text_segment = 0;
  BPTR  first_segment = NULL;
  BPTR* last_segment_ptr = NULL;

  for (i=0; i<eo->nsects; i++) {
    if( (s = eo->sections[i]) != NULL ) {
      if (s->address) {
	BPTR this_segment = MKBADDR( s->address ) - 1;

        if( s->address != text_section )
        {
          if( first_segment == 0 )
          {
            first_segment = this_segment;
          }

          if( last_segment_ptr != NULL )
          {
            *last_segment_ptr = this_segment;
          }
          
          last_segment_ptr = (BPTR*) ((ULONG) s->address - 4);
        }
        else
        {
          text_segment = this_segment;
        }
      }
    }
  }

  if( last_segment_ptr )
  {
    // Make sure the segment list is terminated
    *last_segment_ptr = 0;
  }

  if( first_segment && text_segment )
  {
    *(BPTR*)(BADDR(text_segment)) = first_segment;
  }

#if 0
  {
    int i = 1;
    BPTR segment;

    Printf( "Verifying seglist\n" );
  
    segment = text_segment;

    while( segment != 0 )
    {
      Printf( "Segment %ld at 0x%08lx/%08lx, length %ld\n",
              i,
              segment, (ULONG) BADDR( segment ), 
              *(ULONG*)( BADDR( segment - 1 ) ) );

      segment = *(ULONG*)( BADDR( segment ) );
      ++i;
    }
  }

  return 0;
#endif

  return text_segment;
}


#if 0
static void write_short( void* addr, short value, BOOL little_endian )
{
  if(little_endian)
  {
    value = bswap_short(value);
  }

  *((short*) addr) = value;
}
#endif

static void write_long( void* addr, long value, BOOL little_endian )
{
  if(little_endian)
  {
    value = bswap_long(value);
  }

  *((long*) addr) = value;
}

#if 0
static short read_short( void* addr, BOOL little_endian )
{
  short value;

  value = *((short*) addr);

  if(little_endian)
  {
    value = bswap_short(value);
  }

  return value;
}
#endif

static long read_long( void* addr, BOOL little_endian )
{
  long value;

  value = *((long*) addr);

  if(little_endian)
  {
    value = bswap_long(value);
  }

  return value;
}


static BOOL relocate(struct ElfObject *eo)
{
  struct ELFSection *es;
  unsigned int shndx;

  for (shndx=0; shndx<(eo->nsects-1); shndx++) {
    if( (es = eo->sections[shndx]) != NULL ) {
      BOOL rela   = (es->flags & ElfSecF_RELA) != 0;
      BOOL little = strcmp(es->name,".text") == 0;
      struct Elf32_Rela *r;
      int i;

      //Printf("relocate(): relocating section %s "
      //        "at 0x%08lx\n",es->name,es->address);

      for (i=0,r=es->relocs; i<es->nrel; i++,r++) {
        struct Elf32_Sym *sym = &eo->symtab[ELF32_R_SYM(r->r_info)];
        long s; 
        uint8 *p = (uint8 *)es->address + r->r_offset;

        switch (sym->st_shndx) {
          case SHN_UNDEF:
          {
            const struct ELFDynSymbols* syms = eo->user_syms;

            while( syms->name != NULL )
            {
              if( strcmp( eo->symnames+sym->st_name, syms->name)==0 )
              {
                s = (ULONG) syms->addr;
                break;
              }
              
              ++syms;
            }

            if( syms->name == NULL )
            {
              Req( "Undefined symbol: '%s'", (ULONG)eo->symnames+sym->st_name);
              return FALSE;
            }
            break;
          }

          case SHN_ABS:
            s = sym->st_value;
            break;
        
          case SHN_COMMON:
            Req( "Common symbol: '%s'", (ULONG) eo->symnames+sym->st_name);
            return FALSE;
        
          default:
            s = (long)eo->sections[sym->st_shndx]->address + sym->st_value;
            break;
        }
#if 0
        Printf("*** Now doing relocation type %s "
                "at %s+%ld referencing "
                "symbol %s+%ld in section %s at %08lx\n",
                reloc_name[ELF32_R_TYPE(r->r_info)],es->name,r->r_offset,
                eo->symnames+sym->st_name,sym->st_value,
                eo->sections[sym->st_shndx]->name, eo->sections[sym->st_shndx]->address);
#endif

        switch (ELF32_R_TYPE(r->r_info)) {
          case R_386_NONE:
            break;

          case R_386_32:
            //Printf( "Value: %08lx (raw: %08lx)\n", read_long(p, little), *(long*)p);
            if (rela)
              write_long( p, s + r->r_addend, little );
            else
              write_long( p, read_long( p, little ) + s, little );
            //Printf( "Value: %08lx (raw: %08lx)\n", read_long(p, little), *(long*)p);
            break;

          case R_386_PC32:
            //Printf( "Value: %08lx (raw: %08lx)\n", read_long(p, little), *(long*)p);
            if (rela)
              write_long( p, s + r->r_addend - (long) p, little );
            else
              write_long( p, read_long( p, little ) + s - (long) p, little );
            //Printf( "Value: %08lx (raw: %08lx)\n", read_long(p, little), *(long*)p);
            break;

          default:
            Req("Relocation type %s\nat %s+%ld referencing\n"
                   "symbol %s+%ld\nis not supported.",
                   (ULONG)reloc_name[ELF32_R_TYPE(r->r_info)],(ULONG)es->name,r->r_offset,
                   (ULONG)eo->symnames+sym->st_name,sym->st_value);
            return (FALSE);
        }
      }
    }
  }
  return (TRUE);
}

static void*
alloc_dosseg( size_t size )
{
  void* address;

  /* align size to 32 bytes, add 8 bytes and allocate 32-byte aligned
     memory */

  size = ((size+31)&~31);

  address = AllocMem( size + 8 + 31, MEMF_ANY );

  if( address != NULL )
  {
    Forbid();
    FreeMem( address, size + 8 + 31 );
    address = AllocAbs( size + 8,
			(void*) (((ULONG) (address + 8 + 31) & ~31) - 8) );
    Permit();
  }

  if( address != NULL )
  {
    *(ULONG*) address = size + 8;
    memset( address + 4, 0, size + 4 );
//    Printf( "alloc_dosseg( %ld ) -> %08lx\n", size, address + 8 );
    return address + 8;
  }
  else
  {
    return NULL;
  }
}


void
free_dosseg( void* addr )
{
//  Printf( "free_dosseg( %08lx ) -> FreeMem( %08lx, %ld )\n", addr, addr - 8, *(ULONG*)(addr - 8) );
  
  FreeMem( addr - 8, *(ULONG*)(addr - 8) );
}


static void*
allocstr( const char* string )
{
  void* mem;

  mem = AllocVec( strlen( string ) + 1, MEMF_ANY );

  if( mem != NULL )
  {
    strcpy( mem, string );
  }

  return mem;
}


static BPTR
opstream(struct ElfObject *eo __attribute__ ((unused)), const char* name )
{
  BPTR handle = NULL;

  if( name != NULL )
  {
    handle = Open( (char*) name, MODE_OLDFILE );
  }

  return (handle);
}


static BOOL
clstream(struct ElfObject *eo)
{
  Close( eo->handle );
  return TRUE;
}


static BOOL
rdstream(struct ElfObject *eo,void *buf,long len)
{
  long r;
  
  r = Read( eo->handle, buf, len );

  if( r != len )
  {
    return (FALSE);
  }
  return (TRUE);
}


static long
skstream(struct ElfObject *eo,long offs,long mode)
{
  long r;

  r = Seek( eo->handle, offs, mode );
  return (r);
}


static BOOL
prstream(struct ElfObject *eo,long offs,void *buf,long len)
/* position and read stream */
{
  if (skstream(eo,offs,OFFSET_BEGINNING) != -1)
    return (rdstream(eo,buf,len));
  return FALSE;
}


static void *readsection(struct ElfObject *eo,struct Elf32_Shdr *sh)
/* allocate memory and read section contents */
{
  void *p;

  if( (p = AllocVec(sh->sh_size,MEMF_ANY)) != NULL )
    if (prstream(eo,sh->sh_offset,p,sh->sh_size))
      return (p);
  return (NULL);
}


static struct ELFSection *progsection(struct ElfObject *eo,
                                      struct Elf32_Shdr *sh)
/* Create Section structure from Elf32_Shdr. The contents of these     */
/* sections will be loaded and relocated on demand, e.g. if a new task */
/* is created. */
{
  struct ELFSection *s;

  if( (s = AllocVec(sizeof(struct ELFSection),MEMF_ANY)) != NULL ) {
    memset(s,0,sizeof(struct ELFSection));
    s->name = eo->secnames + sh->sh_name;
    s->flags = sh->sh_type==SHT_NOBITS ? ElfSecF_NOBITS:0;
    s->alignment = (uint8)sh->sh_addralign;
    s->offset = sh->sh_offset;
    s->size = sh->sh_size;
    return (s);
  }
  return (NULL);
}


static BOOL common_symbols(struct ElfObject *eo)
/* Create a and initialize Section structure for Common symbols. */
{
//  static const char *FN = "common_symbols(): ";
  static const char *bssname = ".bss";
  struct ELFSection *s = NULL;
  struct Elf32_Sym *sym;
//  uint16 *relocp;
  uint32 offset = 0,idx = 0,cnt=0;
  unsigned int i;

  /* First try to find a .bss, where Common symbols could be appended */
  for (i=0; i<(eo->nsects-1); i++) {
    if( (s = eo->sections[i]) != NULL ) {
      if (!strcmp(s->name,bssname) && (s->flags & ElfSecF_NOBITS)) {
        idx = i;
        offset = s->size;
        //Printf("%sfound %s at index %ld with size=%ld\n",
        //        FN,bssname,idx,offset);
        break;
      }
      else
        s = NULL;
    }
  }

  if (!s) {
    /* No .bss section present, allocate an own one */
    if( (s = AllocVec(sizeof(struct ELFSection),MEMF_ANY)) != NULL ) {
      memset(s,0,sizeof(struct ELFSection));
      s->name = allocstr((char *)bssname);
      s->flags = ElfSecF_NOBITS;
      s->alignment = 32;
      offset = 0;
      idx = eo->nsects-1;
      eo->sections[idx] = s;
      //Printf("%screated new %s at index %ld\n",FN,bssname,idx);
    }
    else
      return (FALSE);
  }

  /* Ok, search for COMMON symbols now */
  for (i=1,sym=&eo->symtab[1]; i<eo->nsyms; i++,sym++) {
    if (sym->st_shndx == SHN_COMMON) {
      offset = (offset + sym->st_value-1) & ~(sym->st_value-1);
      sym->st_value = offset;
      sym->st_shndx = idx;
      offset += sym->st_size;
      cnt++;
    }
  }
//  Printf("%sassigned %ld common symbols (%ld bytes)\n",
//          FN,cnt,offset-s->size);
  s->size = offset;  /* set new .bss section size */

  return (TRUE);
}


static BOOL getrelocs(struct ElfObject *eo,struct Elf32_Shdr *sh)
/* read relocation entries for a section */
{
  uint32 rsize = sh->sh_entsize;
  int nrelocs = (int)(sh->sh_size/rsize);
  struct ELFSection *s = eo->sections[sh->sh_info];


  s->nrel = nrelocs;
  if (sh->sh_type == SHT_RELA) {
    s->flags |= ElfSecF_RELA;
    if( (s->relocs = readsection(eo,sh)) != NULL )
      return (TRUE);
  }
  else {
    struct Elf32_Rela *r;

    if ((r = s->relocs = AllocVec(nrelocs*sizeof(struct Elf32_Rela),
                                  MEMF_ANY)) &&
        (skstream(eo,sh->sh_offset,OFFSET_BEGINNING) != -1)) {
      while (nrelocs--) {
        r->r_addend = 0;
        if (!rdstream(eo,r,sizeof(struct Elf32_Rel)))
          return (FALSE);
        r++;
      }
      return (TRUE);
    }        
  }
  return (FALSE);
}
