MCLinker upstream commit e764452.
Change-Id: I5c9ec467ec96a0143e1e67c59365f3b6303e7348
diff --git a/lib/LD/EhFrame.cpp b/lib/LD/EhFrame.cpp
new file mode 100644
index 0000000..76af403
--- /dev/null
+++ b/lib/LD/EhFrame.cpp
@@ -0,0 +1,373 @@
+//===- 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 <mcld/MC/MCLinker.h>
+#include <mcld/Support/MsgHandling.h>
+#include <mcld/Target/TargetLDBackend.h>
+#include <llvm/Support/Dwarf.h>
+#include <llvm/Support/Host.h>
+
+using namespace mcld;
+
+//==========================
+// EhFrame
+EhFrame::EhFrame()
+ : m_fCanRecognizeAll(true) {
+}
+
+EhFrame::~EhFrame()
+{
+}
+
+uint64_t EhFrame::readEhFrame(Layout& pLayout,
+ const TargetLDBackend& pBackend,
+ llvm::MCSectionData& pSD,
+ LDSection& pSection,
+ MemoryArea& pArea)
+{
+ MemoryRegion* region = pArea.request(pSection.offset(),
+ pSection.size());
+ // an empty .eh_frame
+ if (NULL == region) {
+ note(diag::note_ehframe) << "an empty eh_frame";
+ 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) {
+ note(diag::note_ehframe) << "a terminator";
+ pArea.release(region);
+ return 0;
+ }
+
+ if (0xffffffff == len) {
+ debug(diag::debug_eh_unsupport) << "64-bit eh_frame";
+ 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) << "CIE or FDE size smaller than 4";
+ 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) << "Non-end entry with zero length";
+ m_fCanRecognizeAll = false;
+ }
+ break;
+ }
+ if (0xffffffff == len) {
+ debug(diag::debug_eh_unsupport) << "64-bit eh_frame";
+ m_fCanRecognizeAll = false;
+ break;
+ }
+
+ if (eh_end - p < 4) {
+ debug(diag::debug_eh_unsupport) <<
+ "CIE:ID field / FDE: CIE Pointer field";
+ 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(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) {
+ 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) {
+ debug(diag::debug_eh_unsupport) << "CIE version";
+ 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)) {
+ debug(diag::debug_eh_unsupport) << "unrecognized Code Alignment Factor";
+ return false;
+ }
+ // skip the Data Alignment Factor
+ if (!skipLEB128(&p, cie_end)) {
+ debug(diag::debug_eh_unsupport) << "unrecognized Data Alignment Factor";
+ return false;
+ }
+ // skip the Return Address Register
+ if (cie_end - p < 1) {
+ debug(diag::debug_eh_unsupport) << "unrecognized Return Address Register";
+ 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') {
+ debug(diag::debug_eh_unsupport) << "augmentation string `eh`";
+ 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)) {
+ debug(diag::debug_eh_unsupport) <<
+ "unrecognized Augmentation Data Length";
+ return false;
+ }
+
+ while (aug_str != aug_str_end) {
+ switch (*aug_str) {
+ default:
+ debug(diag::debug_eh_unsupport) << "augmentation string";
+ return false;
+
+ // LDSA encoding (1 byte)
+ case 'L':
+ if (cie_end - p < 1) {
+ debug(diag::debug_eh_unsupport) << "augmentation string `L`";
+ 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) {
+ debug(diag::debug_eh_unsupport) << "augmentation string `P`";
+ 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)) {
+ debug(diag::debug_eh_unsupport) << "augmentation string `P`";
+ return false;
+ }
+ switch (per_encode & 7) {
+ default:
+ debug(diag::debug_eh_unsupport) << "augmentation string `P`";
+ 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) {
+ debug(diag::debug_eh_unsupport) << "augmentation string `P`";
+ return false;
+ }
+ p += per_align;
+ }
+ // skip the second argument
+ if (static_cast<uint32_t>(cie_end - p) < per_length) {
+ debug(diag::debug_eh_unsupport) << "augmentation string `P`";
+ return false;
+ }
+ p += per_length;
+ }
+ break;
+
+ // FDE encoding (1 byte)
+ case 'R':
+ if (cie_end - p < 1) {
+ debug(diag::debug_eh_unsupport) << "augmentation string `R`";
+ 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:
+ debug(diag::debug_eh_unsupport) << "FDE encoding";
+ 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<llvm::MCFragment*>(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) {
+ debug(diag::debug_eh_unsupport) << "FDE PC Begin";
+ 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<llvm::MCFragment*>(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)
+{
+ MCRegionFragment* frag = NULL;
+ for (FragListType::iterator it = pList.begin(); it != pList.end(); ++it) {
+ frag = static_cast<MCRegionFragment*>(*it);
+ pArea.release(&(frag->getRegion()));
+ delete *it;
+ }
+ pList.clear();
+}
+