• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */
18 
19 /**
20  * @author Vitaly A. Provodin
21  */
22 
23 package org.apache.harmony.jpda.tests.jdwp.share;
24 
25 import java.lang.reflect.Field;
26 
27 import java.io.*;
28 import java.nio.file.*;
29 
30 import java.io.IOException;
31 import java.util.Arrays;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.Vector;
35 
36 import org.apache.harmony.jpda.tests.framework.LogWriter;
37 import org.apache.harmony.jpda.tests.framework.StreamRedirector;
38 import org.apache.harmony.jpda.tests.framework.TestErrorException;
39 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPDebuggeeWrapper;
40 import org.apache.harmony.jpda.tests.share.JPDATestOptions;
41 
42 /**
43  * This class provides basic DebuggeeWrapper implementation based on JUnit framework,
44  * which can launch and control debuggee process.
45  */
46 public class JDWPUnitDebuggeeProcessWrapper extends JDWPDebuggeeWrapper {
47 
48     /**
49      * Target VM debuggee process.
50      */
51     public Process process;
52 
53     protected int realPid = -1;
54 
55     protected StreamRedirector errRedir;
56     protected StreamRedirector outRedir;
57 
58     /**
59      * The expected exit code for the debuggee process.
60      */
61     private int expectedExitCode = 0;
62 
63     /**
64      * Creates new instance with given data.
65      *
66      * @param settings
67      *            test run options
68      * @param logWriter
69      *            where to print log messages
70      */
JDWPUnitDebuggeeProcessWrapper(JPDATestOptions settings, LogWriter logWriter)71     public JDWPUnitDebuggeeProcessWrapper(JPDATestOptions settings, LogWriter logWriter) {
72         super(settings, logWriter);
73     }
74 
75     /**
76      * Sets the expected exit code. This is meant to be used by tests that will request target
77      * VM termination with VirtualMachine.Exit command.
78      */
setExpectedExitCode(int expectedExitCode)79     public void setExpectedExitCode(int expectedExitCode) {
80         this.expectedExitCode = expectedExitCode;
81     }
82 
setRealPid(int pid)83     public void setRealPid(int pid) {
84       logWriter.println("Got pid: " + pid);
85       this.realPid = pid;
86     }
87 
88     /**
89      * Launches process and redirects output.
90      */
launchProcessAndRedirectors(String cmdLine)91     public void launchProcessAndRedirectors(String cmdLine) throws IOException {
92         logWriter.println("Launch process: " + cmdLine);
93         process = launchProcess(cmdLine);
94         logWriter.println("Launched process");
95         if (process != null) {
96             logWriter.println("Start redirectors");
97             errRedir = new StreamRedirector(process.getErrorStream(), logWriter, "STDERR");
98             errRedir.setDaemon(true);
99             errRedir.start();
100             outRedir = new StreamRedirector(process.getInputStream(), logWriter, "STDOUT");
101             outRedir.setDaemon(true);
102             outRedir.start();
103             logWriter.println("Started redirectors");
104         }
105     }
106 
107     /**
108      * Waits for process to exit and closes output redirectors
109      */
finishProcessAndRedirectors()110     public void finishProcessAndRedirectors() {
111         if (process != null) {
112             try {
113                 logWriter.println("Waiting for process exit");
114                 WaitForProcessExit(process);
115                 logWriter.println("Finished process");
116             } catch (IOException e) {
117                 throw new TestErrorException("IOException in waiting for process exit: ", e);
118             }
119 
120             logWriter.println("Waiting for redirectors finish");
121             if (outRedir != null) {
122                 outRedir.exit();
123                 try {
124                     outRedir.join(settings.getTimeout());
125                 } catch (InterruptedException e) {
126                     logWriter.println("InterruptedException in stopping outRedirector: " + e);
127                 }
128                 if (outRedir.isAlive()) {
129                     logWriter.println("WARNING: redirector not stopped: " + outRedir.getName());
130                 }
131             }
132             if (errRedir != null) {
133                 errRedir.exit();
134                 try {
135                     errRedir.join(settings.getTimeout());
136                 } catch (InterruptedException e) {
137                     logWriter.println("InterruptedException in stopping errRedirector: " + e);
138                 }
139                 if (errRedir.isAlive()) {
140                     logWriter.println("WARNING: redirector not stopped: " + errRedir.getName());
141                 }
142             }
143             logWriter.println("Finished redirectors");
144         }
145     }
146 
147     /**
148      * Launches process with given command line.
149      *
150      * @param cmdLine
151      *            command line
152      * @return associated Process object or null if not available
153      * @throws IOException
154      *             if error occurred in launching process
155      */
launchProcess(String cmdLine)156     protected Process launchProcess(String cmdLine) throws IOException {
157 
158     	// Runtime.exec(String) does not preserve quoted arguments
159     	// process = Runtime.getRuntime().exec(cmdLine);
160 
161     	String args[] = splitCommandLine(cmdLine);
162     	process = Runtime.getRuntime().exec(args);
163         return process;
164     }
165 
166     /**
167      * Splits command line into arguments preserving spaces in quoted arguments
168      * either with single and double quotes (not prefixed by '\').
169      *
170      * @param cmd
171      *            command line
172      * @return associated Process object or null if not available
173      */
174 /*
175     public String[] splitCommandLine(String cmd) {
176 
177         // allocate array for parsed arguments
178         int max_argc = 250;
179         Vector argv = new Vector();
180 
181         // parse command line
182         int len = cmd.length();
183         if (len > 0) {
184             for (int arg = 0; arg < len;) {
185                 // skip initial spaces
186                 while (Character.isWhitespace(cmd.charAt(arg))) arg++;
187                 // parse non-spaced or quoted argument
188                 for (int p = arg; ; p++) {
189                     // check for ending separator
190                     if (p >= len || Character.isWhitespace(cmd.charAt(p))) {
191                     	if (p > len) p = len;
192                     	String val = cmd.substring(arg, p);
193                         argv.add(val);
194                         arg = p + 1;
195                         break;
196                     }
197 
198                     // check for starting quote
199                     if (cmd.charAt(p) == '\"') {
200                          char quote = cmd.charAt(p++);
201                          // skip all chars until terminating quote or end of line
202                          for (; p < len; p++) {
203                              // check for terminating quote
204                              if (cmd.charAt(p) == quote)
205                             	 break;
206                              // skip escaped quote
207                              if (cmd.charAt(p) == '\\' && (p+1) < len && cmd.charAt(p+1) == quote)
208                             	 p++;
209                          }
210                      }
211 
212                     // skip escaped quote
213                     if (cmd.charAt(p) == '\\' && (p+1) < len && cmd.charAt(p+1) == '\"') {
214                     	p++;
215                     }
216                 }
217             }
218         }
219 
220     	logWriter.println("Splitted command line: " + argv);
221         int size = argv.size();
222         String args[] = new String[size];
223         return (String[])argv.toArray(args);
224 	}
225 */
splitCommandLine(String cmd)226     public String[] splitCommandLine(String cmd) {
227 
228         int len = cmd.length();
229         char chars[] = new char[len];
230         Vector<String> argv = new Vector<String>();
231 
232         if (len > 0) {
233             for (int arg = 0; arg < len;) {
234                 // skip initial spaces
235                 while (Character.isWhitespace(cmd.charAt(arg))) arg++;
236                 // parse non-spaced or quoted argument
237                 for (int p = arg, i = 0; ; p++) {
238                     // check for starting quote
239                     if (p < len && (cmd.charAt(p) == '\"' || cmd.charAt(p) == '\'')) {
240                          char quote = cmd.charAt(p++);
241                          // copy all chars until terminating quote or end of line
242                          for (; p < len; p++) {
243                              // check for terminating quote
244                              if (cmd.charAt(p) == quote) {
245                             	 p++;
246                             	 break;
247                              }
248                              // preserve escaped quote
249                              if (cmd.charAt(p) == '\\' && (p+1) < len && cmd.charAt(p+1) == quote)
250                             	 p++;
251                              chars[i++] = cmd.charAt(p);
252                          }
253                      }
254 
255                     // check for ending separator
256                     if (p >= len || Character.isWhitespace(cmd.charAt(p))) {
257                     	String val = new String(chars, 0, i);
258                         argv.add(val);
259                         arg = p + 1;
260                         break;
261                     }
262 
263                     // preserve escaped quote
264                     if (cmd.charAt(p) == '\\' && (p+1) < len
265                     		&& (cmd.charAt(p+1) == '\"' || cmd.charAt(p+1) == '\'')) {
266                     	p++;
267                     }
268 
269                     // copy current char
270                     chars[i++] = cmd.charAt(p);
271                 }
272             }
273         }
274 
275     	logWriter.println("Splitted command line: " + argv);
276         int size = argv.size();
277         String args[] = new String[size];
278         return argv.toArray(args);
279 	}
280 
281     /**
282      * Waits for launched process to exit.
283      *
284      * @param process
285      *            associated Process object or null if not available
286      * @throws IOException
287      *             if any exception occurs in waiting
288      */
WaitForProcessExit(Process process)289     protected void WaitForProcessExit(Process process) throws IOException {
290         ProcessWaiter thrd = new ProcessWaiter();
291         thrd.setDaemon(true);
292         thrd.start();
293         try {
294             thrd.join(settings.getTimeout());
295         } catch (InterruptedException e) {
296             throw new TestErrorException(e);
297         }
298 
299         if (thrd.isAlive()) {
300             // ProcessWaiter thread is still running (after we wait until a timeout) but is
301             // waiting for the debuggee process to exit. We send an interrupt request to
302             // that thread so it receives an InterrupedException and terminates.
303             thrd.interrupt();
304         }
305 
306         try {
307             int exitCode = process.exitValue();
308             logWriter.println("Finished debuggee with exit code: " + exitCode);
309             if (exitCode != expectedExitCode) {
310                 throw new TestErrorException("Debuggee exited with code " + exitCode +
311                         " but we expected code " + expectedExitCode);
312             }
313         } catch (IllegalThreadStateException e) {
314             logWriter.println("Terminate debuggee process with " + e);
315             GetRemoteStackTrace(process);
316             throw new TestErrorException("Debuggee process did not finish during timeout", e);
317         } finally {
318             // dispose any resources of the process
319             process.destroy();
320         }
321     }
322 
GetRealPid(Process process)323     private int GetRealPid(Process process) {
324       int initial_pid = GetInitialPid(process);
325       logWriter.println("initial pid: " + initial_pid);
326       String[] names = new String[] { "java", "dalvikvm", "dalvikvm32", "dalvikvm64" };
327       return FindPidFor(Paths.get("/proc").resolve(Integer.toString(initial_pid)), names);
328     }
329 
getPid(Path proc)330     private int getPid(Path proc) throws IOException {
331       try {
332         // See man 5 proc for information on stat file. All we really care about is that it is a
333         // list of various pieces of information about the process separated by ' '. The first of
334         // these is the PID.
335         return Integer.valueOf(new String(Files.readAllBytes(proc.resolve("stat"))).split(" ")[0]);
336       } catch (IOException e) {
337         logWriter.printError("Failed to find real pid of process:", e);
338         return -1;
339       }
340     }
341 
FindPidFor(Path cur, String[] names)342     private int FindPidFor(Path cur, String[] names) {
343       try {
344         if (!cur.toFile().exists()) {
345           return -1;
346         }
347         int pid = getPid(cur);
348         if (Arrays.stream(names).anyMatch(
349             (x) -> {
350               try {
351                 return cur.resolve("exe").toRealPath().endsWith(x);
352               } catch (Exception e) {
353                 return false;
354               }
355             })) {
356           return pid;
357         } else {
358           Path children = cur.resolve("task").resolve(Integer.toString(pid)).resolve("children");
359           if (!children.toFile().exists()) {
360             logWriter.printError("Failed to find children file");
361             return -1;
362           } else {
363             for (String child_pid : ReadChildPids(children.toFile())) {
364               logWriter.println("Examining pid: " + child_pid);
365               int res = FindPidFor(Paths.get("/proc").resolve(child_pid), names);
366               if (res != -1) {
367                 return res;
368               }
369             }
370             return -1;
371           }
372         }
373       } catch (Exception e) {
374         logWriter.printError("Failed to find real pid of process:", e);
375         return -1;
376       }
377     }
378 
ReadChildPids(File children)379     private String[] ReadChildPids(File children) throws Exception {
380       if (!children.exists()) {
381         return new String[0];
382       } else {
383         return new String(Files.readAllBytes(children.toPath())).split(" ");
384       }
385     }
386 
GetInitialPid(Process process)387     private int GetInitialPid(Process process) {
388       try {
389         Class<?> unix_process_class = Class.forName("java.lang.UNIXProcess");
390         if (!process.getClass().equals(unix_process_class)) {
391           throw new TestErrorException("Process is of unexpected type. Timeout happened.");
392         }
393         Field pid = unix_process_class.getDeclaredField("pid");
394         pid.setAccessible(true);
395         return (int)pid.get(process);
396       } catch (ReflectiveOperationException e) {
397         throw new TestErrorException("Unable to get pid from process to debug timeout!", e);
398       }
399     }
400 
GetRemoteStackTrace(Process process)401     private void GetRemoteStackTrace(Process process) throws IOException {
402       int pid_number = realPid == -1 ? GetRealPid(process) : realPid;
403       if (pid_number == -1) {
404         logWriter.printError("Could not determine subprocess pid. Cannot dump process");
405         return;
406       }
407       String pid = Integer.toString(realPid);
408       logWriter.println("trying to dump " + pid);
409       List<String> cmd = new ArrayList<>(Arrays.asList(splitCommandLine(settings.getDumpProcessCommand())));
410       cmd.add(pid);
411       logWriter.println("command: " + cmd);
412 
413       ProcessBuilder b = new ProcessBuilder(cmd).redirectErrorStream(true);
414       Process p = null;
415       StreamRedirector out = null;
416       try {
417         p = b.start();
418         out = new StreamRedirector(p.getInputStream(), logWriter, "dump-" + pid);
419         out.setDaemon(true);
420         out.start();
421         p.waitFor();
422         // Wait 5 seconds for the streams to catch up.
423         Thread.sleep(5000);
424       } catch (Exception e) {
425         throw new TestErrorException("Unable to get process dump!", e);
426       } finally {
427         if (p != null) {
428           p.destroy();
429         }
430         if (out != null) {
431           try {
432             out.exit();
433             out.join();
434           } catch (Exception e) {
435             logWriter.println("Suppressing error: " + e);
436           }
437         }
438       }
439     }
440 
441     /**
442      * Separate thread for waiting for process exit for specified timeout.
443      */
444     class ProcessWaiter extends Thread {
445         @Override
run()446         public void run() {
447             try {
448                 process.waitFor();
449             } catch (InterruptedException e) {
450                 logWriter.println("Ignoring exception in ProcessWaiter thread interrupted: " + e);
451             }
452         }
453     }
454 
455     @Override
start()456     public void start() {
457     }
458 
459     @Override
stop()460     public void stop() {
461     }
462 }
463