/*
 * Copyright (c) 2013-2014 Freescale Semiconductor, Inc.
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
#if !defined(_StELFFile_h_)
#define _StELFFile_h_

#include <iostream>
#include <map>
#include <stdexcept>
#include <string>
#include <vector>

#include "blfwk/ELF.h"
#include "blfwk/stdafx.h"

//! Variations of the ARM ELF format.
typedef enum
{
    eIllegalVariant = 0, //!< Illegal variant.
    eARMVariant = 1,     //!< Standard ARM ELF specification.
    eGHSVariant,         //!< Green Hills Software variant.
    eGCCVariant          //!< GNU Compiler Collection variant.
} ELFVariant_t;

//! Possible ARM ELF symbol types.
typedef enum
{
    eUnknownSymbol,
    eARMSymbol,
    eThumbSymbol,
    eDataSymbol
} ARMSymbolType_t;

/*!
 * \brief Parser for Executable and Linking Format (ELF) files.
 *
 * The stream passed into the constructor needs to stay open for the life
 * of the object. This is because calls to getSectionDataAtIndex() and
 * getSegmentDataAtIndex() read the data directly from the input stream.
 */
class StELFFile
{
public:
    typedef std::vector<Elf32_Shdr>::const_iterator const_section_iterator;
    typedef std::vector<Elf32_Phdr>::const_iterator const_segment_iterator;

public:
    //! \brief Constructor.
    StELFFile(std::istream &inStream);

    //! \brief Destructor.
    virtual ~StELFFile();

    //! \name File format variant
    //@{
    //! \brief Return the ELF format variant to which this file is set.
    virtual ELFVariant_t ELFVariant() { return m_elfVariant; }
    //! \brief Set the ELF format variation to either #eARMVariant or #eGHSVariant.
    virtual void setELFVariant(ELFVariant_t variant) { m_elfVariant = variant; }
    //@}

    //! \name File name
    //@{
    virtual void setName(const std::string &inName) { m_name = inName; }
    virtual std::string getName() const { return m_name; }
    //@}

    //! \name ELF header
    //@{
    //! \brief Returns the ELF file header.
    inline const Elf32_Ehdr &getFileHeader() const { return m_header; }
    //@}

    //! \name Sections
    //! Methods pertaining to the object file's sections.
    //@{
    //! \brief Returns the number of sections in the file.
    inline unsigned getSectionCount() const { return static_cast<unsigned>(m_sectionHeaders.size()); }
    //! \brief Returns a reference to section number \a inIndex.
    const Elf32_Shdr &getSectionAtIndex(unsigned inIndex) const;

    inline const_section_iterator getSectionBegin() const { return m_sectionHeaders.begin(); }
    inline const_section_iterator getSectionEnd() const { return m_sectionHeaders.end(); }
    //! \brief Returns the index of the section with the name \a inName.
    unsigned getIndexOfSectionWithName(const std::string &inName);

    //! \brief Returns the data for the section.
    uint8_t *getSectionDataAtIndex(unsigned inIndex);

    //! \brief Returns the data for the section.
    uint8_t *getSectionData(const_section_iterator inSection);
    //@}

    //! \name Segments
    //! Methods for accessing the file's program headers for segments.
    //@{
    //! \brief Returns the number of segments, or program headers, in the file.
    inline unsigned getSegmentCount() const { return static_cast<unsigned>(m_programHeaders.size()); }
    //! \brief Returns a reference to the given segment.
    const Elf32_Phdr &getSegmentAtIndex(unsigned inIndex) const;

    inline const_segment_iterator getSegmentBegin() const { return m_programHeaders.begin(); }
    inline const_segment_iterator getSegmentEnd() const { return m_programHeaders.end(); }
    //! \brief Returns the data of the specified segment.
    uint8_t *getSegmentDataAtIndex(unsigned inIndex);

    //! \brief Returns the data of the specified segment.
    uint8_t *getSegmentData(const_segment_iterator inSegment);
    //@}

    //! \name String table
    //! Methods for accessing the string tables.
    //@{
    //! \brief Returns a string from the file's section name string table.
    std::string getSectionNameAtIndex(unsigned inIndex);

    //! \brief Returns a string from any string table in the object file.
    std::string getStringAtIndex(unsigned inStringTableSectionIndex, unsigned inStringIndex);
    //@}

    //! \name Symbol table
    //! Methods for accessing the object file's symbol table. Currently only
    //! a single symbol table with the section name ".symtab" is supported.
    //@{
    //! \brief Returns the number of symbols in the default ".symtab" symbol table.
    unsigned getSymbolCount();

    //! \brief Returns the symbol with index \a inIndex.
    const Elf32_Sym &getSymbolAtIndex(unsigned inIndex);

    //! \brief Returns the section index of the string table containing symbol names.
    unsigned getSymbolNameStringTableIndex() const;

    //! \brief Returns the name of the symbol described by \a inSymbol.
    std::string getSymbolName(const Elf32_Sym &inSymbol);

    unsigned getIndexOfSymbolAtAddress(uint32_t symbolAddress, bool strict = true);

    ARMSymbolType_t getTypeOfSymbolAtIndex(unsigned symbolIndex);
    //@}

    //! \name Debugging
    //@{
    void dumpSections();
    void dumpSymbolTable();
    //@}

protected:
    std::istream &m_stream;                   //!< The source stream for the ELF file.
    ELFVariant_t m_elfVariant;                //!< Variant of the ARM ELF format specification.
    std::string m_name;                       //!< File name. (optional)
    Elf32_Ehdr m_header;                      //!< The ELF file header.
    std::vector<Elf32_Shdr> m_sectionHeaders; //!< All of the section headers.
    std::vector<Elf32_Phdr> m_programHeaders; //!< All of the program headers.
    unsigned m_symbolTableIndex;              //!< Index of ".symtab" section, or #SHN_UNDEF if not present.

    /*!
     * Little structure containing information about cached section data.
     */
    struct SectionDataInfo
    {
        uint8_t *m_data; //!< Pointer to section data.
        unsigned m_size; //!< Section data size in bytes.
        bool m_swapped;  //!< Has this section been byte swapped yet? Used for symbol table.
    };
    typedef std::map<unsigned, SectionDataInfo> SectionDataMap;
    SectionDataMap m_sectionDataCache; //!< Cached data of sections.

    //! \brief Reads a section's data either from cache or from disk.
    SectionDataInfo &getCachedSectionData(unsigned inSectionIndex);

    //! \brief Reads the file, section, and program headers into memory.
    void readFileHeaders();

    uint8_t *readSectionData(const Elf32_Shdr &inHeader);
    uint8_t *readSegmentData(const Elf32_Phdr &inHeader);

    //! \brief Byte swaps the symbol table data into host endianness.
    void byteSwapSymbolTable(const Elf32_Shdr &header, SectionDataInfo &info);
};

/*!
 * \brief Simple exception thrown to indicate an error in the input ELF file format.
 */
class StELFFileException : public std::runtime_error
{
public:
    //! \brief Default constructor.
    StELFFileException(const std::string &inMessage)
        : std::runtime_error(inMessage)
    {
    }
};

#endif // _StELFFile_h_
