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 
17 import java.io.BufferedReader;
18 import java.io.FileReader;
19 import java.io.InputStream;
20 import java.io.OutputStream;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.util.Arrays;
24 import java.util.Comparator;
25 
26 public class Main implements Comparator<Main> {
27   // Whether to test local unwinding. Libunwind uses linker info to find executables. As we do
28   // not dlopen at the moment, this doesn't work, so keep it off for now.
29   public final static boolean TEST_LOCAL_UNWINDING = true;
30 
31   // Unwinding another process, modelling debuggerd. This doesn't use the linker, so should work
32   // no matter whether we're using dlopen or not.
33   public final static boolean TEST_REMOTE_UNWINDING = true;
34 
35   private boolean secondary;
36 
37   private boolean passed;
38 
Main(boolean secondary)39   public Main(boolean secondary) {
40       this.secondary = secondary;
41   }
42 
main(String[] args)43   public static void main(String[] args) throws Exception {
44       boolean secondary = false;
45       if (args.length > 0 && args[args.length - 1].equals("--secondary")) {
46           secondary = true;
47       }
48       new Main(secondary).run();
49   }
50 
51   static {
52       System.loadLibrary("arttest");
53   }
54 
run()55   private void run() {
56       if (secondary) {
57           if (!TEST_REMOTE_UNWINDING) {
58               throw new RuntimeException("Should not be running secondary!");
59           }
60           runSecondary();
61       } else {
62           runPrimary();
63       }
64   }
65 
runSecondary()66   private void runSecondary() {
67       foo();
68       throw new RuntimeException("Didn't expect to get back...");
69   }
70 
runPrimary()71   private void runPrimary() {
72       // First do the in-process unwinding.
73       if (TEST_LOCAL_UNWINDING && !foo()) {
74           System.out.println("Unwinding self failed.");
75       }
76 
77       if (!TEST_REMOTE_UNWINDING) {
78           // Skip the remote step.
79           return;
80       }
81 
82       // Fork the secondary.
83       String[] cmdline = getCmdLine();
84       String[] secCmdLine = new String[cmdline.length + 1];
85       System.arraycopy(cmdline, 0, secCmdLine, 0, cmdline.length);
86       secCmdLine[secCmdLine.length - 1] = "--secondary";
87       Process p = exec(secCmdLine);
88 
89       try {
90           int pid = getPid(p);
91           if (pid <= 0) {
92               throw new RuntimeException("Couldn't parse process");
93           }
94 
95           // Wait a bit, so the forked process has time to run until its sleep phase.
96           try {
97               Thread.sleep(5000);
98           } catch (Exception e) {
99               throw new RuntimeException(e);
100           }
101 
102           if (!unwindOtherProcess(pid)) {
103               System.out.println("Unwinding other process failed.");
104           }
105       } finally {
106           // Kill the forked process if it is not already dead.
107           p.destroy();
108       }
109   }
110 
exec(String[] args)111   private static Process exec(String[] args) {
112       try {
113           return Runtime.getRuntime().exec(args);
114       } catch (Exception exc) {
115           throw new RuntimeException(exc);
116       }
117   }
118 
getPid(Process p)119   private static int getPid(Process p) {
120       // Could do reflection for the private pid field, but String parsing is easier.
121       String s = p.toString();
122       if (s.startsWith("Process[pid=")) {
123           return Integer.parseInt(s.substring("Process[pid=".length(), s.length() - 1));
124       } else {
125           return -1;
126       }
127   }
128 
129   // Read /proc/self/cmdline to find the invocation command line (so we can fork another runtime).
getCmdLine()130   private static String[] getCmdLine() {
131       try {
132           BufferedReader in = new BufferedReader(new FileReader("/proc/self/cmdline"));
133           String s = in.readLine();
134           in.close();
135           return s.split("\0");
136       } catch (Exception exc) {
137           throw new RuntimeException(exc);
138       }
139   }
140 
foo()141   public boolean foo() {
142       // Call bar via Arrays.binarySearch.
143       // This tests that we can unwind from framework code.
144       Main[] array = { this, this, this };
145       Arrays.binarySearch(array, 0, 3, this /* value */, this /* comparator */);
146       return passed;
147   }
148 
compare(Main lhs, Main rhs)149   public int compare(Main lhs, Main rhs) {
150       passed = bar(secondary);
151       // Returning "equal" ensures that we terminate search
152       // after first item and thus call bar() only once.
153       return 0;
154   }
155 
bar(boolean b)156   public boolean bar(boolean b) {
157       if (b) {
158           return sleep(2, b, 1.0);
159       } else {
160           return unwindInProcess(1, b);
161       }
162   }
163 
164   // Native functions. Note: to avoid deduping, they must all have different signatures.
165 
sleep(int i, boolean b, double dummy)166   public native boolean sleep(int i, boolean b, double dummy);
167 
unwindInProcess(int i, boolean b)168   public native boolean unwindInProcess(int i, boolean b);
unwindOtherProcess(int pid)169   public native boolean unwindOtherProcess(int pid);
170 }
171