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

#include <mcld/LD/EhFrame.h>

#include <llvm/Support/Dwarf.h>
#include <llvm/Support/Host.h>

#include <mcld/MC/MCLinker.h>
#include <mcld/Target/TargetLDBackend.h>
#include <mcld/Support/MsgHandling.h>

using namespace mcld;

//==========================
// EhFrame
EhFrame::EhFrame()
 : m_fCanRecognizeAll(true) {
}

EhFrame::~EhFrame()
{
}

uint64_t EhFrame::readEhFrame(Layout& pLayout,
                              const TargetLDBackend& pBackend,
                              SectionData& pSD,
                              const Input& pInput,
                              LDSection& pSection,
                              MemoryArea& pArea)
{
  MemoryRegion* region = pArea.request(
                     pInput.fileOffset() + pSection.offset(), pSection.size());
  // an empty .eh_frame
  if (NULL == region) {
    return 0;
  }

  ConstAddress eh_start = region->start();
  ConstAddress eh_end = region->end();
  ConstAddress p = eh_start;

  // read the Length filed
  uint32_t len = readVal(p, pBackend.isLittleEndian());

  // This CIE is a terminator if the Length field is 0, return 0 to handled it
  // as an ordinary input.
  if (0 == len) {
    pArea.release(region);
    return 0;
  }

  if (0xffffffff == len) {
    debug(diag::debug_eh_unsupport) << pInput.name();
    pArea.release(region);
    m_fCanRecognizeAll = false;
    return 0;
  }

  // record the order of the CIE and FDE fragments
  FragListType frag_list;

  while (p < eh_end) {

    if (eh_end - p < 4) {
      debug(diag::debug_eh_unsupport) << pInput.name();
      m_fCanRecognizeAll = false;
      break;
    }
    // read the Length field
    len = readVal(p, pBackend.isLittleEndian());
    p += 4;

    // the zero length entry should be the end of the section
    if (0 == len) {
      if (p < eh_end) {
        debug(diag::debug_eh_unsupport) << pInput.name();
        m_fCanRecognizeAll = false;
      }
      break;
    }
    if (0xffffffff == len) {
      debug(diag::debug_eh_unsupport) << pInput.name();
      m_fCanRecognizeAll = false;
      break;
    }

    if (eh_end - p < 4) {
      debug(diag::debug_eh_unsupport) << pInput.name();
      m_fCanRecognizeAll = false;
      break;
    }

    // compute the section offset of this entry
    uint32_t ent_offset = static_cast<uint32_t>(p - eh_start - 4);

    // get the MemoryRegion for this entry
    MemoryRegion* ent_region = pArea.request(
                pInput.fileOffset() + pSection.offset() + ent_offset, len + 4);

    // create and add a CIE or FDE entry
    uint32_t id = readVal(p, pBackend.isLittleEndian());
    // CIE
    if (0 == id) {
      if (!addCIE(*ent_region, pBackend, frag_list)) {
        m_fCanRecognizeAll = false;
        pArea.release(ent_region);
        break;
      }
    }

    // FDE
    else {
      if (!addFDE(*ent_region, pBackend, frag_list)) {
        m_fCanRecognizeAll = false;
        pArea.release(ent_region);
        break;
      }
    }
    p += len;
  }

  if (!m_fCanRecognizeAll) {
    debug(diag::debug_eh_unsupport) << pInput.name();
    pArea.release(region);
    deleteFragments(frag_list, pArea);
    return 0;
  }

  // append all CIE and FDE fragments to Layout after we successfully read
  // this eh_frame
  size_t section_size = 0;
  for (FragListType::iterator it = frag_list.begin();
         it != frag_list.end(); ++it)
    section_size += pLayout.appendFragment(**it, pSD, pSection.align());

  pArea.release(region);
  return section_size;
}

bool EhFrame::addCIE(MemoryRegion& pRegion,
                     const TargetLDBackend& pBackend,
                     FragListType& pFragList)
{
  ConstAddress cie_start = pRegion.start();
  ConstAddress cie_end = pRegion.end();
  ConstAddress p = cie_start;

  // skip the Length (4 byte) and CIE ID (4 byte) fields
  p += 8;

  // the version should be 1
  if (1 != *p) {
    return false;
  }
  ++p;

  // get the Augumentation String
  ConstAddress aug_str = p;
  ConstAddress aug_str_end = static_cast<ConstAddress>(
                               memchr(p, '\0', cie_end - p));

  // skip the Augumentation String field
  p = aug_str_end + 1;

  // skip the Code Alignment Factor
  if (!skipLEB128(&p, cie_end)) {
    return false;
  }
  // skip the Data Alignment Factor
  if (!skipLEB128(&p, cie_end)) {
    return false;
  }
  // skip the Return Address Register
  if (cie_end - p < 1) {
    return false;
  }
  ++p;

  // the Augmentation String start with 'eh' is a CIE from gcc before 3.0,
  // in LSB Core Spec 3.0RC1. We do not support it.
  if (aug_str[0] == 'e' && aug_str[1] == 'h') {
    return false;
  }

  // parse the Augmentation String to get the FDE encodeing if 'z' existed
  std::string aug_str_data;
  uint8_t fde_encoding = llvm::dwarf::DW_EH_PE_absptr;
  if (*aug_str == 'z') {

    aug_str_data += *aug_str;
    ++aug_str;

    // skip the Augumentation Data Length
    if (!skipLEB128(&p, cie_end)) {
      return false;
    }

    while (aug_str != aug_str_end) {
      switch (*aug_str) {
        default:
          return false;

        // LDSA encoding (1 byte)
        case 'L':
          if (cie_end - p < 1) {
            return false;
          }
          ++p;
          break;

        // Two arguments, the first one represents the encoding of the second
        // argument (1 byte). The second one is the address of personality
        // routine.
        case 'P': {
          // the first argument
          if (cie_end - p < 1) {
            return false;
          }
          uint8_t per_encode = *p;
          ++p;
          // get the length of the second argument
          uint32_t per_length = 0;
          if (0x60 == (per_encode & 0x60)) {
            return false;
          }
          switch (per_encode & 7) {
            default:
              return false;
            case llvm::dwarf::DW_EH_PE_udata2:
              per_length = 2;
              break;
            case llvm::dwarf::DW_EH_PE_udata4:
              per_length = 4;
              break;
            case llvm::dwarf::DW_EH_PE_udata8:
              per_length = 8;
              break;
            case llvm::dwarf::DW_EH_PE_absptr:
              per_length = pBackend.bitclass() / 8;
              break;
          }
          // skip the alignment
          if (llvm::dwarf::DW_EH_PE_aligned == (per_encode & 0xf0)) {
            uint32_t per_align = p - cie_end;
            per_align += per_length - 1;
            per_align &= ~(per_length -1);
            if (static_cast<uint32_t>(cie_end - p) < per_align) {
              return false;
            }
            p += per_align;
          }
          // skip the second argument
          if (static_cast<uint32_t>(cie_end - p) < per_length) {
            return false;
          }
          p += per_length;
        }
        break;

        // FDE encoding (1 byte)
        case 'R':
          if (cie_end - p < 1) {
            return false;
          }
          fde_encoding = *p;
          switch (fde_encoding & 7) {
            case llvm::dwarf::DW_EH_PE_udata2:
            case llvm::dwarf::DW_EH_PE_udata4:
            case llvm::dwarf::DW_EH_PE_udata8:
            case llvm::dwarf::DW_EH_PE_absptr:
              break;
            default:
              return false;
          }
          ++p;
          break;
      } // end switch
      aug_str_data += *aug_str;
      ++aug_str;
    } // end while
  }

  note(diag::note_eh_cie) << pRegion.size()
                          << aug_str_data
                          << (fde_encoding & 7);

  // create and push back the CIE entry
  CIE* entry = new CIE(pRegion, fde_encoding);
  m_CIEs.push_back(entry);
  pFragList.push_back(static_cast<Fragment*>(entry));
  return true;
}

bool EhFrame::addFDE(MemoryRegion& pRegion,
                     const TargetLDBackend& pBackend,
                     FragListType& pFragList)
{
  ConstAddress fde_start = pRegion.start();
  ConstAddress fde_end = pRegion.end();
  ConstAddress p = fde_start;

  // skip the Length (4 byte) and CIE Pointer (4 byte) fields
  p += 8;

  // get the entry offset of the PC Begin
  if (fde_end - p < 1) {
    return false;
  }
  FDE::Offset pc_offset = static_cast<FDE::Offset>(p - fde_start);

  note(diag::note_eh_fde) << pRegion.size() << pc_offset;
  // create and push back the FDE entry
  FDE* entry = new FDE(pRegion, **(m_CIEs.end() -1), pc_offset);
  m_FDEs.push_back(entry);
  pFragList.push_back(static_cast<Fragment*>(entry));
  return true;
}

uint32_t EhFrame::readVal(ConstAddress pAddr, bool pIsTargetLittleEndian)
{
  const uint32_t* p = reinterpret_cast<const uint32_t*>(pAddr);
  uint32_t val = *p;

  // byte swapping if the host and target have different endian
  if (llvm::sys::isLittleEndianHost() != pIsTargetLittleEndian)
    val = bswap32(val);
  return val;
}

bool EhFrame::skipLEB128(ConstAddress* pp, ConstAddress pend)
{
  for (ConstAddress p = *pp; p < pend; ++p) {
    if (0 == (*p & 0x80)) {
      *pp = p + 1;
      return true;
    }
  }
  return false;
}

void EhFrame::deleteFragments(FragListType& pList, MemoryArea& pArea)
{
  RegionFragment* frag = NULL;
  for (FragListType::iterator it = pList.begin(); it != pList.end(); ++it) {
    frag = static_cast<RegionFragment*>(*it);
    pArea.release(&(frag->getRegion()));
    delete *it;
  }
  pList.clear();
}