Add .eh_frame_hdr search to Linux unwinder.
authorDan Albert <danalbert@google.com>
Fri, 27 Feb 2015 22:21:07 +0000 (22:21 +0000)
committerDan Albert <danalbert@google.com>
Fri, 27 Feb 2015 22:21:07 +0000 (22:21 +0000)
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

libcxxabi/src/Unwind/AddressSpace.hpp
libcxxabi/src/Unwind/EHHeaderParser.hpp [new file with mode: 0644]
libcxxabi/src/Unwind/UnwindCursor.hpp
libcxxabi/src/Unwind/config.h

index 960c8f8..f9e64ae 100644 (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);
diff --git a/libcxxabi/src/Unwind/EHHeaderParser.hpp b/libcxxabi/src/Unwind/EHHeaderParser.hpp
new file mode 100644 (file)
index 0000000..7945c7b
--- /dev/null
@@ -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
index 6817485..b4d413f 100644 (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) {
index 6a70897..2a7da6a 100644 (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