//===- 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();
}