| /* |
| * 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. |
| * <p> |
| * Object of this class is constructed by <code>DebugeeBinder</code> |
| * 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. |
| * <p> |
| * 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). |
| * <p> |
| * Particular derived classes <code>nsk.share.jdi.Debugee</code> and |
| * <code>nsk.share.jdwp.Debugee</code> 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 <i>null</i> if not available. */ |
| protected Process process = null; |
| |
| /** Make new <code>Debugee</code> 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 <code>DebugeeArgumentHandler</code> of the debugee object. */ |
| public DebugeeArgumentHandler getArgumentHandler() { |
| return binder.getArgumentHandler(); |
| } |
| |
| /** Return <code>Log</code> of the debugee object. */ |
| public Log getLog() { |
| return log; |
| } |
| |
| /** Return <code>Process</code> object associated with debugee VM or <i>null</i>. */ |
| 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 <code>signal</code> 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 <code>signal</code> 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 <code>timeout</code>. |
| * |
| * @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 <code>in</code> 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 <code>out</code> 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 <code>Log</code>. 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 <code>err</code> 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 <code>Log</code>. 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 <code>Log</code> 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 <code>close()</code>. |
| * |
| * @throws Throwable if any throwable exception is thrown during finalization |
| */ |
| protected void finalize() throws Throwable { |
| close(); |
| super.finalize(); |
| } |
| } |