Path: blob/master/test/hotspot/jtreg/vmTestbase/nsk/share/jpda/DebugeeBinder.java
41161 views
/*1* Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223package nsk.share.jpda;2425import nsk.share.*;2627import java.io.*;28import java.net.*;29import java.util.*;3031/**32* This class provides debugger with ability to launch33* debuggee VM and to make connection to it using JDI connector or34* JDWP transport.35* <p>36* The present version of <code>Binder</code> allows37* to launch debuggee VM either on local machine (<i>local</i> launch mode),38* or on remote host using <code>BindServer</code> utility39* (<i>remote</i> launch mode). Also there is an ability to launch40* debuggee VM manually as a separate process on local or remote machine41* (<i>manual</i> launch mode), which is usefull for debugging.42* All these launching modes are specified by command line option43* <code>-debugee.launch</code> recognized by <code>DebugeeArgumentHandler</code>.44* <p>45* <code>Binder</code> also makes it possible to establish TCP/IP46* connection between debugger and debuggee throw <code>IOPipe</code>47* object. This connection allows debugger to communicate with debuggee48* by exchanging with synchronization messages and data.49* <p>50* To launch debuggee VM and bind to it use <code>bindToDebugee()</code>51* method. This method construct mirror of debugee VM, represented by52* object of <code>DebugeeProcess</code> class or derived. This mirror object53* allows to control debuggee VM.54* <p>55* See also <code>nsk.share.jdi.Binder</code> and <code>nsk.share.jdwp.Binder</code>56* classes which provide launching and binding to debuggee VM using specific57* JDI and JDWP features.58*59* @see DebugeeArgumentHandler60* @see DebugeeProcess61* @see IOPipe62* @see BindServer63*64* @see nsk.share.jdi.Binder65* @see nsk.share.jdwp.Binder66*/67public class DebugeeBinder extends Log.Logger implements Finalizable {6869private static final boolean IS_WINDOWS = System.getProperty("os.name")70.toLowerCase()71.startsWith("win");7273public static int TRY_DELAY = 1000; // milliseconds7475public static int CONNECT_TIMEOUT = 1 * 60 * 1000; // milliseconds76public static int CONNECT_TRY_DELAY = 2 * 1000; // milliseconds77public static int CONNECT_TRIES = CONNECT_TIMEOUT / CONNECT_TRY_DELAY;7879public static int THREAD_TIMEOUT = 2 * CONNECT_TRY_DELAY; // milliseconds80public static int PING_TIMEOUT = 30 * 1000; // milliseconds8182public static int SOCKET_TIMEOUT = 2 * 1000; // milliseconds83public static int SOCKET_LINGER = 1; // milliseconds8485private static int TRACE_LEVEL_PACKETS = 10;86private static int TRACE_LEVEL_THREADS = 20;87private static int TRACE_LEVEL_ACTIONS = 30;88private static int TRACE_LEVEL_SOCKETS = 40;89private static int TRACE_LEVEL_IO = 50;9091/**92* Default message prefix for <code>Binder</code> object.93*/94public static final String LOG_PREFIX = "binder> ";9596private DebugeeArgumentHandler argumentHandler = null;9798/**99* Get version string.100*/101public static String getVersion () {102return "@(#)Binder.java %I% %E%";103}104105// -------------------------------------------------- //106107private BindServerListener bindServerListener = null;108private ServerSocket pipeServerSocket = null;109110// -------------------------------------------------- //111112/**113* Incarnate new Binder obeying the given114* <code>argumentHandler</code>; and assign the given115* <code>log</code>.116*/117public DebugeeBinder (DebugeeArgumentHandler argumentHandler, Log log) {118super(log, LOG_PREFIX);119this.argumentHandler = argumentHandler;120Finalizer finalizer = new Finalizer(this);121finalizer.activate();122}123124/**125* Get argument handler of this binder object.126*/127DebugeeArgumentHandler getArgumentHandler() {128return argumentHandler;129}130131// -------------------------------------------------- //132133/**134* Wait for given thread finished for THREAD_TIMEOUT timeout and135* interrupt this thread if not finished.136*137* @param thr thread to wait for138* @param logger to write log messages to139*/140public static void waitForThread(Thread thr, Log.Logger logger) {141waitForThread(thr, THREAD_TIMEOUT, logger);142}143144/**145* Wait for given thread finished for specified timeout and146* interrupt this thread if not finished.147*148* @param thr thread to wait for149* @param millisecs timeout in milliseconds150* @param logger to write log messages to151*/152public static void waitForThread(Thread thr, long millisecs, Log.Logger logger) {153if (thr != null) {154if (thr.isAlive()) {155try {156logger.trace(TRACE_LEVEL_THREADS, "Waiting for thread: " + thr.getName());157thr.join(millisecs);158} catch (InterruptedException e) {159e.printStackTrace(logger.getOutStream());160throw new Failure ("Thread interrupted while waiting for another thread:\n\t"161+ e);162} finally {163if (thr.isAlive()) {164logger.trace(TRACE_LEVEL_THREADS, "Interrupting not finished thread: " + thr);165thr.interrupt();166}167}168}169}170}171172173/**174* Make preperation for IOPipe connection before starting debugee VM process.175* May change options in the passed <code>argumentHandler</code>.176*/177public void prepareForPipeConnection(DebugeeArgumentHandler argumentHandler) {178if (argumentHandler.isTransportAddressDynamic()) {179try {180pipeServerSocket = new ServerSocket();181pipeServerSocket.setReuseAddress(false);182pipeServerSocket.bind(null);183184} catch (IOException e) {185e.printStackTrace(getOutStream());186throw new Failure("Caught IOException while binding for IOPipe connection: \n\t"187+ e);188}189190int port = pipeServerSocket.getLocalPort();191argumentHandler.setPipePortNumber(port);192}193}194195/**196* Return already bound ServerSocket for IOPipe connection or null.197*/198protected ServerSocket getPipeServerSocket() {199return pipeServerSocket;200}201202/**203* Close ServerSocket used for IOPipeConnection if any.204*/205private void closePipeServerSocket() {206if (pipeServerSocket != null) {207try {208pipeServerSocket.close();209} catch (IOException e) {210println("# WARNING: Caught IOException while closing ServerSocket used for IOPipe connection: \n\t"211+ e);212}213}214}215216// -------------------------------------------------- //217218/**219* Make environment for launching JVM process.220*/221public String[] makeProcessEnvironment() {222/*223String env = new String[0];224return env;225*/226return null;227}228229/**230* Launch process by the specified command line.231*232* @throws IOException if I/O error occured while launching process233*/234public Process launchProcess(String cmdLine) throws IOException {235String env[] = makeProcessEnvironment();236return Runtime.getRuntime().exec(cmdLine, env);237}238239/**240* Launch process by the arguments array.241*242* @throws IOException if I/O error occured while launching process243*/244public Process launchProcess(String[] args) throws IOException {245String env[] = makeProcessEnvironment();246return Runtime.getRuntime().exec(args, env);247}248249/**250* Make string representation of debuggee VM transport address according251* to current command line options.252*/253public String makeTransportAddress() {254String address = null;255if (argumentHandler.isSocketTransport()) {256if (argumentHandler.isListeningConnector()) {257address = argumentHandler.getTestHost()258+ ":" + argumentHandler.getTransportPort();259} else {260address = argumentHandler.getTransportPort();261}262} else if (argumentHandler.isShmemTransport() ) {263address = argumentHandler.getTransportSharedName();264} else {265throw new TestBug("Undefined transport type: "266+ argumentHandler.getTransportType());267}268return address;269}270271/**272* Make command line to launch debugee VM as a string using given quote symbol,273* using specified <code>transportAddress</code> for JDWP connection.274*/275public String makeCommandLineString(String classToExecute, String transportAddress, String quote) {276String[] args = makeCommandLineArgs(classToExecute, transportAddress);277return ArgumentParser.joinArguments(args, quote);278}279280/**281* Make command line to launch debugee VM as a string using given quote symbol.282*/283public String makeCommandLineString(String classToExecute, String quote) {284return makeCommandLineString(classToExecute, makeTransportAddress(), quote);285}286287/**288* Make command line to launch debugee VM as a string using default quote symbol,289* using specified <code>transportAddress</code> for JDWP connection.290*/291/*292public String makeCommandLineString(String classToExecute, String transportAddress) {293return makeCommandLineString(classToExecute, transportAddress, "\"");294}295*/296297/**298* Make command line to launch debugee VM as a string using default quote symbol.299*/300/*301public String makeCommandLineString(String classToExecute) {302return makeCommandLineString(classToExecute, makeTransportAddress());303}304*/305306/**307* Make command line to launch debugee VM as an array of arguments,308* using specified <code>transportAddress</code> for JDWP connection.309*/310public String[] makeCommandLineArgs(String classToExecute, String transportAddress) {311Vector<String> args = new Vector<String>();312313args.add(argumentHandler.getLaunchExecPath());314315String javaOpts = argumentHandler.getLaunchOptions();316if (javaOpts != null && javaOpts.length() > 0) {317StringTokenizer st = new StringTokenizer(javaOpts);318319while (st.hasMoreTokens()) {320args.add(st.nextToken());321}322}323324/*325String classPath = System.getProperty("java.class.path");326args.add("-classpath")327args.add(classPath);328*/329330args.add("-Xdebug");331332String server;333if (argumentHandler.isAttachingConnector()) {334server = "y";335} else {336server = "n";337}338339String jdwpArgs = "-Xrunjdwp:"340+ "server=" + server341+ ",transport=" + argumentHandler.getTransportName()342+ ",address=" + transportAddress;343344if (! argumentHandler.isDefaultJVMDIStrictMode()) {345if (argumentHandler.isJVMDIStrictMode())346jdwpArgs += ",strict=y";347else348jdwpArgs += ",strict=n";349}350351args.add(jdwpArgs);352353if (classToExecute != null) {354StringTokenizer st = new StringTokenizer(classToExecute);355356while (st.hasMoreTokens()) {357args.add(st.nextToken());358}359}360361String[] rawArgs = argumentHandler.getRawArguments();362for (int i = 0; i < rawArgs.length; i++) {363String rawArg = rawArgs[i];364// " has to be escaped on windows365if (IS_WINDOWS) {366rawArg = rawArg.replace("\"", "\\\"");367}368args.add(rawArg);369}370371String[] argsArray = new String[args.size()];372for (int i = 0; i < args.size(); i++) {373argsArray[i] = (String) args.elementAt(i);374}375376return argsArray;377}378379/**380* Make command line to launch debugee VM as an array of arguments.381*/382public String[] makeCommandLineArgs(String classToExecute) {383return makeCommandLineArgs(classToExecute, makeTransportAddress());384}385386/**387* Make connection to remote BindServer and start BindServerListener thread.388*389* @throws IOException if I/O error occured while connecting390*/391public void connectToBindServer(String taskID) {392if (bindServerListener != null) {393throw new Failure("Connection to BindServer already exists");394}395try {396bindServerListener = new BindServerListener(this);397bindServerListener.setDaemon(true);398bindServerListener.connect(taskID);399bindServerListener.start();400} catch (IOException e) {401e.printStackTrace(getOutStream());402throw new Failure("Caught exception while connecting to BindServer:\n\t" + e);403}404}405406/**407* Split string into list of substrings using specified separator.408*/409private static String[] splitString(String givenString, String separator) {410Vector<String> tmpList = new Vector<String>();411StringTokenizer tokenizer = new StringTokenizer(givenString, separator);412while(tokenizer.hasMoreTokens()) {413tmpList.add(tokenizer.nextToken());414}415String[] list = new String[tmpList.size()];416for (int i = 0; i < tmpList.size(); i++) {417list[i] = tmpList.elementAt(i);418}419return list;420}421422/**423* Send command to remote <code>BindServer</code> and receive reply.424*425* @throws IOException if I/O error occured while launching process426*/427public synchronized Object sendRemoteCommand(Object command) {428try {429bindServerListener.sendCommand(command);430Object reply = bindServerListener.getReply();431return reply;432} catch (IOException e) {433e.printStackTrace(log.getOutStream());434throw new Failure("Unexpected exception while sending command to BindServer:\n\t"435+ e);436}437}438439/**440* Launch remote process using request to <code>BindServer</code>.441*442* @throws IOException if I/O error occured443*/444public void launchRemoteProcess(String[] args) throws IOException {445String pathSeparator = System.getProperty("path.separator");446BindServer.LaunchDebugee command =447new BindServer.LaunchDebugee(args,448System.getProperty("file.separator"),449System.getProperty("user.dir"),450splitString(System.getProperty("java.library.path"), pathSeparator),451splitString(System.getProperty("java.class.path"), pathSeparator),452splitString(System.getProperty("java.library.path"), pathSeparator));453454Object reply = sendRemoteCommand(command);455if (reply instanceof BindServer.OK) {456// do nothing457} else if (reply instanceof BindServer.RequestFailed) {458BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;459throw new Failure("BindServer error: " + castedReply.reason);460} else {461throw new Failure("Wrong reply from BindServer: " + reply);462}463}464465/**466* Return exit status of the remotely launched process467* using request to <code>BindServer</code>.468*/469public int getRemoteProcessStatus () {470Object reply = sendRemoteCommand(new BindServer.DebugeeExitCode());471if (reply instanceof BindServer.OK) {472BindServer.OK castedReply = (BindServer.OK)reply;473return (int)castedReply.info;474} else if (reply instanceof BindServer.CaughtException) {475BindServer.CaughtException castedReply = (BindServer.CaughtException)reply;476throw new IllegalThreadStateException(castedReply.reason);477} else if (reply instanceof BindServer.RequestFailed) {478BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;479throw new Failure("BindServer error: " + castedReply.reason);480} else {481throw new Failure("Wrong reply from BindServer: " + reply);482}483}484485/**486* Check whether the remotely launched process has been terminated487* using request to <code>BindServer</code>.488*/489public boolean isRemoteProcessTerminated () {490try {491int value = getRemoteProcessStatus();492return true;493} catch (IllegalThreadStateException e) {494return false;495}496}497498// ---------------------------------------------- //499500/**501* Kill the remotely launched process502* using request to <code>BindServer</code>.503*/504public void killRemoteProcess () {505Object reply = sendRemoteCommand(new BindServer.KillDebugee());506if (reply instanceof BindServer.OK) {507return;508} else if (reply instanceof BindServer.RequestFailed) {509BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;510throw new Failure("BindServer error: " + castedReply.reason);511} else {512throw new Failure("Wrong reply from BindServer: " + reply);513}514}515516/**517* Wait until the remotely launched process exits or crashes518* using request to <code>BindServer</code>.519*/520public int waitForRemoteProcess () {521522Object reply = sendRemoteCommand(new BindServer.WaitForDebugee(0));523if (reply instanceof BindServer.OK) {524BindServer.OK castedReply = (BindServer.OK)reply;525return (int)castedReply.info;526} else if (reply instanceof BindServer.RequestFailed) {527BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;528throw new Failure("BindServer error: " + castedReply.reason);529} else {530throw new Failure("Wrong reply from BindServer: " + reply);531}532}533534/**535* Close binder by closing all started threads.536*/537public void close() {538if (bindServerListener != null) {539bindServerListener.close();540}541closePipeServerSocket();542}543544/**545* Finalize binder by invoking <code>close()</code>.546*547* @throws Throwable if any throwable exception is thrown during finalization548*/549protected void finalize() throws Throwable {550close();551super.finalize();552}553554/**555* Finalize binder at exit by invoking <code>finalize()</code>.556*557* @throws Throwable if any throwable exception is thrown during finalization558*/559public void finalizeAtExit() throws Throwable {560finalize();561}562563/**564* Separate thread for listening connection from <code>BindServer</code>.565*/566private class BindServerListener extends Thread {567private SocketConnection connection = null;568private Log.Logger logger = null;569570/** List of received responses from <code>BindServer</code>. */571private LinkedList<BindServer.Response> replies = new LinkedList<BindServer.Response>();572573/**574* Make thread.575*/576public BindServerListener(Log.Logger logger) {577this.logger = logger;578}579580/**581* Establish connection to <code>BindServer</code>.582*/583public void connect(String taskID) throws IOException {584String host = argumentHandler.getDebugeeHost();585int port = argumentHandler.getBindPortNumber();586display("Connecting to BindServer: " + host + ":" + port);587connection = new SocketConnection(logger, "BindServer");588// connection.setPingTimeout(DebugeeBinder.PING_TIMEOUT);589connection.attach(host, port);590handshake(taskID);591}592593/**594* Receive OK(version) from BindServer and check received version number.595*/596private void handshake(String taskID) {597// receive OK(version)598trace(TRACE_LEVEL_ACTIONS, "Waiting for initial OK(version) from BindServer");599Object reply = connection.readObject();600trace(TRACE_LEVEL_ACTIONS, "Got initial OK(version) from BindServer: " + reply);601if (reply instanceof BindServer.RequestFailed) {602BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;603trace(TRACE_LEVEL_ACTIONS, "Reply is RequestFailed: throw Failure");604throw new Failure("BindServer error: " + castedReply.reason);605} else if (reply instanceof BindServer.OK) {606BindServer.OK castedReply = (BindServer.OK)reply;607trace(TRACE_LEVEL_ACTIONS, "Reply is OK: check BindServer version");608if (castedReply.info != BindServer.VERSION) {609throw new Failure("Wrong version of BindServer: " + castedReply.info610+ " (expected: " + BindServer.VERSION + ")");611}612display("Connected to BindServer: version " + castedReply.info);613} else {614trace(TRACE_LEVEL_ACTIONS, "Reply is unknown: throw Failure");615throw new Failure("Wrong reply from BindServer: " + reply);616}617618// send TaskID(id)619try {620trace(TRACE_LEVEL_ACTIONS, "Sending TaskID(id) to BindServer");621sendCommand(new BindServer.TaskID(taskID));622trace(TRACE_LEVEL_ACTIONS, "Sent TaskID(id) to BindServer");623} catch (IOException e) {624throw new Failure("Caught IOException while sending TaskID(id) to BindServer:\n\t"625+ e);626}627}628629/**630* Check if thread is connected to <code>BindServer</code>.631*/632public boolean isConnected() {633return (connection != null && connection.isConnected());634}635636/**637* Send a command to </code>BindServer</code>.638*/639public synchronized void sendCommand(Object command) throws IOException {640connection.writeObject(command);641}642643/**644* Receive response from <code>BindServer</code>.645*/646public Object getReply() {647synchronized (replies) {648while (replies.isEmpty()) {649if (!isConnected()) {650throw new Failure("No reply from BindServer: connection lost");651}652try {653replies.wait(TRY_DELAY);654} catch (InterruptedException e) {655e.printStackTrace(getOutStream());656throw new Failure("Thread interrupted while waiting for reply from BindServer:\n\t"657+ e);658}659}660Object reply = replies.removeFirst();661if (reply == null) {662throw new Failure("No reply from BindServer: connection lost");663}664return reply;665}666}667668/**669* Add response object to the list of received responses.670*/671private void addReply(BindServer.Response reply) {672synchronized (replies) {673replies.add(reply);674replies.notifyAll();675}676}677678/**679* Read packets from <code>BindServer<code> connection and680* notify waiting thread if response or IOPipe message received.681* Received lines of redirected streams are put into log.682*/683public void run() {684trace(TRACE_LEVEL_THREADS, "BindServerListener thread started");685try {686for (;;) {687Object reply = connection.readObject();688if (reply == null) {689break;690} else if (reply instanceof BindServer.Disconnect) {691reply = null;692trace(TRACE_LEVEL_ACTIONS, "Packet is Disconnect: close connection");693break;694} else if (reply instanceof BindServer.RedirectedStream) {695BindServer.RedirectedStream castedReply = (BindServer.RedirectedStream)reply;696trace(TRACE_LEVEL_ACTIONS, "Packet is RedirectedStream: put message into log");697log.println(castedReply.line);698} else if (reply instanceof BindServer.Response) {699BindServer.Response castedReply = (BindServer.Response)reply;700trace(TRACE_LEVEL_ACTIONS, "Packet is reply: notify all threads waiting for reply");701addReply(castedReply);702} else {703trace(TRACE_LEVEL_ACTIONS, "Packet is unknown: throw Failure");704throw new Failure("Wrong reply from BindServer: " + reply);705}706}707} catch (Exception e) {708e.printStackTrace(getOutStream());709complain("Caught exception while reading packets from BindServer:\n\t" + e);710} finally {711closeConnection();712addReply(null);713trace(TRACE_LEVEL_THREADS, "BindServerListener thread finished");714}715}716717/**718* Send Disconnect command to </code>BindServer</code>.719*/720public void disconnect() {721if (connection == null) return;722try {723sendCommand(new BindServer.Disconnect());724} catch (IOException e) {725display("Caught IOException while requesting disconnection with BindServer");726}727}728729/**730* Close socket connection.731*/732public void closeConnection() {733if (connection != null) {734connection.close();735}736}737738/**739* Wait for thread finished in the specified timeout or interrupt it.740*/741public void waitForThread(long millis) {742DebugeeBinder.waitForThread(this, millis, logger);743}744745/**746* Close this thread by waiting for it finishes or interrupt it747* and close socket connection.748*/749public void close() {750disconnect();751waitForThread(DebugeeBinder.THREAD_TIMEOUT);752closeConnection();753}754755} // BindServerListener756757} // DebugeeBinder758759760