Add .eh_frame_hdr search to Linux unwinder.

This improves the performance of unwinding on DWARF based targets. The
32-bit x86 support for scanning the full eh_frame
(CFI_Parser::findFDE) apparently does not work (at least not on
Linux). Since the eh_frame_hdr code delegates to that, this still
doesn't work for x86 Linux, but it has been tested on x86_64 Linux and
aarch64 Android.

llvm-svn: 230802
This commit is contained in:
Dan Albert 2015-02-27 22:21:07 +00:00
parent e3959eb54e
commit 198d366fad
4 changed files with 242 additions and 10 deletions

View File

@ -56,6 +56,9 @@ extern EHTEntry __exidx_start;
extern EHTEntry __exidx_end;
#endif // !defined(_LIBUNWIND_IS_BAREMETAL)
#elif _LIBUNWIND_SUPPORT_DWARF_UNWIND
#include <link.h>
#include "EHHeaderParser.hpp"
#endif // LIBCXXABI_ARM_EHABI
namespace libunwind {
@ -132,7 +135,8 @@ public:
static uint64_t getULEB128(pint_t &addr, pint_t end);
static int64_t getSLEB128(pint_t &addr, pint_t end);
pint_t getEncodedP(pint_t &addr, pint_t end, uint8_t encoding);
pint_t getEncodedP(pint_t &addr, pint_t end, uint8_t encoding,
pint_t datarelBase = 0);
bool findFunctionName(pint_t addr, char *buf, size_t bufLen,
unw_word_t *offset);
bool findUnwindSections(pint_t targetAddr, UnwindInfoSections &info);
@ -195,9 +199,9 @@ inline int64_t LocalAddressSpace::getSLEB128(pint_t &addr, pint_t end) {
return result;
}
inline LocalAddressSpace::pint_t LocalAddressSpace::getEncodedP(pint_t &addr,
pint_t end,
uint8_t encoding) {
inline LocalAddressSpace::pint_t
LocalAddressSpace::getEncodedP(pint_t &addr, pint_t end, uint8_t encoding,
pint_t datarelBase) {
pint_t startAddr = addr;
const uint8_t *p = (uint8_t *)addr;
pint_t result;
@ -263,7 +267,12 @@ inline LocalAddressSpace::pint_t LocalAddressSpace::getEncodedP(pint_t &addr,
_LIBUNWIND_ABORT("DW_EH_PE_textrel pointer encoding not supported");
break;
case DW_EH_PE_datarel:
_LIBUNWIND_ABORT("DW_EH_PE_datarel pointer encoding not supported");
// DW_EH_PE_datarel is only valid in a few places, so the parameter has a
// default value of 0, and we abort in the event that someone calls this
// function with a datarelBase of 0 and DW_EH_PE_datarel encoding.
if (datarelBase == 0)
_LIBUNWIND_ABORT("DW_EH_PE_datarel is invalid with a datarelBase of 0");
result += datarelBase;
break;
case DW_EH_PE_funcrel:
_LIBUNWIND_ABORT("DW_EH_PE_funcrel pointer encoding not supported");
@ -353,6 +362,64 @@ inline bool LocalAddressSpace::findUnwindSections(pint_t targetAddr,
info.arm_section, info.arm_section_length);
if (info.arm_section && info.arm_section_length)
return true;
#elif _LIBUNWIND_SUPPORT_DWARF_UNWIND
#if _LIBUNWIND_SUPPORT_DWARF_INDEX
struct dl_iterate_cb_data {
LocalAddressSpace *addressSpace;
UnwindInfoSections *sects;
uintptr_t targetAddr;
};
dl_iterate_cb_data cb_data = {this, &info, targetAddr};
int found = dl_iterate_phdr(
[](struct dl_phdr_info *pinfo, size_t, void *data) -> int {
auto cbdata = static_cast<dl_iterate_cb_data *>(data);
size_t object_length;
bool found_obj = false;
bool found_hdr = false;
assert(cbdata);
assert(cbdata->sects);
if (cbdata->targetAddr < pinfo->dlpi_addr) {
return false;
}
for (ElfW(Half) i = 0; i < pinfo->dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &pinfo->dlpi_phdr[i];
if (phdr->p_type == PT_LOAD) {
uintptr_t begin = pinfo->dlpi_addr + phdr->p_vaddr;
uintptr_t end = begin + phdr->p_memsz;
if (cbdata->targetAddr >= begin && cbdata->targetAddr < end) {
cbdata->sects->dso_base = begin;
object_length = phdr->p_memsz;
found_obj = true;
}
} else if (phdr->p_type == PT_GNU_EH_FRAME) {
EHHeaderParser<LocalAddressSpace>::EHHeaderInfo hdrInfo;
uintptr_t eh_frame_hdr_start = pinfo->dlpi_addr + phdr->p_vaddr;
cbdata->sects->dwarf_index_section = eh_frame_hdr_start;
cbdata->sects->dwarf_index_section_length = phdr->p_memsz;
EHHeaderParser<LocalAddressSpace>::decodeEHHdr(
*cbdata->addressSpace, eh_frame_hdr_start, phdr->p_memsz,
hdrInfo);
cbdata->sects->dwarf_section = hdrInfo.eh_frame_ptr;
found_hdr = true;
}
}
if (found_obj && found_hdr) {
cbdata->sects->dwarf_section_length = object_length;
return true;
} else {
return false;
}
},
&cb_data);
return static_cast<bool>(found);
#else
#error "_LIBUNWIND_SUPPORT_DWARF_UNWIND requires _LIBUNWIND_SUPPORT_DWARF_INDEX on this platform."
#endif
#endif
return false;
@ -408,7 +475,8 @@ public:
pint_t getP(pint_t addr);
uint64_t getULEB128(pint_t &addr, pint_t end);
int64_t getSLEB128(pint_t &addr, pint_t end);
pint_t getEncodedP(pint_t &addr, pint_t end, uint8_t encoding);
pint_t getEncodedP(pint_t &addr, pint_t end, uint8_t encoding,
pint_t datarelBase = 0);
bool findFunctionName(pint_t addr, char *buf, size_t bufLen,
unw_word_t *offset);
bool findUnwindSections(pint_t targetAddr, UnwindInfoSections &info);

View File

@ -0,0 +1,161 @@
//===------------------------- EHHeaderParser.hpp -------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is dual licensed under the MIT and the University of Illinois Open
// Source Licenses. See LICENSE.TXT for details.
//
//
// Parses ELF .eh_frame_hdr sections.
//
//===----------------------------------------------------------------------===//
#ifndef __EHHEADERPARSER_HPP__
#define __EHHEADERPARSER_HPP__
#include "libunwind.h"
#include "AddressSpace.hpp"
#include "DwarfParser.hpp"
namespace libunwind {
/// \brief EHHeaderParser does basic parsing of an ELF .eh_frame_hdr section.
///
/// See DWARF spec for details:
/// http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html
///
template <typename A> class EHHeaderParser {
public:
typedef typename A::pint_t pint_t;
/// Information encoded in the EH frame header.
struct EHHeaderInfo {
pint_t eh_frame_ptr;
size_t fde_count;
pint_t table;
uint8_t table_enc;
};
static void decodeEHHdr(A &addressSpace, pint_t ehHdrStart, pint_t ehHdrEnd,
EHHeaderInfo &ehHdrInfo);
static bool findFDE(A &addressSpace, pint_t pc, pint_t ehHdrStart,
uint32_t sectionLength,
typename CFI_Parser<A>::FDE_Info *fdeInfo,
typename CFI_Parser<A>::CIE_Info *cieInfo);
private:
static bool decodeTableEntry(A &addressSpace, pint_t &tableEntry,
pint_t ehHdrStart, pint_t ehHdrEnd,
uint8_t tableEnc,
typename CFI_Parser<A>::FDE_Info *fdeInfo,
typename CFI_Parser<A>::CIE_Info *cieInfo);
static size_t getTableEntrySize(uint8_t tableEnc);
};
template <typename A>
void EHHeaderParser<A>::decodeEHHdr(A &addressSpace, pint_t ehHdrStart,
pint_t ehHdrEnd, EHHeaderInfo &ehHdrInfo) {
pint_t p = ehHdrStart;
uint8_t version = addressSpace.get8(p++);
if (version != 1)
_LIBUNWIND_ABORT("Unsupported .eh_frame_hdr version");
uint8_t eh_frame_ptr_enc = addressSpace.get8(p++);
uint8_t fde_count_enc = addressSpace.get8(p++);
ehHdrInfo.table_enc = addressSpace.get8(p++);
ehHdrInfo.eh_frame_ptr =
addressSpace.getEncodedP(p, ehHdrEnd, eh_frame_ptr_enc, ehHdrStart);
ehHdrInfo.fde_count =
addressSpace.getEncodedP(p, ehHdrEnd, fde_count_enc, ehHdrStart);
ehHdrInfo.table = p;
}
template <typename A>
bool EHHeaderParser<A>::decodeTableEntry(
A &addressSpace, pint_t &tableEntry, pint_t ehHdrStart, pint_t ehHdrEnd,
uint8_t tableEnc, typename CFI_Parser<A>::FDE_Info *fdeInfo,
typename CFI_Parser<A>::CIE_Info *cieInfo) {
// Have to decode the whole FDE for the PC range anyway, so just throw away
// the PC start.
addressSpace.getEncodedP(tableEntry, ehHdrEnd, tableEnc, ehHdrStart);
pint_t fde =
addressSpace.getEncodedP(tableEntry, ehHdrEnd, tableEnc, ehHdrStart);
const char *message =
CFI_Parser<A>::decodeFDE(addressSpace, fde, fdeInfo, cieInfo);
if (message != NULL) {
_LIBUNWIND_DEBUG_LOG("EHHeaderParser::decodeTableEntry: bad fde: %s\n",
message);
return false;
}
return true;
}
template <typename A>
bool EHHeaderParser<A>::findFDE(A &addressSpace, pint_t pc, pint_t ehHdrStart,
uint32_t sectionLength,
typename CFI_Parser<A>::FDE_Info *fdeInfo,
typename CFI_Parser<A>::CIE_Info *cieInfo) {
pint_t ehHdrEnd = ehHdrStart + sectionLength;
EHHeaderParser<A>::EHHeaderInfo hdrInfo;
EHHeaderParser<A>::decodeEHHdr(addressSpace, ehHdrStart, ehHdrEnd, hdrInfo);
size_t tableEntrySize = getTableEntrySize(hdrInfo.table_enc);
pint_t tableEntry;
size_t low = 0;
for (size_t len = hdrInfo.fde_count; len > 1;) {
size_t mid = low + (len / 2);
tableEntry = hdrInfo.table + mid * tableEntrySize;
pint_t start = addressSpace.getEncodedP(tableEntry, ehHdrEnd,
hdrInfo.table_enc, ehHdrStart);
if (start == pc) {
low = mid;
break;
} else if (start < pc) {
low = mid;
len -= (len / 2);
} else {
len /= 2;
}
}
tableEntry = hdrInfo.table + low * tableEntrySize;
if (decodeTableEntry(addressSpace, tableEntry, ehHdrStart, ehHdrEnd,
hdrInfo.table_enc, fdeInfo, cieInfo)) {
if (pc >= fdeInfo->pcStart && pc < fdeInfo->pcEnd)
return true;
}
return false;
}
template <typename A>
size_t EHHeaderParser<A>::getTableEntrySize(uint8_t tableEnc) {
switch (tableEnc & 0x0f) {
case DW_EH_PE_sdata2:
case DW_EH_PE_udata2:
return 4;
case DW_EH_PE_sdata4:
case DW_EH_PE_udata4:
return 8;
case DW_EH_PE_sdata8:
case DW_EH_PE_udata8:
return 16;
case DW_EH_PE_sleb128:
case DW_EH_PE_uleb128:
_LIBUNWIND_ABORT("Can't binary search on variable length encoded data.");
case DW_EH_PE_omit:
return 0;
default:
_LIBUNWIND_ABORT("Unknown DWARF encoding for search table.");
}
}
}
#endif

View File

@ -29,6 +29,7 @@
#include "CompactUnwinder.hpp"
#include "config.h"
#include "DwarfInstructions.hpp"
#include "EHHeaderParser.hpp"
#include "libunwind.h"
#include "Registers.hpp"
#include "Unwind-EHABI.h"
@ -829,8 +830,9 @@ bool UnwindCursor<A, R>::getInfoFromDwarfSection(pint_t pc,
}
#if _LIBUNWIND_SUPPORT_DWARF_INDEX
if (!foundFDE && (sects.dwarf_index_section != 0)) {
// Have eh_frame_hdr section which is index into dwarf section.
// TO DO: implement index search
foundFDE = EHHeaderParser<A>::findFDE(
_addressSpace, pc, sects.dwarf_index_section,
(uint32_t)sects.dwarf_index_section_length, &fdeInfo, &cieInfo);
}
#endif
if (!foundFDE) {

View File

@ -83,8 +83,9 @@
#define _LIBUNWIND_ABORT(msg) assert_rtn(__func__, __FILE__, __LINE__, msg)
#define _LIBUNWIND_SUPPORT_COMPACT_UNWIND 0
#define _LIBUNWIND_SUPPORT_DWARF_UNWIND 0
#define _LIBUNWIND_SUPPORT_DWARF_INDEX 0
#define _LIBUNWIND_SUPPORT_DWARF_UNWIND !defined(__arm__) || \
defined(__ARM_DWARF_EH__)
#define _LIBUNWIND_SUPPORT_DWARF_INDEX _LIBUNWIND_SUPPORT_DWARF_UNWIND
#endif