/*
 * Copyright (c) 2012, Chris Johns <chrisj@rtems.org>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
/**
 * @file
 *
 * @ingroup rtems_ld
 *
 * @brief RTEMS Linker.
 *
 * @todo Set the RAP alignment as the max of all alignments.
 */

#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>

#include <algorithm>
#include <list>
#include <iomanip>

#include <rld.h>
#include <rld-compression.h>
#include <rld-rap.h>

namespace rld
{
  namespace rap
  {

    /**
     * Output details or not.
     */
    bool add_obj_details = true;

    /**
     * Store the path of object files.
     */
    std::string rpath;

    /**
     * The names of the RAP sections.
     */
    static const char* section_names[rap_secs] =
    {
      ".text",
      ".const",
      ".ctor",
      ".dtor",
      ".data",
      ".bss"
    };

    /**
     * RAP relocation record. This one does not have const fields.
     */
    struct relocation
    {
      uint32_t    offset;    //< The offset in the section to apply the fixup.
      uint32_t    info;      //< The ELF info record.
      uint32_t    addend;    //< The ELF constant addend.
      std::string symname;   //< The symbol name if there is one.
      uint32_t    symtype;   //< The type of symbol.
      int         symsect;   //< The symbol's RAP section.
      uint32_t    symvalue;  //< The symbol's default value.
      uint32_t    symbinding;//< The symbol's binding.

      /**
       * Construct the relocation using the file relocation, the offset of the
       * section in the target RAP section and the RAP section of the symbol.
       */
      relocation (const files::relocation& reloc, const uint32_t offset);
    };

    /**
     * Relocation records.
     */
    typedef std::vector < relocation > relocations;

    /**
     * Relocation symname sorter for the relocations container.
     */
    class reloc_symname_compare
    {
    public:
      bool operator () (const relocation& lhs,
                        const relocation& rhs) const {
        return lhs.symname < rhs.symname;
      }
    };

    /**
     * Relocation offset sorter for the relocations container.
     */
    class reloc_offset_compare
    {
    public:
      bool operator () (const relocation& lhs,
                        const relocation& rhs) const {
        if (lhs.symname == rhs.symname)
          return lhs.offset < rhs.offset;
        else return false;
      }
    };

    /**
     * An object section's offset, size and alignment.
     */
    struct osection
    {
      std::string name;     //< The name of the section.
      uint32_t    offset;   //< The offset in the object file.
      uint32_t    size;     //< The size of this section.
      uint32_t    align;    //< The alignment.
      uint32_t    relocs;   //< The number of relocations.
      uint64_t    flags;    //< The flags.

      /**
       * Construct the object section.
       */
      osection (const std::string& name,
                uint32_t           offset,
                uint32_t           size,
                uint32_t           align,
                uint32_t           relocs,
                uint64_t           flags);

      /**
       * Default constructor.
       */
      osection ();
    };

    /**
     * Map of object file section offsets keyed by the object file section
     * index. This is used when adding the external symbols so the symbol's
     * value can be adjusted by the offset of the section in the RAP section.
     */
    typedef std::map < int, osection > osections;

    /**
     * An ordered container of object section indexes. We need the same
     * order so the alignments match up with the layout.
     */
    typedef std::vector < int > osecindexes;

    /**
     * Section detail will be written into RAP file
     */
    struct section_detail
    {
      uint32_t name;   //< The offset in the strtable.
      uint32_t offset; //< The offset in the rap section.
      uint32_t id;     //< The rap id.
      uint32_t size;   //< The size of the section.

      /* Constructor */
      section_detail (uint32_t name, uint32_t offset, uint32_t id, uint32_t size);
    };

    /*
     * A container of section detail
     */
    typedef std::list < section_detail > section_details;

    /**
     * The RAP section data.
     */
    struct section
    {
      std::string name;      //< The name of the section.
      uint32_t    offset;    //< The offset of the section.
      bool        rela;      //< The relocation record has an addend field.
      relocations relocs;    //< The relocations for this section.
      osections   osecs;     //< The object section index.
      osecindexes osindexes; //< The object section indexes in order.

      /**
       * Default constructor.
       */
      section ();

      /**
       * Clear the section.
       */
      void clear ();

      /**
       * The size of the section given the offset.
       */
      uint32_t size (uint32_t offset = 0) const;

      /**
       * The alignment of the first section.
       */
      uint32_t alignment () const;

      /**
       * The alignment of the object section given its index.
       */
      uint32_t alignment (int index) const;

      /**
       * Set the offset of this section based on the previous section.
       */
      void set_offset (const section& sec);

      /**
       * Return the object section given the index.
       */
      const osection& get_osection (int index) const;

      /**
       * Output helper function to report the sections in an object file. This
       * is useful when seeing the flags in the sections.
       */
      void output ();
    };

    /**
     * A symbol. This matches the symbol structure 'rtems_rtl_obj_sym_t' in the
     * target code.
     */
    struct external
    {
      /**
       * Size of an external in the RAP file.
       */
      static const uint32_t rap_size = sizeof (uint32_t) * 3;

      const uint32_t name;  //< The string table's name index.
      const sections sec;   //< The section the symbols belongs to.
      const uint32_t value; //< The offset from the section base.
      const uint32_t data;  //< The ELF st.info field.

      /**
       * The constructor.
       */
      external (const uint32_t name,
                const sections sec,
                const uint32_t value,
                const uint32_t data);

      /**
       * Copy constructor.
       */
      external (const external& orig);

    };

    /**
     * A container of externals.
     */
    typedef std::list < external > externals;

    /**
     * The specific data for each object we need to collect to create the RAP
     * format file.
     */
    struct object
    {
      files::object&  obj;            //< The object file.
      files::sections text;           //< All executable code.
      files::sections const_;         //< All read only data.
      files::sections ctor;           //< The static constructor table.
      files::sections dtor;           //< The static destructor table.
      files::sections data;           //< All initialised read/write data.
      files::sections bss;            //< All uninitialised read/write data
      files::sections symtab;         //< All exported symbols.
      files::sections strtab;         //< All exported strings.
      section         secs[rap_secs]; //< The sections of interest.

      /**
       * The constructor. Need to have an object file to create.
       */
      object (files::object& obj);

      /**
       * The copy constructor.
       */
      object (const object& orig);

      /**
       * Find the section type that matches the section index.
       */
      sections find (const uint32_t index) const;

      /**
       * The total number of relocations in the object file.
       */
      uint32_t get_relocations () const;

      /**
       * The total number of relocations for a specific RAP section in the
       * object file.
       */
      uint32_t get_relocations (int sec) const;

      /**
       * Output the object file details..
       */
      void output ();

    private:
      /**
       * No default constructor allowed.
       */
      object ();
    };

    /**
     * A container of objects.
     */
    typedef std::list < object > objects;

    /**
     * The RAP image.
     */
    class image
    {
    public:
      /**
       * Construct the image.
       */
      image ();

      /**
       * Load the layout data from the object files.
       *
       * @param app_objects The object files in the application.
       * @param init The initialisation entry point label.
       * @param fini The finish entry point label.
       */
      void layout (const files::object_list& app_objects,
                   const std::string&        init,
                   const std::string&        fini);

      /**
       * Collection the symbols from the object file.
       *
       * @param obj The object file to collection the symbol from.
       */
      void collect_symbols (object& obj);

      /**
       * Write the compressed output file. This is the top level write
       * interface.
       *
       * @param comp The compressor.
       */
      void write (compress::compressor& comp);

      /**
       * Write the RAP section to the compressed output file given the object files.
       * Check to make sure the size in the layout and the size written match.
       *
       * @param comp The compressor.
       * @param sec The RAP setion to write.
       */
      void write (compress::compressor& comp, sections sec);

      /**
       * Write the sections to the compressed output file. The file sections
       * are used to ensure the alignment. The offset is used to ensure the
       * alignment of the first section of the object when it is written.
       *
       * @param comp The compressor.
       * @param obj The object file the sections are part of.
       * @param secs The container of file sections to write.
       * @param offset The current offset in the RAP section.
       */
      void write (compress::compressor&  comp,
                  files::object&         obj,
                  const files::sections& secs,
                  uint32_t&              offset);

      /**
       * Write the external symbols.
       */
      void write_externals (compress::compressor& comp);

      /**
       * Write the relocation records for all the object files.
       */
      void write_relocations (compress::compressor& comp);

      /**
       * Write the details of the files.
       */
      void write_details (compress::compressor& comp);

      /**
       * The total number of relocations for a specific RAP section in the
       * image.
       */
      uint32_t get_relocations (int sec) const;

      /**
       * Clear the image values.
       */
      void clear ();

      /**
       * Report the RAP section's size.
       */
      uint32_t section_size (sections sec) const;

      /**
       * Find a symbol name in the string table.
       */
      std::size_t find_in_strtab (const std::string& symname);

    private:

      objects     objs;                //< The RAP objects
      uint32_t    sec_size[rap_secs];  //< The sections of interest.
      uint32_t    sec_align[rap_secs]; //< The sections of interest.
      bool        sec_rela[rap_secs];  //< The sections of interest.
      externals   externs;             //< The symbols in the image
      uint32_t    symtab_size;         //< The size of the symbols.
      std::string strtab;              //< The strings table.
      uint32_t    relocs_size;         //< The relocations size.
      uint32_t    init_off;            //< The strtab offset to the init label.
      uint32_t    fini_off;            //< The strtab offset to the fini label.
    };

    /*
     * Per machine specific special handling.
     */
    bool
    machine_symbol_check (const symbols::symbol& sym)
    {
      int symsec = sym.section_index ();

      /*
       * Ignore section index 0
       */
      if (symsec == 0)
        return false;

      /*
       * Ignore sparc common section
       */
      if ((elf::object_machine_type () == EM_SPARC) && (symsec == 65522))
        return false;

      return true;
    }

    bool
    machine_relocation_check (const files::relocation& reloc)
    {
      /*
       * Drop some ARM relocations.
       */
      if (elf::object_machine_type () == EM_ARM)
      {
        switch (reloc.type)
        {
          case 40:  /* R_ARM_V4BX */
            return false;
          default:
            break;
        }
      }

      return true;
    }

    const char*
    section_name (int sec)
    {
      if (sec < rap_secs)
        return section_names[sec];
      throw rld::error ("Invalid section '" + rld::to_string (sec) + "'",
                        "rap::section-name");
    }

    /**
     * Update the offset taking into account the alignment.
     *
     * @param offset The current offset.
     * @param size The size to move the offset by.
     * @param alignment The alignment of the offset.
     * @return uint32_t The new aligned offset.
     */
    uint32_t align_offset (uint32_t offset, uint32_t size, uint32_t alignment)
    {
      offset += size;

      if (alignment > 1)
      {
        uint32_t mask = alignment - 1;
        if (offset & mask)
        {
          offset &= ~mask;
          offset += alignment;
        }
      }

      return offset;
    }

    relocation::relocation (const files::relocation& reloc,
                            const uint32_t           offset)
      : offset (reloc.offset + offset),
        info (reloc.info),
        addend (reloc.addend),
        symname (reloc.symname),
        symtype (reloc.symtype),
        symsect (reloc.symsect),
        symvalue (reloc.symvalue),
        symbinding (reloc.symbinding)
    {
    }

    section_detail::section_detail (uint32_t name,
                                    uint32_t offset,
                                    uint32_t id,
                                    uint32_t size)
      : name (name),
        offset (offset),
        id (id),
        size (size)
    {
    }

    osection::osection (const std::string& name,
                        uint32_t           offset,
                        uint32_t           size,
                        uint32_t           align,
                        uint32_t           relocs,
                        uint64_t           flags)
      : name (name),
        offset (offset),
        size (size),
        align (align),
        relocs (relocs),
        flags (flags)
    {
    }

    osection::osection ()
      : offset (0),
        size (0),
        align (0),
        relocs (0),
        flags (0)
    {
    }

    section::section ()
      : offset (0),
        rela (false)
    {
    }

    void
    section::clear ()
    {
      offset = 0;
      rela = false;
    }

    uint32_t
    section::size (uint32_t offset_) const
    {
      uint32_t end = offset_;
      if (end == 0)
        end = offset;
      for (size_t si = 0; si < osindexes.size (); ++si)
      {
        const osection& osec = get_osection (osindexes[si]);
        end = align_offset (end, 0, osec.align);
        end += osec.size;
      }
      return end - offset;
    }

    uint32_t
    section::alignment () const
    {
      if (!osindexes.empty ())
      {
        const osection& osec = get_osection (osindexes[0]);
        return osec.align;
      }
      return 0;
    }

    uint32_t
    section::alignment (int index) const
    {
      const osection& osec = get_osection (index);
      return osec.align;
    }

    void
    section::set_offset (const section& sec)
    {
      uint32_t align = alignment ();
      offset = align_offset (sec.offset, sec.size (), align);
      if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
        std::cout << "rap:section::set-offset: " << name
                  << " offset=" << offset
                  << " size=" << size ()
                  << " align=" << align
                  << " sec.offset=" << sec.offset
                  << " sec.size=" << sec.size (sec.offset)
                  << std::endl;
    }

    const osection&
    section::get_osection (int index) const
    {
      osections::const_iterator osi = osecs.find (index);
      if (osi == osecs.end ())
        throw rld::error ("Invalid object seciton index in '" + name +"': index=" +
                          rld::to_string (index),
                          "rap::section");
      return (*osi).second;
    }

    /**
     * Output helper function to report the sections in an object file. This is
     * useful when seeing the flags in the sections.
     */
    void
    section::output ()
    {
      if (!osindexes.empty ())
      {
        std::cout << ' ' << name
                  << ": size: " << size (offset)
                  << " offset: " << offset
                  << " rela: " << (rela ? "yes" : "no")
                  << std::endl;

        for (osecindexes::const_iterator osi = osindexes.begin ();
             osi != osindexes.end ();
             ++osi)
        {
          const osection& osec = get_osection (*osi);

          if (osec.size)
          {
            #define SF(f, i, c) if (osec.flags & (f)) flags[i] = c

            std::string flags ("--------------");

            SF (SHF_WRITE,            0, 'W');
            SF (SHF_ALLOC,            1, 'A');
            SF (SHF_EXECINSTR,        2, 'E');
            SF (SHF_MERGE,            3, 'M');
            SF (SHF_STRINGS,          4, 'S');
            SF (SHF_INFO_LINK,        5, 'I');
            SF (SHF_LINK_ORDER,       6, 'L');
            SF (SHF_OS_NONCONFORMING, 7, 'N');
            SF (SHF_GROUP,            8, 'G');
            SF (SHF_TLS,              9, 'T');
            SF (SHF_AMD64_LARGE,     10, 'a');
            SF (SHF_ENTRYSECT,       11, 'e');
            SF (SHF_COMDEF,          12, 'c');
            SF (SHF_ORDERED,         13, 'O');

            std::cout << "  " << std::left
                      << std::setw (15) << osec.name
                      << " " << flags
                      << " size: " << std::setw (5) << osec.size
                      << " align: " << std::setw (3) << osec.align
                      << " relocs: " << std::setw (4) << osec.relocs
                      << " offset: " << std::setw (5) << osec.offset
                      << std::hex
                      << " image: 0x" << offset + osec.offset
                      << std::dec << std::right << std::endl;
          }
        }
      }
    }

    /**
     * Helper for for_each to merge the related object sections into the RAP
     * section.
     */
    class section_merge
    {
    public:

      section_merge (object& obj, section& sec);

      ~section_merge ();

      void operator () (const files::section& fsec);

    private:

      object&  obj;
      section& sec;
    };

    section_merge::section_merge (object& obj, section& sec)
      : obj (obj),
        sec (sec)
    {
      sec.offset = 0;
      sec.rela = false;
    }

    section_merge::~section_merge ()
    {
      if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
        try
        {
          std::cout << "rap:section-merge: " << sec.name
                    << " size=" << sec.size ()
                    << " offset=" << sec.offset
                    << " " << obj.obj.name ().full ()  << std::endl;
        }
        catch ( rld::error re )
        {
          std::cerr << "error: "
                    << re.where << ": " << re.what
                    << std::endl;
        }
    }

    void
    section_merge::operator () (const files::section& fsec)
    {
      /*
       * Align the size up to the next alignment boundary and use that as the
       * offset for this object file section.
       */
      uint32_t offset = align_offset (sec.size (), 0, fsec.alignment);

      if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
        std::cout << "rap:section-merge: " << fsec.name
                  << " sec-size=" << sec.size ()
                  << " relocs=" << fsec.relocs.size ()
                  << " offset=" << offset
                  << " fsec.size=" << fsec.size
                  << " fsec.alignment=" << fsec.alignment
                  << " fsec.rela=" << fsec.rela
                  << " " << obj.obj.name ().full ()  << std::endl;

      /*
       * Add the object file's section offset to the map. This is needed
       * to adjust the external symbol offsets.
       */
      osection osec (fsec.name,
                     offset,
                     fsec.size,
                     fsec.alignment,
                     fsec.relocs.size (),
                     fsec.flags);
      sec.osecs[fsec.index] = osec;
      sec.osindexes.push_back (fsec.index);

      uint32_t rc = 0;

      for (files::relocations::const_iterator fri = fsec.relocs.begin ();
           fri != fsec.relocs.end ();
           ++fri, ++rc)
      {
        const files::relocation& freloc = *fri;
        bool                     merge_reloc = machine_relocation_check (freloc);

        if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
          std::cout << " " << std::setw (2) << sec.relocs.size ()
                    << '/' << std::setw (2) << rc << ':'
                    << " merge=" << merge_reloc
                    << std::hex
                    << " reloc.type=" << freloc.type
                    << " reloc.info=0x" << freloc.info
                    << std::dec
                    << " reloc.offset=" << freloc.offset
                    << " reloc.addend=" << freloc.addend
                    << " reloc.symtype=" << freloc.symtype
                    << " reloc.symsect=" << freloc.symsect
                    << " reloc.symbinding=" << freloc.symbinding
                    << std::endl;
        if (merge_reloc)
          sec.relocs.push_back (relocation (freloc, offset));
      }

      std::stable_sort (sec.relocs.begin (),
                        sec.relocs.end (),
                        reloc_symname_compare ());
      std::stable_sort (sec.relocs.begin (),
                        sec.relocs.end (),
                        reloc_offset_compare ());

      if (fsec.rela == true)
        sec.rela = fsec.rela;
    }

    external::external (const uint32_t name,
                        const sections sec,
                        const uint32_t value,
                        const uint32_t data)
      : name (name),
        sec (sec),
        value (value),
        data (data)
    {
    }

    external::external (const external& orig)
      : name (orig.name),
        sec (orig.sec),
        value (orig.value),
        data (orig.data)
    {
    }

    object::object (files::object& obj)
      : obj (obj)
    {
      /*
       * Set up the names of the sections.
       */
      for (int s = 0; s < rap_secs; ++s)
        secs[s].name = section_names[s];

      /*
       * Get the relocation records. Collect the various section types from the
       * object file into the RAP sections. Merge those sections into the RAP
       * sections.
       */

      obj.open ();
      try
      {
        obj.begin ();
        obj.load_relocations ();
        obj.end ();
      }
      catch (...)
      {
        obj.close ();
        throw;
      }
      obj.close ();

      obj.get_sections (text,   SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR);
      obj.get_sections (const_, SHT_PROGBITS, SHF_ALLOC, SHF_WRITE | SHF_EXECINSTR);
      obj.get_sections (ctor,   ".ctors");
      obj.get_sections (dtor,   ".dtors");
      obj.get_sections (data,   SHT_PROGBITS, SHF_ALLOC | SHF_WRITE);
      obj.get_sections (bss,    SHT_NOBITS,   SHF_ALLOC | SHF_WRITE);
      obj.get_sections (symtab, SHT_SYMTAB);
      obj.get_sections (strtab, ".strtab");

      std::for_each (text.begin (), text.end (),
                     section_merge (*this, secs[rap_text]));
      std::for_each (const_.begin (), const_.end (),
                     section_merge (*this, secs[rap_const]));
      std::for_each (ctor.begin (), ctor.end (),
                     section_merge (*this, secs[rap_ctor]));
      std::for_each (dtor.begin (), dtor.end (),
                     section_merge (*this, secs[rap_dtor]));
      std::for_each (data.begin (), data.end (),
                     section_merge (*this, secs[rap_data]));
      std::for_each (bss.begin (), bss.end (),
                     section_merge (*this, secs[rap_bss]));
    }

    object::object (const object& orig)
      : obj (orig.obj),
        text (orig.text),
        const_ (orig.const_),
        ctor (orig.ctor),
        dtor (orig.dtor),
        data (orig.data),
        bss (orig.bss),
        symtab (orig.symtab),
        strtab (orig.strtab)
    {
      for (int s = 0; s < rap_secs; ++s)
        secs[s] = orig.secs[s];
    }

    sections
    object::find (const uint32_t index) const
    {
      const files::section* sec;

      sec = files::find (text, index);
      if (sec)
        return rap_text;

      sec = files::find (const_, index);
      if (sec)
        return rap_const;

      sec = files::find (ctor, index);
      if (sec)
        return rap_ctor;

      sec = files::find (dtor, index);
      if (sec)
        return rap_dtor;

      sec = files::find (data, index);
      if (sec)
        return rap_data;

      sec = files::find (bss, index);
      if (sec)
        return rap_bss;

      throw rld::error ("Section index '" + rld::to_string (index) +
                        "' not found: " + obj.name ().full (), "rap::object");
    }

    uint32_t
    object::get_relocations () const
    {
      uint32_t relocs = 0;
      for (int s = 0; s < rap_secs; ++s)
        relocs += get_relocations (s);
      return relocs;
    }

    uint32_t
    object::get_relocations (int sec) const
    {
      if ((sec < 0) || (sec >= rap_secs))
        throw rld::error ("Invalid section index '" + rld::to_string (sec),
                          "rap::relocations");
      return secs[sec].relocs.size ();
    }

    void
    object::output ()
    {
      std::cout << "rap:object: " << obj.name ().full () << std::endl;
      secs[rap_text].output ();
      secs[rap_const].output ();
      secs[rap_ctor].output ();
      secs[rap_dtor].output ();
      secs[rap_data].output ();
      if (secs[rap_bss].size ())
        std::cout << " bss: size: " << secs[rap_bss].size () << std::endl;
    }

    image::image ()
    {
      clear ();
    }

    void
    image::layout (const files::object_list& app_objects,
                   const std::string&        init,
                   const std::string&        fini)
    {
      clear ();

      /*
       * Create the local objects which contain the layout information.
       */
      for (files::object_list::const_iterator aoi = app_objects.begin ();
           aoi != app_objects.end ();
           ++aoi)
      {
        files::object& app_obj = *(*aoi);

        if (!app_obj.valid ())
          throw rld::error ("Not valid: " + app_obj.name ().full (),
                            "rap::layout");

        objs.push_back (object (app_obj));
      }

      for (objects::iterator oi = objs.begin (), poi = objs.begin ();
           oi != objs.end ();
           ++oi)
      {
        object& obj = *oi;

        /*
         * Update the offsets in the object file. We need the object's offset
         * to set the relocation offset's correctly as they are relative to the
         * object file.
         */
        if (oi != objs.begin ())
        {
          object& pobj = *poi;
          for (int s = 0; s < rap_secs; ++s)
          {
            obj.secs[s].set_offset (pobj.secs[s]);
            sec_size[s] = obj.secs[s].offset + obj.secs[s].size ();
            sec_align[s] = obj.secs[s].alignment ();
            if (obj.secs[s].rela == true)
              sec_rela[s] = obj.secs[s].rela;
          }
          ++poi;
        }
        else
        {
          for (int s = 0; s < rap_secs; ++s)
          {
            sec_size[s] =  obj.secs[s].size ();
            sec_align[s] = obj.secs[s].alignment ();
            if (obj.secs[s].rela == true)
              sec_rela[s] = true;
          }
        }

        collect_symbols (obj);

        relocs_size += obj.get_relocations ();

        if (rld::verbose () >= RLD_VERBOSE_DETAILS)
          obj.output ();
      }

      init_off = strtab.size () + 1;
      strtab += '\0';
      strtab += init;

      fini_off = strtab.size () + 1;
      strtab += '\0';
      strtab += fini;

      if (rld::verbose () >= RLD_VERBOSE_INFO)
      {
        uint32_t total = (sec_size[rap_text] + sec_size[rap_const] +
                          sec_size[rap_data] + sec_size[rap_bss] +
                          symtab_size + strtab.size() + relocs_size);
        std::cout << "rap::layout: total:" << total
                  << " text:" << sec_size[rap_text]
                  << " const:" << sec_size[rap_const]
                  << " ctor:" << sec_size[rap_ctor]
                  << " dtor:" << sec_size[rap_dtor]
                  << " data:" << sec_size[rap_data]
                  << " bss:" << sec_size[rap_bss]
                  << " symbols:" << symtab_size << " (" << externs.size () << ')'
                  << " strings:" << strtab.size () + 1
                  << " relocs:" << relocs_size
                  << std::endl;
      }
    }

    void
    image::collect_symbols (object& obj)
    {
      symbols::pointers& esyms = obj.obj.external_symbols ();
      for (symbols::pointers::const_iterator ei = esyms.begin ();
           ei != esyms.end ();
           ++ei)
      {
        const symbols::symbol& sym = *(*ei);

        if ((sym.type () == STT_OBJECT) || (sym.type () == STT_FUNC) || (sym.type () == STT_NOTYPE))
        {
          if ((sym.binding () == STB_GLOBAL) || (sym.binding () == STB_WEAK))
          {
            int symsec = sym.section_index ();

            /*
             * Do not noting if the symbol is reject at the machine level.
             */
            if (!machine_symbol_check (sym))
              continue;

            sections    rap_sec = obj.find (symsec);
            section&    sec = obj.secs[rap_sec];
            std::size_t name;

            /*
             * See if the name is already in the string table.
             */
            name = find_in_strtab (sym.name ());

            if (name == std::string::npos)
            {
              name = strtab.size () + 1;
              strtab += '\0';
              strtab += sym.name ();
            }

            /*
             * The symbol's value is the symbols value plus the offset of the
             * object file's section offset in the RAP section.
             */
            externs.push_back (external (name,
                                         rap_sec,
                                         sec.offset + sec.osecs[symsec].offset +
                                         sym.value (),
                                         sym.info ()));

            symtab_size += external::rap_size;
          }
        }
      }
    }

    void
    image::write (compress::compressor& comp)
    {
      /*
       * Start with the machine type so the target can check the applicatiion
       * is ok and can be loaded. Add the init and fini labels to the string
       * table and add the references to the string table next. Follow this
       * with the section details then the string table and symbol table then
       * finally the relocation records.
       */

      if (rld::verbose () >= RLD_VERBOSE_INFO)
        std::cout << "rap:output: machine=" << comp.transferred () << std::endl;

      comp << elf::object_machine_type ()
           << elf::object_datatype ()
           << elf::object_class ();

      /*
       * The init and fini label offsets. Then the symbol table and string
       * table sizes.
       */

      if (rld::verbose () >= RLD_VERBOSE_INFO)
        std::cout << "rap:output: header=" << comp.transferred () << std::endl;

      comp << init_off
           << fini_off
           << symtab_size
           << (uint32_t) strtab.size () + 1
           << (uint32_t) 0;

      /*
       * Output file details
       */
      if (add_obj_details)
      {
        write_details (comp);
      }
      else
      {
        comp << (uint32_t)0; /* No file details */
      }

      /*
       * The sections.
       */
      for (int s = 0; s < rap_secs; ++s)
        comp << sec_size[s]
             << sec_align[s];

      /*
       * Output the sections from each object file.
       */
      write (comp, rap_text);
      write (comp, rap_const);
      write (comp, rap_ctor);
      write (comp, rap_dtor);
      write (comp, rap_data);

      if (rld::verbose () >= RLD_VERBOSE_INFO)
        std::cout << "rap:output: strtab=" << comp.transferred () << std::endl;

      strtab += '\0';
      comp << strtab;

      if (rld::verbose () >= RLD_VERBOSE_INFO)
        std::cout << "rap:output: symbols=" << comp.transferred () << std::endl;

      write_externals (comp);

      if (rld::verbose () >= RLD_VERBOSE_INFO)
        std::cout << "rap:output: relocs=" << comp.transferred () << std::endl;

      write_relocations (comp);
    }

    /**
     * Helper for for_each to write out the various sections.
     */
    class section_writer
    {
    public:

      section_writer (image&                img,
                      compress::compressor& comp,
                      sections              sec);

      void operator () (object& obj);

    private:

      image&                img;
      compress::compressor& comp;
      sections              sec;
      uint32_t              offset;
    };

    section_writer::section_writer (image&                img,
                                    compress::compressor& comp,
                                    sections              sec)
      : img (img),
        comp (comp),
        sec (sec),
        offset (0)
    {
      if (rld::verbose () >= RLD_VERBOSE_INFO)
        std::cout << "rap:output: " << section_names[sec]
                  << ": offset=" << comp.transferred ()
                  << " size=" << img.section_size (sec) << std::endl;
    }

    void
    section_writer::operator () (object& obj)
    {
      if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
        std::cout << "rap:writing: " << section_names[sec] << std::endl;

      switch (sec)
      {
        case rap_text:
          img.write (comp, obj.obj, obj.text, offset);
          break;
        case rap_const:
          img.write (comp, obj.obj, obj.const_, offset);
          break;
        case rap_ctor:
          img.write (comp, obj.obj, obj.ctor, offset);
          break;
        case rap_dtor:
          img.write (comp, obj.obj, obj.dtor, offset);
          break;
        case rap_data:
          img.write (comp, obj.obj, obj.data, offset);
          break;
        default:
          break;
      }
    }

    void
    image::write (compress::compressor& comp, sections sec)
    {
      uint32_t image_offset = comp.transferred ();

      std::for_each (objs.begin (), objs.end (),
                     section_writer (*this, comp, sec));

      uint32_t written = comp.transferred () - image_offset;

      if (written != sec_size[sec])
      {
        std::string msg = "Image output size does not match layout size: ";
        msg += section_names[sec];
        msg += ": layout-size=" + rld::to_string (sec_size[sec]);
        msg += " image-size=" + rld::to_string (written);
        throw rld::error (msg, "rap::write");
      }
    }

    void
    image::write (compress::compressor&  comp,
                  files::object&         obj,
                  const files::sections& secs,
                  uint32_t&              offset)
    {
      uint32_t size = 0;

      obj.open ();

      try
      {
        obj.begin ();

        if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
          std::cout << "rap:write sections: " << obj.name ().full () << std::endl;

        for (files::sections::const_iterator si = secs.begin ();
             si != secs.end ();
             ++si)
        {
          const files::section& sec = *si;
          uint32_t              unaligned_offset = offset + size;

          offset = align_offset (offset, size, sec.alignment);

          if (offset != unaligned_offset)
          {
            char ee = '\xee';
            for (uint32_t p = 0; p < (offset - unaligned_offset); ++p)
              comp.write (&ee, 1);
          }

          comp.write (obj, sec.offset, sec.size);

          if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
            std::cout << " sec: " << sec.index << ' ' << sec.name
                      << " offset=" << offset
                      << " size=" << sec.size
                      << " align=" << sec.alignment
                      << " padding=" << (offset - unaligned_offset)  << std::endl;

          size = sec.size;
        }

        offset += size;

        if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
          std::cout << " total size=" << offset << std::endl;

        obj.end ();
      }
      catch (...)
      {
        obj.close ();
        throw;
      }

      obj.close ();
    }

    void
    image::write_externals (compress::compressor& comp)
    {
      int count = 0;
      for (externals::const_iterator ei = externs.begin ();
           ei != externs.end ();
           ++ei, ++count)
      {
        const external& ext = *ei;

        if (rld::verbose () >= RLD_VERBOSE_TRACE)
          std::cout << "rap:externs: " << count
                    << " name=" << &strtab[ext.name] << " (" << ext.name << ')'
                    << " section=" << section_names[ext.sec]
                    << " data=" << ext.data
                    << " value=0x" << std::hex << ext.value << std::dec
                    << std::endl;

        if ((ext.data & 0xffff0000) != 0)
          throw rld::error ("Data value has data in bits higher than 15",
                            "rap::write-externs");

        comp << (uint32_t) ((ext.sec << 16) | ext.data)
             << ext.name
             << ext.value;
      }
    }

    void
    image::write_relocations (compress::compressor& comp)
    {
      for (int s = 0; s < rap_secs; ++s)
      {
        uint32_t count = get_relocations (s);
        uint32_t sr = 0;
        uint32_t header;

        if (rld::verbose () >= RLD_VERBOSE_TRACE)
          std::cout << "rap:relocation: section:" << section_names[s]
                    << " relocs=" << count
                    << " rela=" << (char*) (sec_rela[s] ? "yes" : "no")
                    << std::endl;

        header = count;
        header |= sec_rela[s] ? RAP_RELOC_RELA : 0;

        comp << header;

        for (objects::iterator oi = objs.begin ();
             oi != objs.end ();
             ++oi)
        {
          object&      obj = *oi;
          section&     sec = obj.secs[s];
          relocations& relocs = sec.relocs;
          uint32_t     rc = 0;

          if (rld::verbose () >= RLD_VERBOSE_TRACE)
            std::cout << " relocs=" << sec.relocs.size ()
                      << " sec.offset=" << sec.offset
                      << " sec.size=" << sec.size ()
                      << " sec.align=" << sec.alignment ()
                      << "  " << obj.obj.name ().full ()  << std::endl;

          for (relocations::const_iterator ri = relocs.begin ();
               ri != relocs.end ();
               ++ri)
          {
            const relocation& reloc = *ri;
            uint32_t          info = GELF_R_TYPE (reloc.info);
            uint32_t          offset;
            uint32_t          addend = reloc.addend;
            bool              write_addend = sec.rela;
            bool              write_symname = false;

            if (reloc.symsect == 0)
              continue;

            offset = sec.offset + reloc.offset;

            if (rld::verbose () >= RLD_VERBOSE_TRACE)
              std::cout << "  " << std::setw (2) << sr
                        << '/' << std::setw (2) << rc << ':'
                        << std::hex
                        << " reloc.info=0x" << reloc.info
                        << std::dec
                        << " reloc.offset=" << reloc.offset
                        << " reloc.addend=" << reloc.addend
                        << " reloc.symtype=" << reloc.symtype
                        << " reloc.symsect=" << reloc.symsect
                        << " (" << obj.obj.get_section (reloc.symsect).name << ')'
                        << " reloc.symvalue=" << reloc.symvalue
                        << " reloc.symbinding=" << reloc.symbinding
                        << std::endl;

            if ((reloc.symtype == STT_SECTION) || (reloc.symbinding == STB_LOCAL))
            {
              int rap_symsect = obj.find (reloc.symsect);

              /*
               * Bit 31 clear, bits 30:8 RAP section index.
               */
              info |= rap_symsect << 8;

              addend += (obj.secs[rap_symsect].offset +
                         obj.secs[rap_symsect].osecs[reloc.symsect].offset +
                         reloc.symvalue);

              write_addend = true;

              if (rld::verbose () >= RLD_VERBOSE_TRACE)
                std::cout << "  " << std::setw (2) << sr
                          << '/' << std::setw (2) << rc << ':'
                          << " rsym: sect=" << section_names[rap_symsect]
                          << " rap_symsect=" << rap_symsect
                          << " sec.offset=" << obj.secs[rap_symsect].offset
                          << " sec.osecs=" << obj.secs[rap_symsect].osecs[reloc.symsect].offset
                          << " addend=" << addend
                          << std::endl;
            }
            else
            {
              /*
               * Bit 31 must be set. Bit 30 determines the type of string and
               * bits 29:8 the strtab offset or the size of the appended
               * string.
               */

              info |= RAP_RELOC_STRING;

              std::size_t size = find_in_strtab (reloc.symname);

              if (size == std::string::npos)
              {
                /*
                 * Bit 30 clear, the size of the symbol name.
                 */
                info |= reloc.symname.size () << 8;
                write_symname = true;
              }
              else
              {
                /*
                 * Bit 30 set, the offset in the strtab.
                 */
                info |= RAP_RELOC_STRING_EMBED | (size << 8);
              }
            }

            if (rld::verbose () >= RLD_VERBOSE_TRACE)
            {
              std::cout << "  " << std::setw (2) << sr << '/'
                        << std::setw (2) << rc
                        << std::hex << ": reloc: info=0x" << info << std::dec
                        << " offset=" << offset;
              if (write_addend)
                std::cout << " addend=" << addend;
              if ((info & RAP_RELOC_STRING) != 0)
              {
                std::cout << " symname=" << reloc.symname;
                if (write_symname)
                  std::cout << " (appended)";
              }
              std::cout << std::hex
                        << " reloc.info=0x" << reloc.info << std::dec
                        << " reloc.offset=" << reloc.offset
                        << " reloc.symtype=" << reloc.symtype
                        << std::endl;
            }

            comp << info << offset;

            if (write_addend)
              comp << addend;

            if (write_symname)
              comp << reloc.symname;

            ++rc;
            ++sr;
          }
        }
      }
    }

    void image::write_details (compress::compressor& comp)
    {

      std::string strtable;
      uint32_t pos = 0;

      section_details s_details;


      if (rld::verbose () >= RLD_VERBOSE_TRACE)
      {
        std::cout << "rap:file details" << std::endl
                  << "  total " << objs.size () <<" files" << std::endl;
      }

      comp << (uint32_t)(objs.size ());

      /* rpath for rap file */
      if (rld::verbose () >= RLD_VERBOSE_TRACE)
      {
        std::cout << "rap:file rpath=" << rld::rap::rpath << std::endl;
      }

      comp << (uint32_t)rld::rap::rpath.length ();

      if (rld::rap::rpath.length () > 0)
      {
        strtable += rld::rap::rpath;
      }

      for (objects::iterator oi = objs.begin ();
           oi != objs.end ();
           ++oi)
      {
          object& obj = *oi;

          /* obj full name */
          strtable += obj.obj.name ().full ();
          strtable += '\0';
      }

      pos = strtable.length ();

      uint32_t sec_num = 0;
      for (objects::iterator oi = objs.begin ();
           oi != objs.end ();
           ++oi)
      {
        object& obj = *oi;

        if (rld::verbose () >= RLD_VERBOSE_TRACE)
          std::cout << "file:" << obj.obj.name ().full () << std::endl;

        for (int s = 0; s < rap_secs; ++s)
        {
          section& sec = obj.secs[s];

          if (rld::verbose () >= RLD_VERBOSE_TRACE)
          {
            std::cout << "rap:section: " << sec.name << " "
                         "offset= " << sec.offset << std::endl;
          }

          for (size_t si = 0; si < sec.osindexes.size (); ++si)
          {
            const osection& osec = sec.get_osection (sec.osindexes[si]);

            strtable += osec.name;
            strtable += '\0';

            /* sec.offset + osec.offset is the offset in the rap section */
            s_details.push_back (section_detail (pos,
                                                 sec.offset + osec.offset,
                                                 s,
                                                 osec.size));

            pos = strtable.length ();

            if (rld::verbose () >= RLD_VERBOSE_TRACE)
            {
              std::cout << "osec.name=" << osec.name << " "
                        << "osec.offset=" << osec.offset << " "
                        << "osec.size=" << osec.size << std::endl;
            }
          }
        }

        /* Output section numbers*/
        comp << (uint32_t)((s_details.size () - sec_num));
        if (rld::verbose () >= RLD_VERBOSE_TRACE)
          std::cout << "sec_num:" << s_details.size () - sec_num  << std::endl;
        sec_num = s_details.size ();
      }

      comp << (uint32_t)(strtable.size ());
      if (rld::verbose () >= RLD_VERBOSE_TRACE)
        std::cout << "total detail size:" << strtable.size () << std::endl;

      comp << strtable;

      for (section_details::const_iterator si = s_details.begin ();
           si != s_details.end ();
           ++si)
      {
        const section_detail& sec_detail = *si;
        comp << (uint32_t)(sec_detail.name);

        if (sec_detail.id > 0xf)
          std::cout << "Out max rap section id 15\n" << std::endl;

        comp << (uint32_t)((sec_detail.id << 28) | sec_detail.offset);
        comp << (uint32_t)(sec_detail.size);
      }
    }

    uint32_t
    image::get_relocations (int sec) const
    {
      if ((sec < 0) || (sec >= rap_secs))
        throw rld::error ("Invalid section index '" + rld::to_string (sec),
                          "rap::image::relocations");

      uint32_t relocs = 0;

      for (objects::const_iterator oi = objs.begin ();
           oi != objs.end ();
           ++oi)
      {
        const object& obj = *oi;
        relocs += obj.get_relocations (sec);
      }

      return relocs;
    }

    void
    image::clear ()
    {
      for (int s = 0; s < rap_secs; ++s)
      {
        sec_size[s] = 0;
        sec_align[s] = 0;
        sec_rela[s] = false;
      }
      symtab_size = 0;
      strtab.clear ();
      relocs_size = 0;
      init_off = 0;
      fini_off = 0;
    }

    uint32_t
    image::section_size (sections sec) const
    {
      if ((sec < 0) || (sec >= rap_secs))
        throw rld::error ("Invalid section index '" + rld::to_string (sec),
                          "rap::image::section_size");
      return sec_size[sec];
    }

    std::size_t
    image::find_in_strtab (const std::string& symname)
    {
      std::size_t pos = 0;
      while (pos < strtab.size ())
      {
        std::size_t off = strtab.find (symname, pos);
        if (off == std::string::npos)
          break;
        if (::strlen (strtab.c_str () + off) == symname.size ())
          return off;
        pos = off + 1;
      }
      return std::string::npos;
    }

    void
    write (files::image&             app,
           const std::string&        init,
           const std::string&        fini,
           const files::object_list& app_objects,
           const symbols::table&     /* symbols */) /* Add back for incremental
                                                     * linking */
    {
      std::string header;

      header = "RAP,00000000,0002,LZ77,00000000\n";
      app.write (header.c_str (), header.size ());

      compress::compressor compressor (app, 2 * 1024);
      image                rap;

      rap.layout (app_objects, init, fini);
      rap.write (compressor);

      compressor.flush ();

      std::ostringstream length;

      length << std::setfill ('0') << std::setw (8)
             << header.size () + compressor.compressed ();

      header.replace (4, 8, length.str ());

      app.seek (0);
      app.write (header.c_str (), header.size ());

      if (rld::verbose () >= RLD_VERBOSE_INFO)
      {
        int pcent = (compressor.compressed () * 100) / compressor.transferred ();
        int premand = (((compressor.compressed () * 1000) + 500) /
                       compressor.transferred ()) % 10;
        std::cout << "rap: objects: " << app_objects.size ()
                  << ", size: " << compressor.compressed ()
                  << ", expanded: " << compressor.transferred ()
                  << ", compressed: " << pcent << '.' << premand << '%'
                  << std::endl;
      }
    }

  }
}
