setjmp/longjmp: avoid invalid values in the stack pointer.

arm64 was already being careful, but x86/x86-64 and 32-bit ARM could be
caught by a signal in a state where the stack pointer was mangled.

For 32-bit ARM I've taken care with the link register too, to avoid
potential issues with unwinding.

Bug: http://b/152210274
Test: treehugger
Change-Id: I1ce285b017a633c732dbe04743368f4cae27af85
diff --git a/tests/setjmp_test.cpp b/tests/setjmp_test.cpp
index e6b6819..4b1482a 100644
--- a/tests/setjmp_test.cpp
+++ b/tests/setjmp_test.cpp
@@ -18,6 +18,8 @@
 
 #include <setjmp.h>
 #include <stdlib.h>
+#include <sys/syscall.h>
+#include <unistd.h>
 
 #include "BionicDeathTest.h"
 #include "SignalUtils.h"
@@ -268,3 +270,57 @@
   if (value == 0) call_longjmp(buf);
   EXPECT_EQ(123, value);
 }
+
+TEST(setjmp, bug_152210274) {
+  // Ensure that we never have a mangled value in the stack pointer.
+#if defined(__BIONIC__)
+  struct sigaction sa = {.sa_flags = SA_SIGINFO, .sa_sigaction = [](int, siginfo_t*, void*) {}};
+  ASSERT_EQ(0, sigaction(SIGPROF, &sa, 0));
+
+  constexpr size_t kNumThreads = 20;
+
+  // Start a bunch of threads calling setjmp/longjmp.
+  auto jumper = [](void* arg) -> void* {
+    sigset_t set;
+    sigemptyset(&set);
+    sigaddset(&set, SIGPROF);
+    pthread_sigmask(SIG_UNBLOCK, &set, nullptr);
+
+    jmp_buf buf;
+    for (size_t count = 0; count < 100000; ++count) {
+      if (setjmp(buf) != 0) {
+        perror("setjmp");
+        abort();
+      }
+      if (*static_cast<pid_t*>(arg) == 100) longjmp(buf, 1);
+    }
+    return nullptr;
+  };
+  pid_t tids[kNumThreads] = {};
+  for (size_t i = 0; i < kNumThreads; ++i) {
+    pthread_t t;
+    ASSERT_EQ(0, pthread_create(&t, nullptr, jumper, &tids[i]));
+    tids[i] = pthread_gettid_np(t);
+  }
+
+  // Start the interrupter thread.
+  auto interrupter = [](void* arg) -> void* {
+    pid_t* tids = static_cast<pid_t*>(arg);
+    for (size_t count = 0; count < 1000; ++count) {
+      for (size_t i = 0; i < kNumThreads; i++) {
+        if (tgkill(getpid(), tids[i], SIGPROF) == -1 && errno != ESRCH) {
+          perror("tgkill failed");
+          abort();
+        }
+      }
+      usleep(100);
+    }
+    return nullptr;
+  };
+  pthread_t t;
+  ASSERT_EQ(0, pthread_create(&t, nullptr, interrupter, tids));
+  pthread_join(t, nullptr);
+#else
+  GTEST_LOG_(INFO) << "tests uses functions not in glibc";
+#endif
+}