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