/* * 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();
}
}