Main.java revision 03bf174e41488879ad5a7a74896669b7431ad31a
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import java.io.BufferedReader;
18import java.io.FileReader;
19import java.io.InputStreamReader;
20import java.util.Arrays;
21import java.util.Comparator;
22
23public class Main implements Comparator<Main> {
24  // Whether to test local unwinding. Libunwind uses linker info to find executables. As we do
25  // not dlopen at the moment, this doesn't work, so keep it off for now.
26  public final static boolean TEST_LOCAL_UNWINDING = true;
27
28  // Unwinding another process, modelling debuggerd. This doesn't use the linker, so should work
29  // no matter whether we're using dlopen or not.
30  public final static boolean TEST_REMOTE_UNWINDING = true;
31
32  private boolean secondary;
33
34  private boolean full_signatures;
35
36  private boolean passed;
37
38  public Main(boolean secondary, boolean full_signatures) {
39      this.secondary = secondary;
40      this.full_signatures = full_signatures;
41  }
42
43  public static void main(String[] args) throws Exception {
44    System.loadLibrary(args[0]);
45      boolean secondary = false;
46      boolean full_signatures = false;
47      for (String arg : args) {
48        if (arg.equals("--secondary")) {
49          secondary = true;
50        }
51        if (arg.equals("--full-signatures")) {
52          full_signatures = true;
53        }
54      }
55      new Main(secondary, full_signatures).run();
56  }
57
58  private void run() {
59      if (secondary) {
60          if (!TEST_REMOTE_UNWINDING) {
61              throw new RuntimeException("Should not be running secondary!");
62          }
63          runSecondary();
64      } else {
65          runPrimary();
66      }
67  }
68
69  private void runSecondary() {
70      foo();
71      throw new RuntimeException("Didn't expect to get back...");
72  }
73
74  private void runPrimary() {
75      // First do the in-process unwinding.
76      if (TEST_LOCAL_UNWINDING && !foo()) {
77          System.out.println("Unwinding self failed.");
78      }
79
80      if (!TEST_REMOTE_UNWINDING) {
81          // Skip the remote step.
82          return;
83      }
84
85      // Fork the secondary.
86      String[] cmdline = getCmdLine();
87      String[] secCmdLine = new String[cmdline.length + 1];
88      System.arraycopy(cmdline, 0, secCmdLine, 0, cmdline.length);
89      secCmdLine[secCmdLine.length - 1] = "--secondary";
90      Process p = exec(secCmdLine);
91
92      try {
93          int pid = getPid(p);
94          if (pid <= 0) {
95              throw new RuntimeException("Couldn't parse process");
96          }
97
98          // Wait until the forked process had time to run until its sleep phase.
99          try {
100              InputStreamReader stdout = new InputStreamReader(p.getInputStream(), "UTF-8");
101              BufferedReader lineReader = new BufferedReader(stdout);
102              while (!lineReader.readLine().contains("Going to sleep")) {
103              }
104          } catch (Exception e) {
105              throw new RuntimeException(e);
106          }
107
108          if (!unwindOtherProcess(full_signatures, pid)) {
109              System.out.println("Unwinding other process failed.");
110          }
111      } finally {
112          // Kill the forked process if it is not already dead.
113          p.destroy();
114      }
115  }
116
117  private static Process exec(String[] args) {
118      try {
119          return Runtime.getRuntime().exec(args);
120      } catch (Exception exc) {
121          throw new RuntimeException(exc);
122      }
123  }
124
125  private static int getPid(Process p) {
126      // Could do reflection for the private pid field, but String parsing is easier.
127      String s = p.toString();
128      if (s.startsWith("Process[pid=")) {
129          return Integer.parseInt(s.substring("Process[pid=".length(), s.indexOf(",")));
130      } else {
131          return -1;
132      }
133  }
134
135  // Read /proc/self/cmdline to find the invocation command line (so we can fork another runtime).
136  private static String[] getCmdLine() {
137      try {
138          BufferedReader in = new BufferedReader(new FileReader("/proc/self/cmdline"));
139          String s = in.readLine();
140          in.close();
141          return s.split("\0");
142      } catch (Exception exc) {
143          throw new RuntimeException(exc);
144      }
145  }
146
147  public boolean foo() {
148      // Call bar via Arrays.binarySearch.
149      // This tests that we can unwind from framework code.
150      Main[] array = { this, this, this };
151      Arrays.binarySearch(array, 0, 3, this /* value */, this /* comparator */);
152      return passed;
153  }
154
155  public int compare(Main lhs, Main rhs) {
156      passed = bar(secondary);
157      // Returning "equal" ensures that we terminate search
158      // after first item and thus call bar() only once.
159      return 0;
160  }
161
162  public boolean bar(boolean b) {
163      if (b) {
164          return sleep(2, b, 1.0);
165      } else {
166          return unwindInProcess(full_signatures, 1, b);
167      }
168  }
169
170  // Native functions. Note: to avoid deduping, they must all have different signatures.
171
172  public native boolean sleep(int i, boolean b, double dummy);
173
174  public native boolean unwindInProcess(boolean full_signatures, int i, boolean b);
175  public native boolean unwindOtherProcess(boolean full_signatures, int pid);
176}
177