//===- ELFReader.cpp ------------------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include <llvm/ADT/StringRef.h>
#include <llvm/ADT/Twine.h>
#include <llvm/Support/ELF.h>
#include <llvm/Support/Host.h>

#include <mcld/MC/MCLinker.h>
#include <mcld/LD/ELFReader.h>
#include <mcld/Target/GNULDBackend.h>
#include <mcld/Support/MemoryArea.h>
#include <mcld/Support/MemoryRegion.h>
#include <mcld/Support/MsgHandling.h>
#include <cstring>

using namespace mcld;

//===----------------------------------------------------------------------===//
// ELFReaderIF
/// getLDSectionKind
LDFileFormat::Kind
ELFReaderIF::getLDSectionKind(uint32_t pType, const char* pName) const
{
  // name rules
  llvm::StringRef name(pName);
  if (name.startswith(".debug") ||
      name.startswith(".zdebug") ||
      name.startswith(".gnu.linkonce.wi.") ||
      name.startswith(".line") ||
      name.startswith(".stab"))
    return LDFileFormat::Debug;
  if (name.startswith(".comment"))
    return LDFileFormat::MetaData;
  if (name.startswith(".interp") || name.startswith(".dynamic"))
    return LDFileFormat::Note;
  if (name.startswith(".eh_frame"))
    return LDFileFormat::EhFrame;
  if (name.startswith(".eh_frame_hdr"))
    return LDFileFormat::EhFrameHdr;
  if (name.startswith(".gcc_except_table"))
    return LDFileFormat::GCCExceptTable;

  // type rules
  switch(pType) {
  case llvm::ELF::SHT_NULL:
    return LDFileFormat::Null;
  case llvm::ELF::SHT_INIT_ARRAY:
  case llvm::ELF::SHT_FINI_ARRAY:
  case llvm::ELF::SHT_PREINIT_ARRAY:
  case llvm::ELF::SHT_PROGBITS:
    return LDFileFormat::Regular;
  case llvm::ELF::SHT_SYMTAB:
  case llvm::ELF::SHT_DYNSYM:
  case llvm::ELF::SHT_STRTAB:
    return LDFileFormat::NamePool;
  case llvm::ELF::SHT_RELA:
  case llvm::ELF::SHT_REL:
    return LDFileFormat::Relocation;
  case llvm::ELF::SHT_NOBITS:
    return LDFileFormat::BSS;
  case llvm::ELF::SHT_DYNAMIC:
  case llvm::ELF::SHT_NOTE:
    return LDFileFormat::Note;
  case llvm::ELF::SHT_HASH:
  case llvm::ELF::SHT_SHLIB:
    return LDFileFormat::MetaData;
  case llvm::ELF::SHT_GROUP:
    return LDFileFormat::Group;
  case llvm::ELF::SHT_GNU_versym:
  case llvm::ELF::SHT_GNU_verdef:
  case llvm::ELF::SHT_GNU_verneed:
    return LDFileFormat::Version;
  default:
    if ((pType >= llvm::ELF::SHT_LOPROC && pType <= llvm::ELF::SHT_HIPROC) ||
        (pType >= llvm::ELF::SHT_LOOS && pType <= llvm::ELF::SHT_HIOS) ||
        (pType >= llvm::ELF::SHT_LOUSER && pType <= llvm::ELF::SHT_HIUSER))
      return LDFileFormat::Target;
    fatal(diag::err_unsupported_section) << pName << pType;
  }
  return LDFileFormat::MetaData;
}

/// getSymType
ResolveInfo::Type ELFReaderIF::getSymType(uint8_t pInfo, uint16_t pShndx) const
{
  ResolveInfo::Type result = static_cast<ResolveInfo::Type>(pInfo & 0xF);
  if (llvm::ELF::SHN_ABS == pShndx && ResolveInfo::Section == result) {
    // In Mips, __gp_disp is a special section symbol. Its name comes from
    // .strtab, not .shstrtab. However, it is unique. Only it is also a ABS
    // symbol. So here is a tricky to identify __gp_disp and convert it to
    // Object symbol.
    return ResolveInfo::Object;
  }

  return result;
}

/// getSymDesc
ResolveInfo::Desc ELFReaderIF::getSymDesc(uint16_t pShndx, const Input& pInput) const
{
  if (pShndx == llvm::ELF::SHN_UNDEF)
    return ResolveInfo::Undefined;

  if (pShndx < llvm::ELF::SHN_LORESERVE) {
    // an ELF symbol defined in a section which we are not including
    // must be treated as an Undefined.
    // @ref Google gold linker: symtab.cc: 1086
    if (NULL == pInput.context()->getSection(pShndx))
      return ResolveInfo::Undefined;
    return ResolveInfo::Define;
  }

  if (pShndx == llvm::ELF::SHN_ABS)
    return ResolveInfo::Define;

  if (pShndx == llvm::ELF::SHN_COMMON)
    return ResolveInfo::Common;

  // FIXME: ELF weak alias should be ResolveInfo::Indirect
  return ResolveInfo::NoneDesc;
}

/// getSymBinding
ResolveInfo::Binding
ELFReaderIF::getSymBinding(uint8_t pBinding, uint16_t pShndx, uint8_t pVis) const
{

  // TODO:
  // if --just-symbols option is enabled, the symbol must covert to Absolute

  switch(pBinding) {
  case llvm::ELF::STB_LOCAL:
    return ResolveInfo::Local;
  case llvm::ELF::STB_GLOBAL:
    return ResolveInfo::Global;
  case llvm::ELF::STB_WEAK:
    return ResolveInfo::Weak;
  }

  if (pShndx == llvm::ELF::SHN_ABS)
    return ResolveInfo::Absolute;

  return ResolveInfo::NoneBinding;
}

/// getSymFragmentRef
FragmentRef*
ELFReaderIF::getSymFragmentRef(Input& pInput,
                               MCLinker& pLinker,
                               uint16_t pShndx,
                               uint32_t pOffset) const
{

  if (pShndx == llvm::ELF::SHN_UNDEF || pShndx >= llvm::ELF::SHN_LORESERVE)
    return NULL;

  LDSection* sect_hdr = pInput.context()->getSection(pShndx);

  if (NULL == sect_hdr)
    unreachable(diag::unreachable_invalid_section_idx) << pShndx
                                                       << pInput.path().native();

  FragmentRef* result = pLinker.getLayout().getFragmentRef(*sect_hdr, pOffset);
  return result;
}

/// getSymVisibility
ResolveInfo::Visibility
ELFReaderIF::getSymVisibility(uint8_t pVis) const
{
  return static_cast<ResolveInfo::Visibility>(pVis);
}

/// getSymValue - get the section offset of the symbol.
uint64_t ELFReaderIF::getSymValue(uint64_t pValue,
                                  uint16_t pShndx,
                                  const Input& pInput) const
{
  if (Input::Object == pInput.type()) {
    // In relocatable files, st_value holds alignment constraints for a symbol
    // whose section index is SHN_COMMON
    if (pShndx == llvm::ELF::SHN_COMMON || pShndx == llvm::ELF::SHN_ABS) {
      return pValue;
    }

    // In relocatable files, st_value holds a section offset for a defined symbol.
    // TODO:
    // if --just-symbols option are enabled, convert the value from section offset
    // to virtual address by adding input section's virtual address.
    // The section's virtual address in relocatable files is normally zero, but
    // people can use link script to change it.
    return pValue;
  }

  // In executable and shared object files, st_value holds a virtual address.
  // the virtual address is useless during linking.
  return 0x0;
}

bool ELFReaderIF::readEhFrame(Input& pInput,
                              MCLinker& pLinker,
                              LDSection& pInputSectHdr) const
{
  LDSection& out_sect = pLinker.getOrCreateOutputSectHdr(pInputSectHdr.name(),
                                                         pInputSectHdr.kind(),
                                                         pInputSectHdr.type(),
                                                         pInputSectHdr.flag());

  size_t size = pLinker.addEhFrame(pInput, pInputSectHdr, *pInput.memArea());

  out_sect.setSize(out_sect.size() + size);
  return true;
}