/* * 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.*; import java.util.*; /** * This class provides debugger with ability to launch * debuggee VM and to make connection to it using JDI connector or * JDWP transport. *
* The present version of Binder
allows
* to launch debuggee VM either on local machine (local launch mode),
* or on remote host using BindServer
utility
* (remote launch mode). Also there is an ability to launch
* debuggee VM manually as a separate process on local or remote machine
* (manual launch mode), which is usefull for debugging.
* All these launching modes are specified by command line option
* -debugee.launch
recognized by DebugeeArgumentHandler
.
*
* Binder
also makes it possible to establish TCP/IP
* connection between debugger and debuggee throw IOPipe
* object. This connection allows debugger to communicate with debuggee
* by exchanging with synchronization messages and data.
*
* To launch debuggee VM and bind to it use bindToDebugee()
* method. This method construct mirror of debugee VM, represented by
* object of DebugeeProcess
class or derived. This mirror object
* allows to control debuggee VM.
*
* See also nsk.share.jdi.Binder
and nsk.share.jdwp.Binder
* classes which provide launching and binding to debuggee VM using specific
* JDI and JDWP features.
*
* @see DebugeeArgumentHandler
* @see DebugeeProcess
* @see IOPipe
* @see BindServer
*
* @see nsk.share.jdi.Binder
* @see nsk.share.jdwp.Binder
*/
public class DebugeeBinder extends Log.Logger implements Finalizable {
private static final boolean IS_WINDOWS = System.getProperty("os.name")
.toLowerCase()
.startsWith("win");
public static int TRY_DELAY = 1000; // milliseconds
public static int CONNECT_TIMEOUT = 1 * 60 * 1000; // milliseconds
public static int CONNECT_TRY_DELAY = 2 * 1000; // milliseconds
public static int CONNECT_TRIES = CONNECT_TIMEOUT / CONNECT_TRY_DELAY;
public static int THREAD_TIMEOUT = 2 * CONNECT_TRY_DELAY; // milliseconds
public static int PING_TIMEOUT = 30 * 1000; // milliseconds
public static int SOCKET_TIMEOUT = 2 * 1000; // milliseconds
public static int SOCKET_LINGER = 1; // milliseconds
private static int TRACE_LEVEL_PACKETS = 10;
private static int TRACE_LEVEL_THREADS = 20;
private static int TRACE_LEVEL_ACTIONS = 30;
private static int TRACE_LEVEL_SOCKETS = 40;
private static int TRACE_LEVEL_IO = 50;
/**
* Default message prefix for Binder
object.
*/
public static final String LOG_PREFIX = "binder> ";
private DebugeeArgumentHandler argumentHandler = null;
/**
* Get version string.
*/
public static String getVersion () {
return "@(#)Binder.java %I% %E%";
}
// -------------------------------------------------- //
private BindServerListener bindServerListener = null;
private ServerSocket pipeServerSocket = null;
// -------------------------------------------------- //
/**
* Incarnate new Binder obeying the given
* argumentHandler
; and assign the given
* log
.
*/
public DebugeeBinder (DebugeeArgumentHandler argumentHandler, Log log) {
super(log, LOG_PREFIX);
this.argumentHandler = argumentHandler;
Finalizer finalizer = new Finalizer(this);
finalizer.activate();
}
/**
* Get argument handler of this binder object.
*/
DebugeeArgumentHandler getArgumentHandler() {
return argumentHandler;
}
// -------------------------------------------------- //
/**
* Wait for given thread finished for THREAD_TIMEOUT timeout and
* interrupt this thread if not finished.
*
* @param thr thread to wait for
* @param logger to write log messages to
*/
public static void waitForThread(Thread thr, Log.Logger logger) {
waitForThread(thr, THREAD_TIMEOUT, logger);
}
/**
* Wait for given thread finished for specified timeout and
* interrupt this thread if not finished.
*
* @param thr thread to wait for
* @param millisecs timeout in milliseconds
* @param logger to write log messages to
*/
public static void waitForThread(Thread thr, long millisecs, Log.Logger logger) {
if (thr != null) {
if (thr.isAlive()) {
try {
logger.trace(TRACE_LEVEL_THREADS, "Waiting for thread: " + thr.getName());
thr.join(millisecs);
} catch (InterruptedException e) {
e.printStackTrace(logger.getOutStream());
throw new Failure ("Thread interrupted while waiting for another thread:\n\t"
+ e);
} finally {
if (thr.isAlive()) {
logger.trace(TRACE_LEVEL_THREADS, "Interrupting not finished thread: " + thr);
thr.interrupt();
}
}
}
}
}
/**
* Make preperation for IOPipe connection before starting debugee VM process.
* May change options in the passed argumentHandler
.
*/
protected void prepareForPipeConnection(DebugeeArgumentHandler argumentHandler) {
if (argumentHandler.isTransportAddressDynamic()) {
try {
pipeServerSocket = new ServerSocket();
pipeServerSocket.setReuseAddress(false);
pipeServerSocket.bind(null);
} catch (IOException e) {
e.printStackTrace(getOutStream());
throw new Failure("Caught IOException while binding for IOPipe connection: \n\t"
+ e);
}
int port = pipeServerSocket.getLocalPort();
argumentHandler.setPipePortNumber(port);
}
}
/**
* Return already bound ServerSocket for IOPipe connection or null.
*/
protected ServerSocket getPipeServerSocket() {
return pipeServerSocket;
}
/**
* Close ServerSocket used for IOPipeConnection if any.
*/
private void closePipeServerSocket() {
if (pipeServerSocket != null) {
try {
pipeServerSocket.close();
} catch (IOException e) {
println("# WARNING: Caught IOException while closing ServerSocket used for IOPipe connection: \n\t"
+ e);
}
}
}
// -------------------------------------------------- //
/**
* Make environment for launching JVM process.
*/
public String[] makeProcessEnvironment() {
/*
String env = new String[0];
return env;
*/
return null;
}
/**
* Launch process by the specified command line.
*
* @throws IOException if I/O error occured while launching process
*/
public Process launchProcess(String cmdLine) throws IOException {
String env[] = makeProcessEnvironment();
return Runtime.getRuntime().exec(cmdLine, env);
}
/**
* Launch process by the arguments array.
*
* @throws IOException if I/O error occured while launching process
*/
public Process launchProcess(String[] args) throws IOException {
String env[] = makeProcessEnvironment();
return Runtime.getRuntime().exec(args, env);
}
/**
* Make string representation of debuggee VM transport address according
* to current command line options.
*/
public String makeTransportAddress() {
String address = null;
if (argumentHandler.isSocketTransport()) {
if (argumentHandler.isListeningConnector()) {
address = argumentHandler.getTestHost()
+ ":" + argumentHandler.getTransportPort();
} else {
address = argumentHandler.getTransportPort();
}
} else if (argumentHandler.isShmemTransport() ) {
address = argumentHandler.getTransportSharedName();
} else {
throw new TestBug("Undefined transport type: "
+ argumentHandler.getTransportType());
}
return address;
}
/**
* Make command line to launch debugee VM as a string using given quote symbol,
* using specified transportAddress
for JDWP connection.
*/
public String makeCommandLineString(String classToExecute, String transportAddress, String quote) {
String[] args = makeCommandLineArgs(classToExecute, transportAddress);
return ArgumentParser.joinArguments(args, quote);
}
/**
* Make command line to launch debugee VM as a string using given quote symbol.
*/
public String makeCommandLineString(String classToExecute, String quote) {
return makeCommandLineString(classToExecute, makeTransportAddress(), quote);
}
/**
* Make command line to launch debugee VM as a string using default quote symbol,
* using specified transportAddress
for JDWP connection.
*/
/*
public String makeCommandLineString(String classToExecute, String transportAddress) {
return makeCommandLineString(classToExecute, transportAddress, "\"");
}
*/
/**
* Make command line to launch debugee VM as a string using default quote symbol.
*/
/*
public String makeCommandLineString(String classToExecute) {
return makeCommandLineString(classToExecute, makeTransportAddress());
}
*/
/**
* Make command line to launch debugee VM as an array of arguments,
* using specified transportAddress
for JDWP connection.
*/
public String[] makeCommandLineArgs(String classToExecute, String transportAddress) {
VectorBindServer
and receive reply.
*
* @throws IOException if I/O error occured while launching process
*/
public synchronized Object sendRemoteCommand(Object command) {
try {
bindServerListener.sendCommand(command);
Object reply = bindServerListener.getReply();
return reply;
} catch (IOException e) {
e.printStackTrace(log.getOutStream());
throw new Failure("Unexpected exception while sending command to BindServer:\n\t"
+ e);
}
}
/**
* Launch remote process using request to BindServer
.
*
* @throws IOException if I/O error occured
*/
public void launchRemoteProcess(String[] args) throws IOException {
String pathSeparator = System.getProperty("path.separator");
BindServer.LaunchDebugee command =
new BindServer.LaunchDebugee(args,
System.getProperty("file.separator"),
System.getProperty("user.dir"),
splitString(System.getProperty("java.library.path"), pathSeparator),
splitString(System.getProperty("java.class.path"), pathSeparator),
splitString(System.getProperty("java.library.path"), pathSeparator));
Object reply = sendRemoteCommand(command);
if (reply instanceof BindServer.OK) {
// do nothing
} else if (reply instanceof BindServer.RequestFailed) {
BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;
throw new Failure("BindServer error: " + castedReply.reason);
} else {
throw new Failure("Wrong reply from BindServer: " + reply);
}
}
/**
* Return exit status of the remotely launched process
* using request to BindServer
.
*/
public int getRemoteProcessStatus () {
Object reply = sendRemoteCommand(new BindServer.DebugeeExitCode());
if (reply instanceof BindServer.OK) {
BindServer.OK castedReply = (BindServer.OK)reply;
return (int)castedReply.info;
} else if (reply instanceof BindServer.CaughtException) {
BindServer.CaughtException castedReply = (BindServer.CaughtException)reply;
throw new IllegalThreadStateException(castedReply.reason);
} else if (reply instanceof BindServer.RequestFailed) {
BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;
throw new Failure("BindServer error: " + castedReply.reason);
} else {
throw new Failure("Wrong reply from BindServer: " + reply);
}
}
/**
* Check whether the remotely launched process has been terminated
* using request to BindServer
.
*/
public boolean isRemoteProcessTerminated () {
try {
int value = getRemoteProcessStatus();
return true;
} catch (IllegalThreadStateException e) {
return false;
}
}
// ---------------------------------------------- //
/**
* Kill the remotely launched process
* using request to BindServer
.
*/
public void killRemoteProcess () {
Object reply = sendRemoteCommand(new BindServer.KillDebugee());
if (reply instanceof BindServer.OK) {
return;
} else if (reply instanceof BindServer.RequestFailed) {
BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;
throw new Failure("BindServer error: " + castedReply.reason);
} else {
throw new Failure("Wrong reply from BindServer: " + reply);
}
}
/**
* Wait until the remotely launched process exits or crashes
* using request to BindServer
.
*/
public int waitForRemoteProcess () {
Object reply = sendRemoteCommand(new BindServer.WaitForDebugee(0));
if (reply instanceof BindServer.OK) {
BindServer.OK castedReply = (BindServer.OK)reply;
return (int)castedReply.info;
} else if (reply instanceof BindServer.RequestFailed) {
BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;
throw new Failure("BindServer error: " + castedReply.reason);
} else {
throw new Failure("Wrong reply from BindServer: " + reply);
}
}
/**
* Close binder by closing all started threads.
*/
public void close() {
if (bindServerListener != null) {
bindServerListener.close();
}
closePipeServerSocket();
}
/**
* Finalize binder by invoking close()
.
*
* @throws Throwable if any throwable exception is thrown during finalization
*/
protected void finalize() throws Throwable {
close();
super.finalize();
}
/**
* Finalize binder at exit by invoking finalize()
.
*
* @throws Throwable if any throwable exception is thrown during finalization
*/
public void finalizeAtExit() throws Throwable {
finalize();
}
/**
* Separate thread for listening connection from BindServer
.
*/
private class BindServerListener extends Thread {
private SocketConnection connection = null;
private Log.Logger logger = null;
/** List of received responses from BindServer
. */
private LinkedListBindServer
.
*/
public void connect(String taskID) throws IOException {
String host = argumentHandler.getDebugeeHost();
int port = argumentHandler.getBindPortNumber();
display("Connecting to BindServer: " + host + ":" + port);
connection = new SocketConnection(logger, "BindServer");
// connection.setPingTimeout(DebugeeBinder.PING_TIMEOUT);
connection.attach(host, port);
handshake(taskID);
}
/**
* Receive OK(version) from BindServer and check received version number.
*/
private void handshake(String taskID) {
// receive OK(version)
trace(TRACE_LEVEL_ACTIONS, "Waiting for initial OK(version) from BindServer");
Object reply = connection.readObject();
trace(TRACE_LEVEL_ACTIONS, "Got initial OK(version) from BindServer: " + reply);
if (reply instanceof BindServer.RequestFailed) {
BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;
trace(TRACE_LEVEL_ACTIONS, "Reply is RequestFailed: throw Failure");
throw new Failure("BindServer error: " + castedReply.reason);
} else if (reply instanceof BindServer.OK) {
BindServer.OK castedReply = (BindServer.OK)reply;
trace(TRACE_LEVEL_ACTIONS, "Reply is OK: check BindServer version");
if (castedReply.info != BindServer.VERSION) {
throw new Failure("Wrong version of BindServer: " + castedReply.info
+ " (expected: " + BindServer.VERSION + ")");
}
display("Connected to BindServer: version " + castedReply.info);
} else {
trace(TRACE_LEVEL_ACTIONS, "Reply is unknown: throw Failure");
throw new Failure("Wrong reply from BindServer: " + reply);
}
// send TaskID(id)
try {
trace(TRACE_LEVEL_ACTIONS, "Sending TaskID(id) to BindServer");
sendCommand(new BindServer.TaskID(taskID));
trace(TRACE_LEVEL_ACTIONS, "Sent TaskID(id) to BindServer");
} catch (IOException e) {
throw new Failure("Caught IOException while sending TaskID(id) to BindServer:\n\t"
+ e);
}
}
/**
* Check if thread is connected to BindServer
.
*/
public boolean isConnected() {
return (connection != null && connection.isConnected());
}
/**
* Send a command to BindServer.
*/
public synchronized void sendCommand(Object command) throws IOException {
connection.writeObject(command);
}
/**
* Receive response from BindServer
.
*/
public Object getReply() {
synchronized (replies) {
while (replies.isEmpty()) {
if (!isConnected()) {
throw new Failure("No reply from BindServer: connection lost");
}
try {
replies.wait(TRY_DELAY);
} catch (InterruptedException e) {
e.printStackTrace(getOutStream());
throw new Failure("Thread interrupted while waiting for reply from BindServer:\n\t"
+ e);
}
}
Object reply = replies.removeFirst();
if (reply == null) {
throw new Failure("No reply from BindServer: connection lost");
}
return reply;
}
}
/**
* Add response object to the list of received responses.
*/
private void addReply(BindServer.Response reply) {
synchronized (replies) {
replies.add(reply);
replies.notifyAll();
}
}
/**
* Read packets from BindServer
.
*/
public void disconnect() {
if (connection == null) return;
try {
sendCommand(new BindServer.Disconnect());
} catch (IOException e) {
display("Caught IOException while requesting disconnection with BindServer");
}
}
/**
* Close socket connection.
*/
public void closeConnection() {
if (connection != null) {
connection.close();
}
}
/**
* Wait for thread finished in the specified timeout or interrupt it.
*/
public void waitForThread(long millis) {
DebugeeBinder.waitForThread(this, millis, logger);
}
/**
* Close this thread by waiting for it finishes or interrupt it
* and close socket connection.
*/
public void close() {
disconnect();
waitForThread(DebugeeBinder.THREAD_TIMEOUT);
closeConnection();
}
} // BindServerListener
} // DebugeeBinder
connection and
* notify waiting thread if response or IOPipe message received.
* Received lines of redirected streams are put into log.
*/
public void run() {
trace(TRACE_LEVEL_THREADS, "BindServerListener thread started");
try {
for (;;) {
Object reply = connection.readObject();
if (reply == null) {
break;
} else if (reply instanceof BindServer.Disconnect) {
reply = null;
trace(TRACE_LEVEL_ACTIONS, "Packet is Disconnect: close connection");
break;
} else if (reply instanceof BindServer.RedirectedStream) {
BindServer.RedirectedStream castedReply = (BindServer.RedirectedStream)reply;
trace(TRACE_LEVEL_ACTIONS, "Packet is RedirectedStream: put message into log");
log.println(castedReply.line);
} else if (reply instanceof BindServer.Response) {
BindServer.Response castedReply = (BindServer.Response)reply;
trace(TRACE_LEVEL_ACTIONS, "Packet is reply: notify all threads waiting for reply");
addReply(castedReply);
} else {
trace(TRACE_LEVEL_ACTIONS, "Packet is unknown: throw Failure");
throw new Failure("Wrong reply from BindServer: " + reply);
}
}
} catch (Exception e) {
e.printStackTrace(getOutStream());
complain("Caught exception while reading packets from BindServer:\n\t" + e);
} finally {
closeConnection();
addReply(null);
trace(TRACE_LEVEL_THREADS, "BindServerListener thread finished");
}
}
/**
* Send Disconnect command to
BindServer