/* * Copyright (c) 2001, 2019, 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. */ public 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) { Vector args = new Vector(); args.add(argumentHandler.getLaunchExecPath()); String javaOpts = argumentHandler.getLaunchOptions(); if (javaOpts != null && javaOpts.length() > 0) { StringTokenizer st = new StringTokenizer(javaOpts); while (st.hasMoreTokens()) { args.add(st.nextToken()); } } /* String classPath = System.getProperty("java.class.path"); args.add("-classpath") args.add(classPath); */ args.add("-Xdebug"); String server; if (argumentHandler.isAttachingConnector()) { server = "y"; } else { server = "n"; } String jdwpArgs = "-Xrunjdwp:" + "server=" + server + ",transport=" + argumentHandler.getTransportName() + ",address=" + transportAddress; if (! argumentHandler.isDefaultJVMDIStrictMode()) { if (argumentHandler.isJVMDIStrictMode()) jdwpArgs += ",strict=y"; else jdwpArgs += ",strict=n"; } args.add(jdwpArgs); if (classToExecute != null) { StringTokenizer st = new StringTokenizer(classToExecute); while (st.hasMoreTokens()) { args.add(st.nextToken()); } } String[] rawArgs = argumentHandler.getRawArguments(); for (int i = 0; i < rawArgs.length; i++) { String rawArg = rawArgs[i]; // " has to be escaped on windows if (IS_WINDOWS) { rawArg = rawArg.replace("\"", "\\\""); } args.add(rawArg); } String[] argsArray = new String[args.size()]; for (int i = 0; i < args.size(); i++) { argsArray[i] = (String) args.elementAt(i); } return argsArray; } /** * Make command line to launch debugee VM as an array of arguments. */ public String[] makeCommandLineArgs(String classToExecute) { return makeCommandLineArgs(classToExecute, makeTransportAddress()); } /** * Make connection to remote BindServer and start BindServerListener thread. * * @throws IOException if I/O error occured while connecting */ public void connectToBindServer(String taskID) { if (bindServerListener != null) { throw new Failure("Connection to BindServer already exists"); } try { bindServerListener = new BindServerListener(this); bindServerListener.setDaemon(true); bindServerListener.connect(taskID); bindServerListener.start(); } catch (IOException e) { e.printStackTrace(getOutStream()); throw new Failure("Caught exception while connecting to BindServer:\n\t" + e); } } /** * Split string into list of substrings using specified separator. */ private static String[] splitString(String givenString, String separator) { Vector tmpList = new Vector(); StringTokenizer tokenizer = new StringTokenizer(givenString, separator); while(tokenizer.hasMoreTokens()) { tmpList.add(tokenizer.nextToken()); } String[] list = new String[tmpList.size()]; for (int i = 0; i < tmpList.size(); i++) { list[i] = tmpList.elementAt(i); } return list; } /** * Send command to remote BindServer 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 LinkedList replies = new LinkedList(); /** * Make thread. */ public BindServerListener(Log.Logger logger) { this.logger = logger; } /** * Establish connection to BindServer. */ 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 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. */ 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