/* * Copyright (c) 2001, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package nsk.share.jpda; import nsk.share.*; import java.io.*; import java.net.*; /** * This class is used to control debugee VM process. *

* Object of this class is constructed by DebugeeBinder * and is used as a mirror of debugee VM process. * It provides abilities to launch such process, * redirect standard output streams, wait for process terminates * or kill the process, and so on. *

* This is an abstract class that declares abstract methods to control * debugee VM process. * Derived classes should implement these methods corresponding to the mode * that the process should be started in (locally, remotely or manually). *

* Particular derived classes nsk.share.jdi.Debugee and * nsk.share.jdwp.Debugee provides additional abilities * to control debugee VM using JDI or JDWP specific features. * * @see DebugeeBinder * * @see nsk.share.jdi.Debugee * @see nsk.share.jdwp.Debugee */ abstract public class DebugeeProcess extends FinalizableObject { /** Default prefix for log messages. */ public static final String LOG_PREFIX = "binder> "; public static final String DEBUGEE_STDOUT_LOG_PREFIX = "debugee.stdout> "; public static final String DEBUGEE_STDERR_LOG_PREFIX = "debugee.stderr> "; /** Messages prefix. */ protected String prefix = LOG_PREFIX; /** Binder that creates this debugee process. */ protected DebugeeBinder binder = null; /** Messages log from binder. */ protected Log log = null; /** Communicational channel between debuger and debugee. */ protected IOPipe pipe = null; /** Argument handler from binder. */ protected DebugeeArgumentHandler argumentHandler = null; /** Need or not to check debuggee process termination at exit. */ protected boolean checkTermination = false; /** Debugee VM process or null if not available. */ protected Process process = null; /** Make new Debugee object for the given binder. */ protected DebugeeProcess (DebugeeBinder binder) { this.binder = binder; this.log = binder.getLog(); } /** * Return already bound ServerSocket for IOPipe connection or null. */ protected ServerSocket getPipeServerSocket() { return binder.getPipeServerSocket(); } /** Return DebugeeArgumentHandler of the debugee object. */ public DebugeeArgumentHandler getArgumentHandler() { return binder.getArgumentHandler(); } /** Return Log of the debugee object. */ public Log getLog() { return log; } /** Return Process object associated with debugee VM or null. */ public Process getProcess() { return process; } // --------------------------------------------------- // /** Created and return new IOPipe channel to the debugee VM. */ public IOPipe createIOPipe() { if (pipe != null) { throw new TestBug("IOPipe channel is already created"); } pipe = new IOPipe(this); return pipe; } /** Return IOPipe channel or null if not yet ctreated. */ public IOPipe getIOPipe() { return pipe; } /** Receive and return signal from the debugee VM via IOPipe channel. * * @throws TestBug if IOPipe channel is not created */ public String receiveSignal() { if ( pipe == null ) throw new TestBug("IOPipe channel is not initialized"); return pipe.readln(); } /** Receive an expected signal from the debugee VM via IOPipe channel. * * @throws Failure if received signal is not equal to the expected one * @throws TestBug if IOPipe channel is not created */ public void receiveExpectedSignal(String signal) { String line = receiveSignal(); if (line == null || !line.equals(signal)) throw new Failure("Received unexpected signal from debugee: " + line); display("Received expected signal from debugee: " + signal); } /** Send signal to the debugee VM via IOPipe channel. * * @throws TestBug if IOPipe channel is not defined created. */ public void sendSignal(String signal) { if (pipe == null) throw new TestBug("IOPipe channel is not initialized"); pipe.println(signal); } // --------------------------------------------------- // /** Wait until the debugee VM shutdown or crash. */ abstract protected int waitForDebugee () throws InterruptedException; /** Kill the debugee VM. */ abstract protected void killDebugee (); /** Check whether the debugee VM has been terminated. */ abstract public boolean terminated (); /** Return the debugee VM exit status. */ abstract public int getStatus (); /** Get a pipe to write to the debugee's stdin stream. */ abstract protected OutputStream getInPipe (); /** Get a pipe to read the debugee's stdout stream. */ abstract protected InputStream getOutPipe (); /** Get a pipe to read the debugee's stderr stream. */ abstract protected InputStream getErrPipe (); // --------------------------------------------------- // /** * Wait until the debugee VM shutdown or crash, * and let finish its stdout, stderr, and stdin * redirectors (if any). * * @return Debugee process exit code. * @see #waitForRedirectors(long) */ public int waitFor () { long timeout = binder.getArgumentHandler().getWaitTime() * 60 * 1000; int exitCode = 0; try { exitCode = waitForDebugee(); } catch (InterruptedException ie) { ie.printStackTrace(log.getOutStream()); throw new Failure("Caught exception while waiting for debuggee process: \n\t" + ie); } waitForRedirectors(timeout); if (process != null) { process.destroy(); } return exitCode; } /** * Wait until the debugee VM redirectors to complete for specified timeout. * * @see #waitFor() */ public void waitForRedirectors (long timeout) { try { if (stdinRedirector != null) { if (stdinRedirector.isAlive()) { stdinRedirector.join(timeout); if (stdinRedirector.isAlive()) { log.complain("Timeout for waiting STDIN redirector exceeded"); stdinRedirector.interrupt(); } } stdinRedirector = null; }; if (stdoutRedirector != null) { if (stdoutRedirector.isAlive()) { stdoutRedirector.join(timeout); if (stdoutRedirector.isAlive()) { log.complain("Timeout for waiting STDOUT redirector exceeded"); stdoutRedirector.interrupt(); } } stdoutRedirector = null; }; if (stderrRedirector != null) { if (stderrRedirector.isAlive()) { stderrRedirector.join(timeout); if (stderrRedirector.isAlive()) { log.complain("Timeout for waiting STDERR redirector exceeded"); stderrRedirector.interrupt(); } } stderrRedirector = null; }; } catch (InterruptedException ie) { ie.printStackTrace(log.getOutStream()); throw new Failure("Caught exception while waiting for debuggee output redirectors: \n\t" + ie); } } // --------------------------------------------------- // /** * Get a pipe to write to the debugee's stdin stream, * or throw TestBug exception is redirected. */ final public OutputStream getStdin () { if (stdinRedirector != null) throw new TestBug("Debugee's stdin is redirected"); return getInPipe(); } /** * Get a pipe to read the debugee's stdout stream, * or throw TestBug exception is redirected. */ final public InputStream getStdout () { if (stdoutRedirector != null) throw new TestBug("Debugee's stdout is redirected"); return getOutPipe(); } /** * Get a pipe to read the debugee's stderr stream, * or throw TestBug exception is redirected. */ final public InputStream getStderr () { if (stderrRedirector != null) throw new TestBug("Debugee's stderr is redirected"); return getErrPipe(); } // --------------------------------------------------- // private IORedirector stdoutRedirector = null; private IORedirector stderrRedirector = null; private IORedirector stdinRedirector = null; // /** // * Start a thread redirecting the given in stream // * to the debugee's stdin. If the debugee's stdin was already // * redirected, the TestBug exception is thrown. // */ // final public void redirectStdin(InputStream in) { // if (stdinRedirector != null) // throw new TestBug("the debugee's stdin is already redirected"); // stdinRedirector = new IORedirector(in,getInPipe()); // stdinRedirector.setName("IORedirector for stdin"); // stdinRedirector.setDaemon(true); // stdinRedirector.start(); // } /** * Start thread redirecting the debugee's stdout to the * given out stream. If the debugee's stdout * was already redirected, the TestBug exception is thrown. * * @deprecated Use redirectStdout(Log, String) instead. */ public void redirectStdout(OutputStream out) { if (stdoutRedirector != null) { return; } // throw new TestBug("Debugee's stdout is already redirected"); stdoutRedirector = new IORedirector(getOutPipe(),out); stdoutRedirector.setPrefix(DEBUGEE_STDOUT_LOG_PREFIX); stdoutRedirector.setName("IORedirector for stdout"); stdoutRedirector.setDaemon(true); stdoutRedirector.start(); } /** * Start thread redirecting the debugee's stdout to the * given Log. If the debugee's stdout * was already redirected, the TestBug exception is thrown. */ public void redirectStdout(Log log, String prefix) { if (stdoutRedirector != null) { // stdoutRedirector.setPrefix(prefix); return; // throw new TestBug("the debugee's stdout is already redirected"); } stdoutRedirector = new IORedirector(new BufferedReader(new InputStreamReader(getOutPipe())), log, prefix); stdoutRedirector.setName("IORedirector for stdout"); stdoutRedirector.setDaemon(true); stdoutRedirector.start(); } /** * Start thread redirecting the debugee's stderr to the * given err stream. If the debugee's stderr * was already redirected, the TestBug exception is thrown. * * @deprecated Use redirectStderr(Log, String) instead. */ public void redirectStderr(OutputStream err) { if (stderrRedirector != null) { return; } // throw new TestBug("Debugee's stderr is already redirected"); stderrRedirector = new IORedirector(getErrPipe(),err); stderrRedirector.setPrefix(DEBUGEE_STDERR_LOG_PREFIX); stdoutRedirector.setName("IORedirector for stderr"); stderrRedirector.setDaemon(true); stderrRedirector.start(); } /** * Start thread redirecting the debugee's stderr to the * given Log. If the debugee's stderr * was already redirected, the TestBug exception is thrown. */ public void redirectStderr(Log log, String prefix) { if (stderrRedirector != null) { // stderrRedirector.setPrefix(prefix); return; // throw new TestBug("Debugee's stderr is already redirected"); } stderrRedirector = new IORedirector(new BufferedReader(new InputStreamReader(getErrPipe())), log, prefix); stdoutRedirector.setName("IORedirector for stderr"); stderrRedirector.setDaemon(true); stderrRedirector.start(); } /** * Start thread redirecting the debugee's stdout/stderr to the * given Log using standard prefixes. * If the debugee's stdout/stderr were already redirected, * the TestBug exception is thrown. */ public void redirectOutput(Log log) { redirectStdout(log, "debugee.stdout> "); redirectStderr(log, "debugee.stderr> "); } // --------------------------------------------------- // /** * Kill the debugee VM if it is not terminated yet. * * @throws Throwable if any throwable exception is thrown during finalization */ public void close() { if (checkTermination) { if (!terminated()) { complain("Debugee VM has not exited correctly: trying to kill it"); killDebugee(); } checkTermination = false; } } // --------------------------------------------------- // /** * Display log message with prefix. */ protected void display(String message) { log.display(prefix + message); } /** * Complain about error with specified message. */ protected void complain(String message) { log.complain(prefix + message); } /** * Finalize debuggee VM wrapper by invoking close(). * * @throws Throwable if any throwable exception is thrown during finalization */ protected void finalize() throws Throwable { close(); super.finalize(); } }