--- /dev/null
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if __linux__
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+#endif
+
+#include "jni.h"
+
+#include <backtrace/Backtrace.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "utils.h"
+
+namespace art {
+
+// For testing debuggerd. We do not have expected-death tests, so can't test this by default.
+// Code for this is copied from SignalTest.
+static constexpr bool kCauseSegfault = false;
+char* go_away_compiler_cfi = nullptr;
+
+static void CauseSegfault() {
+#if defined(__arm__) || defined(__i386__) || defined(__x86_64__) || defined(__aarch64__)
+ // On supported architectures we cause a real SEGV.
+ *go_away_compiler_cfi = 'a';
+#else
+ // On other architectures we simulate SEGV.
+ kill(getpid(), SIGSEGV);
+#endif
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_sleep(JNIEnv*, jobject, jint, jboolean, jdouble) {
+ // Keep pausing.
+ for (;;) {
+ pause();
+ }
+}
+
+// Helper to look for a sequence in the stack trace.
+#if __linux__
+static bool CheckStack(Backtrace* bt, const std::vector<std::string>& seq) {
+ size_t cur_search_index = 0; // The currently active index in seq.
+ CHECK_GT(seq.size(), 0U);
+
+ for (Backtrace::const_iterator it = bt->begin(); it != bt->end(); ++it) {
+ if (BacktraceMap::IsValid(it->map)) {
+ LOG(INFO) << "Got " << it->func_name << ", looking for " << seq[cur_search_index];
+ if (it->func_name == seq[cur_search_index]) {
+ cur_search_index++;
+ if (cur_search_index == seq.size()) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+#endif
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindInProcess(JNIEnv*, jobject, jint, jboolean) {
+#if __linux__
+ // TODO: What to do on Valgrind?
+
+ std::unique_ptr<Backtrace> bt(Backtrace::Create(BACKTRACE_CURRENT_PROCESS, GetTid()));
+ if (!bt->Unwind(0, nullptr)) {
+ return JNI_FALSE;
+ } else if (bt->NumFrames() == 0) {
+ return JNI_FALSE;
+ }
+
+ // We cannot really parse an exact stack, as the optimizing compiler may inline some functions.
+ // This is also risky, as deduping might play a trick on us, so the test needs to make sure that
+ // only unique functions are being expected.
+ std::vector<std::string> seq = {
+ "Java_Main_unwindInProcess", // This function.
+ "boolean Main.unwindInProcess(int, boolean)", // The corresponding Java native method frame.
+ "void Main.main(java.lang.String[])" // The Java entry method.
+ };
+
+ bool result = CheckStack(bt.get(), seq);
+ if (!kCauseSegfault) {
+ return result ? JNI_TRUE : JNI_FALSE;
+ } else {
+ LOG(INFO) << "Result of check-stack: " << result;
+ }
+#endif
+
+ if (kCauseSegfault) {
+ CauseSegfault();
+ }
+
+ return JNI_FALSE;
+}
+
+#if __linux__
+static constexpr int kSleepTimeMicroseconds = 50000; // 0.05 seconds
+static constexpr int kMaxTotalSleepTimeMicroseconds = 1000000; // 1 second
+
+// Wait for a sigstop. This code is copied from libbacktrace.
+int wait_for_sigstop(pid_t tid, int* total_sleep_time_usec, bool* detach_failed ATTRIBUTE_UNUSED) {
+ for (;;) {
+ int status;
+ pid_t n = TEMP_FAILURE_RETRY(waitpid(tid, &status, __WALL | WNOHANG));
+ if (n == -1) {
+ PLOG(WARNING) << "waitpid failed: tid " << tid;
+ break;
+ } else if (n == tid) {
+ if (WIFSTOPPED(status)) {
+ return WSTOPSIG(status);
+ } else {
+ PLOG(ERROR) << "unexpected waitpid response: n=" << n << ", status=" << std::hex << status;
+ break;
+ }
+ }
+
+ if (*total_sleep_time_usec > kMaxTotalSleepTimeMicroseconds) {
+ PLOG(WARNING) << "timed out waiting for stop signal: tid=" << tid;
+ break;
+ }
+
+ usleep(kSleepTimeMicroseconds);
+ *total_sleep_time_usec += kSleepTimeMicroseconds;
+ }
+
+ return -1;
+}
+#endif
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_unwindOtherProcess(JNIEnv*, jobject, jint pid_int) {
+#if __linux__
+ // TODO: What to do on Valgrind?
+ pid_t pid = static_cast<pid_t>(pid_int);
+
+ // OK, this is painful. debuggerd uses ptrace to unwind other processes.
+
+ if (ptrace(PTRACE_ATTACH, pid, 0, 0)) {
+ // Were not able to attach, bad.
+ PLOG(ERROR) << "Failed to attach.";
+ kill(pid, SIGCONT);
+ return JNI_FALSE;
+ }
+
+ kill(pid, SIGSTOP);
+
+ bool detach_failed = false;
+ int total_sleep_time_usec = 0;
+ int signal = wait_for_sigstop(pid, &total_sleep_time_usec, &detach_failed);
+ if (signal == -1) {
+ LOG(WARNING) << "wait_for_sigstop failed.";
+ }
+
+ std::unique_ptr<Backtrace> bt(Backtrace::Create(pid, BACKTRACE_CURRENT_THREAD));
+ bool result = true;
+ if (!bt->Unwind(0, nullptr)) {
+ result = false;
+ } else if (bt->NumFrames() == 0) {
+ result = false;
+ }
+
+ if (result) {
+ // See comment in unwindInProcess for non-exact stack matching.
+ std::vector<std::string> seq = {
+ // "Java_Main_sleep", // The sleep function being executed in the
+ // other runtime.
+ // Note: For some reason, the name isn't
+ // resolved, so don't look for it right now.
+ "boolean Main.sleep(int, boolean, double)", // The corresponding Java native method frame.
+ "void Main.main(java.lang.String[])" // The Java entry method.
+ };
+
+ result = CheckStack(bt.get(), seq);
+ }
+
+ if (ptrace(PTRACE_DETACH, pid, 0, 0) != 0) {
+ PLOG(ERROR) << "Detach failed";
+ }
+
+ // Continue the process so we can kill it on the Java side.
+ kill(pid, SIGCONT);
+
+ return result ? JNI_TRUE : JNI_FALSE;
+#else
+ return JNI_FALSE;
+#endif
+}
+
+} // namespace art
--- /dev/null
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class Main {
+ // Whether to test local unwinding. Libunwind uses linker info to find executables. As we do
+ // not dlopen at the moment, this doesn't work, so keep it off for now.
+ public final static boolean TEST_LOCAL_UNWINDING = false;
+
+ // Unwinding another process, modelling debuggerd. This doesn't use the linker, so should work
+ // no matter whether we're using dlopen or not.
+ public final static boolean TEST_REMOTE_UNWINDING = true;
+
+ private boolean secondary;
+
+ public Main(boolean secondary) {
+ this.secondary = secondary;
+ }
+
+ public static void main(String[] args) throws Exception {
+ boolean secondary = false;
+ if (args.length > 0 && args[args.length - 1].equals("--secondary")) {
+ secondary = true;
+ }
+ new Main(secondary).run();
+ }
+
+ static {
+ System.loadLibrary("arttest");
+ }
+
+ private void run() {
+ if (secondary) {
+ if (!TEST_REMOTE_UNWINDING) {
+ throw new RuntimeException("Should not be running secondary!");
+ }
+ runSecondary();
+ } else {
+ runPrimary();
+ }
+ }
+
+ private void runSecondary() {
+ foo(true);
+ throw new RuntimeException("Didn't expect to get back...");
+ }
+
+ private void runPrimary() {
+ // First do the in-process unwinding.
+ if (TEST_LOCAL_UNWINDING && !foo(false)) {
+ System.out.println("Unwinding self failed.");
+ }
+
+ if (!TEST_REMOTE_UNWINDING) {
+ // Skip the remote step.
+ return;
+ }
+
+ // Fork the secondary.
+ String[] cmdline = getCmdLine();
+ String[] secCmdLine = new String[cmdline.length + 1];
+ System.arraycopy(cmdline, 0, secCmdLine, 0, cmdline.length);
+ secCmdLine[secCmdLine.length - 1] = "--secondary";
+ Process p = exec(secCmdLine);
+
+ try {
+ int pid = getPid(p);
+ if (pid <= 0) {
+ throw new RuntimeException("Couldn't parse process");
+ }
+
+ // Wait a bit, so the forked process has time to run until its sleep phase.
+ try {
+ Thread.sleep(5000);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ if (!unwindOtherProcess(pid)) {
+ System.out.println("Unwinding other process failed.");
+ }
+ } finally {
+ // Kill the forked process.
+ p.destroy();
+ }
+ }
+
+ private static Process exec(String[] args) {
+ try {
+ return Runtime.getRuntime().exec(args);
+ } catch (Exception exc) {
+ throw new RuntimeException(exc);
+ }
+ }
+
+ private static int getPid(Process p) {
+ // Could do reflection for the private pid field, but String parsing is easier.
+ String s = p.toString();
+ if (s.startsWith("Process[pid=")) {
+ return Integer.parseInt(s.substring("Process[pid=".length(), s.length() - 1));
+ } else {
+ return -1;
+ }
+ }
+
+ // Read /proc/self/cmdline to find the invocation command line (so we can fork another runtime).
+ private static String[] getCmdLine() {
+ try {
+ BufferedReader in = new BufferedReader(new FileReader("/proc/self/cmdline"));
+ String s = in.readLine();
+ in.close();
+ return s.split("\0");
+ } catch (Exception exc) {
+ throw new RuntimeException(exc);
+ }
+ }
+
+ public boolean foo(boolean b) {
+ return bar(b);
+ }
+
+ public boolean bar(boolean b) {
+ if (b) {
+ return sleep(2, b, 1.0);
+ } else {
+ return unwindInProcess(1, b);
+ }
+ }
+
+ // Native functions. Note: to avoid deduping, they must all have different signatures.
+
+ public native boolean sleep(int i, boolean b, double dummy);
+
+ public native boolean unwindInProcess(int i, boolean b);
+ public native boolean unwindOtherProcess(int pid);
+}