libunwind: Fix unw_step() for ARM EHABI.
authorLogan Chien <tzuhsiang.chien@gmail.com>
Fri, 29 May 2015 15:33:38 +0000 (15:33 +0000)
committerLogan Chien <tzuhsiang.chien@gmail.com>
Fri, 29 May 2015 15:33:38 +0000 (15:33 +0000)
This commit fixes the unw_step() for ARM EHABI.  However, this commit
also changes the implementation details for ARM EHABI.

The first change is that the personality function should call
__gnu_unwind_frame() for default (or de facto) frame unwinding based on
the ARM-defined unwind opcode.  The function __gnu_unwind_frame() will
in turn calls unw_step() which actually unwinds the frame.

The second change is that the implementation _Unwind_Backtrace() should
no longer calls unw_step() to unwind the frame; since according to ARM
EHABI, the personality function should unwind the frame for us.

Special thanks to Anton for helpful suggestion on the initial version of
this patch.

llvm-svn: 238560

libunwind/src/Unwind-EHABI.cpp
libunwind/src/UnwindCursor.hpp
libunwind/src/UnwindLevel1-gcc-ext.c
libunwind/test/libunwind_01.pass.cpp [new file with mode: 0644]
libunwind/test/libunwind_02.pass.cpp [new file with mode: 0644]

index 8a6bfc0..eb641e3 100644 (file)
@@ -168,25 +168,22 @@ static _Unwind_Reason_Code unwindOneFrame(_Unwind_State state,
   assert((*unwindingData & 0xf0000000) == 0x80000000 && "Must be a compact entry");
   Descriptor::Format format =
       static_cast<Descriptor::Format>((*unwindingData & 0x0f000000) >> 24);
-  size_t len = 0;
-  size_t off = 0;
-  unwindingData = decode_eht_entry(unwindingData, &off, &len);
-  if (unwindingData == nullptr) {
-    return _URC_FAILURE;
-  }
+
+  const char *lsda =
+      reinterpret_cast<const char *>(_Unwind_GetLanguageSpecificData(context));
 
   // Handle descriptors before unwinding so they are processed in the context
   // of the correct stack frame.
   _Unwind_Reason_Code result =
-      ProcessDescriptors(
-          state, ucbp, context, format,
-          reinterpret_cast<const char*>(ucbp->pr_cache.ehtp) + len,
-          ucbp->pr_cache.additional);
+      ProcessDescriptors(state, ucbp, context, format, lsda,
+                         ucbp->pr_cache.additional);
 
   if (result != _URC_CONTINUE_UNWIND)
     return result;
 
-  return _Unwind_VRS_Interpret(context, unwindingData, off, len);
+  if (unw_step(reinterpret_cast<unw_cursor_t*>(context)) != UNW_STEP_SUCCESS)
+    return _URC_FAILURE;
+  return _URC_CONTINUE_UNWIND;
 }
 
 // Generates mask discriminator for _Unwind_VRS_Pop, e.g. for _UVRSC_CORE /
@@ -213,26 +210,37 @@ uint32_t RegisterRange(uint8_t start, uint8_t count_minus_one) {
  */
 extern "C" const uint32_t*
 decode_eht_entry(const uint32_t* data, size_t* off, size_t* len) {
-  assert((*data & 0x80000000) != 0 &&
-         "decode_eht_entry() does not support user-defined personality");
-
-  // 6.3: ARM Compact Model
-  // EHT entries here correspond to the __aeabi_unwind_cpp_pr[012] PRs indeded
-  // by format:
-  Descriptor::Format format =
-      static_cast<Descriptor::Format>((*data & 0x0f000000) >> 24);
-  switch (format) {
-    case Descriptor::SU16:
-      *len = 4;
-      *off = 1;
-      break;
-    case Descriptor::LU16:
-    case Descriptor::LU32:
-      *len = 4 + 4 * ((*data & 0x00ff0000) >> 16);
-      *off = 2;
-      break;
-    default:
-      return nullptr;
+  if ((*data & 0x80000000) == 0) {
+    // 6.2: Generic Model
+    //
+    // EHT entry is a prel31 pointing to the PR, followed by data understood
+    // only by the personality routine. Fortunately, all existing assembler
+    // implementations, including GNU assembler, LLVM integrated assembler,
+    // and ARM assembler, assume that the unwind opcodes come after the
+    // personality rountine address.
+    *off = 1; // First byte is size data.
+    *len = (((data[1] >> 24) & 0xff) + 1) * 4;
+    data++; // Skip the first word, which is the prel31 offset.
+  } else {
+    // 6.3: ARM Compact Model
+    //
+    // EHT entries here correspond to the __aeabi_unwind_cpp_pr[012] PRs indeded
+    // by format:
+    Descriptor::Format format =
+        static_cast<Descriptor::Format>((*data & 0x0f000000) >> 24);
+    switch (format) {
+      case Descriptor::SU16:
+        *len = 4;
+        *off = 1;
+        break;
+      case Descriptor::LU16:
+      case Descriptor::LU32:
+        *len = 4 + 4 * ((*data & 0x00ff0000) >> 16);
+        *off = 2;
+        break;
+      default:
+        return nullptr;
+    }
   }
   return data;
 }
@@ -443,6 +451,7 @@ unwind_phase1(unw_context_t *uc, _Unwind_Exception *exception_object) {
   // Walk each frame looking for a place to stop.
   for (bool handlerNotFound = true; handlerNotFound;) {
 
+#if !LIBCXXABI_ARM_EHABI
     // Ask libuwind to get next frame (skip over first which is
     // _Unwind_RaiseException).
     int stepResult = unw_step(&cursor1);
@@ -457,6 +466,7 @@ unwind_phase1(unw_context_t *uc, _Unwind_Exception *exception_object) {
                                  static_cast<void *>(exception_object));
       return _URC_FATAL_PHASE1_ERROR;
     }
+#endif
 
     // See if frame has code to run (has personality routine).
     unw_proc_info_t frameInfo;
@@ -575,6 +585,7 @@ static _Unwind_Reason_Code unwind_phase2(unw_context_t *uc,
       resume = false;
     }
 
+#if !LIBCXXABI_ARM_EHABI
     int stepResult = unw_step(&cursor2);
     if (stepResult == 0) {
       _LIBUNWIND_TRACE_UNWINDING("unwind_phase2(ex_ojb=%p): unw_step() reached "
@@ -587,6 +598,7 @@ static _Unwind_Reason_Code unwind_phase2(unw_context_t *uc,
                                  static_cast<void *>(exception_object));
       return _URC_FATAL_PHASE2_ERROR;
     }
+#endif
 
     // Get info about this frame.
     unw_word_t sp;
@@ -752,11 +764,6 @@ _Unwind_GetLanguageSpecificData(struct _Unwind_Context *context) {
   _LIBUNWIND_TRACE_API(
       "_Unwind_GetLanguageSpecificData(context=%p) => 0x%llx\n",
       static_cast<void *>(context), (long long)result);
-  if (result != 0) {
-    if (*((uint8_t *)result) != 0xFF)
-      _LIBUNWIND_DEBUG_LOG("lsda at 0x%llx does not start with 0xFF\n",
-                           (long long)result);
-  }
   return result;
 }
 
@@ -990,4 +997,13 @@ _Unwind_DeleteException(_Unwind_Exception *exception_object) {
                                            exception_object);
 }
 
+_LIBUNWIND_EXPORT extern "C" _Unwind_Reason_Code
+__gnu_unwind_frame(_Unwind_Exception *exception_object,
+                   struct _Unwind_Context *context) {
+  unw_cursor_t *cursor = (unw_cursor_t *)context;
+  if (unw_step(cursor) != UNW_STEP_SUCCESS)
+    return _URC_FAILURE;
+  return _URC_OK;
+}
+
 #endif  // LIBCXXABI_ARM_EHABI
index b4d413f..7703af9 100644 (file)
@@ -440,6 +440,20 @@ private:
 
 #if LIBCXXABI_ARM_EHABI
   bool getInfoFromEHABISection(pint_t pc, const UnwindInfoSections &sects);
+
+  int stepWithEHABI() {
+    size_t len = 0;
+    size_t off = 0;
+    // FIXME: Calling decode_eht_entry() here is violating the libunwind
+    // abstraction layer.
+    const uint32_t *ehtp =
+        decode_eht_entry(reinterpret_cast<const uint32_t *>(_info.unwind_info),
+                         &off, &len);
+    if (_Unwind_VRS_Interpret((_Unwind_Context *)this, ehtp, off, len) !=
+            _URC_CONTINUE_UNWIND)
+      return UNW_STEP_END;
+    return UNW_STEP_SUCCESS;
+  }
 #endif
 
 #if _LIBUNWIND_SUPPORT_DWARF_UNWIND
@@ -731,7 +745,7 @@ bool UnwindCursor<A, R>::getInfoFromEHABISection(
   //   isSingleWordEHT -- whether the entry is in the index.
   unw_word_t personalityRoutine = 0xbadf00d;
   bool scope32 = false;
-  uintptr_t lsda = 0xbadf00d;
+  uintptr_t lsda;
 
   // If the high bit in the exception handling table entry is set, the entry is
   // in compact form (section 6.3 EHABI).
@@ -744,16 +758,19 @@ bool UnwindCursor<A, R>::getInfoFromEHABISection(
         personalityRoutine = (unw_word_t) &__aeabi_unwind_cpp_pr0;
         extraWords = 0;
         scope32 = false;
+        lsda = isSingleWordEHT ? 0 : (exceptionTableAddr + 4);
         break;
       case 1:
         personalityRoutine = (unw_word_t) &__aeabi_unwind_cpp_pr1;
         extraWords = (exceptionTableData & 0x00ff0000) >> 16;
         scope32 = false;
+        lsda = exceptionTableAddr + (extraWords + 1) * 4;
         break;
       case 2:
         personalityRoutine = (unw_word_t) &__aeabi_unwind_cpp_pr2;
         extraWords = (exceptionTableData & 0x00ff0000) >> 16;
         scope32 = true;
+        lsda = exceptionTableAddr + (extraWords + 1) * 4;
         break;
       default:
         _LIBUNWIND_ABORT("unknown personality routine");
@@ -1281,7 +1298,7 @@ int UnwindCursor<A, R>::step() {
 #elif _LIBUNWIND_SUPPORT_DWARF_UNWIND
   result = this->stepWithDwarfFDE();
 #elif LIBCXXABI_ARM_EHABI
-  result = UNW_STEP_SUCCESS;
+  result = this->stepWithEHABI();
 #else
   #error Need _LIBUNWIND_SUPPORT_COMPACT_UNWIND or \
               _LIBUNWIND_SUPPORT_DWARF_UNWIND or \
index b1e3f77..2a8b24c 100644 (file)
@@ -15,6 +15,7 @@
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 
 #include "config.h"
 #include "libunwind_ext.h"
@@ -110,10 +111,18 @@ _Unwind_Backtrace(_Unwind_Trace_Fn callback, void *ref) {
   _LIBUNWIND_TRACE_API("_Unwind_Backtrace(callback=%p)\n",
                        (void *)(uintptr_t)callback);
 
+#if LIBCXXABI_ARM_EHABI
+  // Create a mock exception object for force unwinding.
+  _Unwind_Exception ex;
+  memset(&ex, '\0', sizeof(ex));
+  ex.exception_class = 0x434C4E47554E5700; // CLNGUNW\0
+#endif
+
   // walk each frame
   while (true) {
     _Unwind_Reason_Code result;
 
+#if !LIBCXXABI_ARM_EHABI
     // ask libuwind to get next frame (skip over first frame which is
     // _Unwind_Backtrace())
     if (unw_step(&cursor) <= 0) {
@@ -122,48 +131,28 @@ _Unwind_Backtrace(_Unwind_Trace_Fn callback, void *ref) {
                                  _URC_END_OF_STACK);
       return _URC_END_OF_STACK;
     }
-
-#if LIBCXXABI_ARM_EHABI
+#else
     // Get the information for this frame.
     unw_proc_info_t frameInfo;
     if (unw_get_proc_info(&cursor, &frameInfo) != UNW_ESUCCESS) {
       return _URC_END_OF_STACK;
     }
 
-    struct _Unwind_Context *context = (struct _Unwind_Context *)&cursor;
+    // Update the pr_cache in the mock exception object.
     const uint32_t* unwindInfo = (uint32_t *) frameInfo.unwind_info;
-    if ((*unwindInfo & 0x80000000) == 0) {
-      // 6.2: Generic Model
-      // EHT entry is a prel31 pointing to the PR, followed by data understood
-      // only by the personality routine. Since EHABI doesn't guarantee the
-      // location or availability of the unwind opcodes in the generic model,
-      // we have to call personality functions with (_US_VIRTUAL_UNWIND_FRAME |
-      // _US_FORCE_UNWIND) state.
-
-      // Create a mock exception object for force unwinding.
-      _Unwind_Exception ex;
-      ex.exception_class = 0x434C4E47554E5700; // CLNGUNW\0
-      ex.pr_cache.fnstart = frameInfo.start_ip;
-      ex.pr_cache.ehtp = (_Unwind_EHT_Header *) unwindInfo;
-      ex.pr_cache.additional= frameInfo.flags;
-
-      // Get and call the personality function to unwind the frame.
-      __personality_routine pr = (__personality_routine) readPrel31(unwindInfo);
-      if (pr(_US_VIRTUAL_UNWIND_FRAME | _US_FORCE_UNWIND, &ex, context) !=
-              _URC_CONTINUE_UNWIND) {
-        return _URC_END_OF_STACK;
-      }
-    } else {
-      size_t off, len;
-      unwindInfo = decode_eht_entry(unwindInfo, &off, &len);
-      if (unwindInfo == NULL) {
-        return _URC_FAILURE;
-      }
-
-      result = _Unwind_VRS_Interpret(context, unwindInfo, off, len);
-      if (result != _URC_CONTINUE_UNWIND) {
-        return _URC_END_OF_STACK;
-      }
+    ex.pr_cache.fnstart = frameInfo.start_ip;
+    ex.pr_cache.ehtp = (_Unwind_EHT_Header *) unwindInfo;
+    ex.pr_cache.additional= frameInfo.flags;
+
+    struct _Unwind_Context *context = (struct _Unwind_Context *)&cursor;
+    // Get and call the personality function to unwind the frame.
+    __personality_routine handler = (__personality_routine) frameInfo.handler;
+    if (handler == NULL) {
+      return _URC_END_OF_STACK;
+    }
+    if (handler(_US_VIRTUAL_UNWIND_FRAME | _US_FORCE_UNWIND, &ex, context) !=
+            _URC_CONTINUE_UNWIND) {
+      return _URC_END_OF_STACK;
     }
 #endif // LIBCXXABI_ARM_EHABI
 
diff --git a/libunwind/test/libunwind_01.pass.cpp b/libunwind/test/libunwind_01.pass.cpp
new file mode 100644 (file)
index 0000000..6957d98
--- /dev/null
@@ -0,0 +1,42 @@
+#include <libunwind.h>
+#include <stdlib.h>
+
+void backtrace(int lower_bound) {
+  unw_context_t context;
+  unw_getcontext(&context);
+
+  unw_cursor_t cursor;
+  unw_init_local(&cursor, &context);
+
+  int n = 0;
+  do {
+    ++n;
+    if (n > 100) {
+      abort();
+    }
+  } while (unw_step(&cursor) > 0);
+
+  if (n < lower_bound) {
+    abort();
+  }
+}
+
+void test1(int i) {
+  backtrace(i);
+}
+
+void test2(int i, int j) {
+  backtrace(i);
+  test1(j);
+}
+
+void test3(int i, int j, int k) {
+  backtrace(i);
+  test2(j, k);
+}
+
+int main() {
+  test1(1);
+  test2(1, 2);
+  test3(1, 2, 3);
+}
diff --git a/libunwind/test/libunwind_02.pass.cpp b/libunwind/test/libunwind_02.pass.cpp
new file mode 100644 (file)
index 0000000..892c087
--- /dev/null
@@ -0,0 +1,37 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <unwind.h>
+
+#define EXPECTED_NUM_FRAMES 50
+#define NUM_FRAMES_UPPER_BOUND 100
+
+_Unwind_Reason_Code callback(_Unwind_Context *context, void *cnt) {
+  int *i = (int *)cnt;
+  ++*i;
+  if (*i > NUM_FRAMES_UPPER_BOUND) {
+    abort();
+  }
+  return _URC_NO_REASON;
+}
+
+void test_backtrace() {
+  int n = 0;
+  _Unwind_Backtrace(&callback, &n);
+  if (n < EXPECTED_NUM_FRAMES) {
+    abort();
+  }
+}
+
+int test(int i) {
+  if (i == 0) {
+    test_backtrace();
+    return 0;
+  } else {
+    return i + test(i - 1);
+  }
+}
+
+int main() {
+  int total = test(50);
+  assert(total == 1275);
+}