ART: CFI Test

Add a run-test that unwinds the process and a forked
process to see whether CFI information is enough
to see Java methods.

In-process unwinding is turned off for now, as it
requires dlopen for oat files.

Black-listed for 64-bit, as libunwind expects 64-bit
ELF files.

Change-Id: I9fe53e448b6cb2ea3d516526c42596dcc2446d98
diff --git a/test/137-cfi/src/Main.java b/test/137-cfi/src/Main.java
new file mode 100644
index 0000000..e184e66
--- /dev/null
+++ b/test/137-cfi/src/Main.java
@@ -0,0 +1,155 @@
+/*
+ * 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);
+}