1 /*
2  * Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 package nsk.share.jpda;
25 
26 import nsk.share.*;
27 
28 import java.io.*;
29 import java.net.*;
30 import java.util.*;
31 
32 /**
33  * This class provides debugger with ability to launch
34  * debuggee VM and to make connection to it using JDI connector or
35  * JDWP transport.
36  * <p>
37  * The present version of <code>Binder</code> allows
38  * to launch debuggee VM either on local machine (<i>local</i> launch mode),
39  * or on remote host using <code>BindServer</code> utility
40  * (<i>remote</i> launch mode). Also there is an ability to launch
41  * debuggee VM manually as a separate process on local or remote machine
42  * (<i>manual</i> launch mode), which is usefull for debugging.
43  * All these launching modes are specified by command line option
44  * <code>-debugee.launch</code> recognized by <code>DebugeeArgumentHandler</code>.
45  * <p>
46  * <code>Binder</code> also makes it possible to establish TCP/IP
47  * connection between debugger and debuggee throw <code>IOPipe</code>
48  * object. This connection allows debugger to communicate with debuggee
49  * by exchanging with synchronization messages and data.
50  * <p>
51  * To launch debuggee VM and bind to it use <code>bindToDebugee()</code>
52  * method. This method construct mirror of debugee VM, represented by
53  * object of <code>DebugeeProcess</code> class or derived. This mirror object
54  * allows to control debuggee VM.
55  * <p>
56  * See also <code>nsk.share.jdi.Binder</code> and <code>nsk.share.jdwp.Binder</code>
57  * classes which provide launching and binding to debuggee VM using specific
58  * JDI and JDWP features.
59  *
60  * @see DebugeeArgumentHandler
61  * @see DebugeeProcess
62  * @see IOPipe
63  * @see BindServer
64  *
65  * @see nsk.share.jdi.Binder
66  * @see nsk.share.jdwp.Binder
67  */
68 public class DebugeeBinder extends Log.Logger implements Finalizable {
69 
70     private static final boolean IS_WINDOWS = System.getProperty("os.name")
71                                                     .toLowerCase()
72                                                     .startsWith("win");
73 
74     public static int TRY_DELAY = 1000;                     // milliseconds
75 
76     public static int CONNECT_TIMEOUT = 1 * 60 * 1000;      // milliseconds
77     public static int CONNECT_TRY_DELAY = 2 * 1000;         // milliseconds
78     public static int CONNECT_TRIES = CONNECT_TIMEOUT / CONNECT_TRY_DELAY;
79 
80     public static int THREAD_TIMEOUT = 2 * CONNECT_TRY_DELAY;  // milliseconds
81     public static int PING_TIMEOUT = 30 * 1000;  // milliseconds
82 
83     public static int SOCKET_TIMEOUT = 2 * 1000;  // milliseconds
84     public static int SOCKET_LINGER = 1;   // milliseconds
85 
86     private static int TRACE_LEVEL_PACKETS = 10;
87     private static int TRACE_LEVEL_THREADS = 20;
88     private static int TRACE_LEVEL_ACTIONS = 30;
89     private static int TRACE_LEVEL_SOCKETS = 40;
90     private static int TRACE_LEVEL_IO = 50;
91 
92     /**
93      * Default message prefix for <code>Binder</code> object.
94      */
95     public static final String LOG_PREFIX = "binder> ";
96 
97     private DebugeeArgumentHandler argumentHandler = null;
98 
99     /**
100      * Get version string.
101      */
getVersion()102     public static String getVersion () {
103         return "@(#)Binder.java %I% %E%";
104     }
105 
106     // -------------------------------------------------- //
107 
108     private BindServerListener bindServerListener = null;
109     private ServerSocket pipeServerSocket = null;
110 
111     // -------------------------------------------------- //
112 
113     /**
114      * Incarnate new Binder obeying the given
115      * <code>argumentHandler</code>; and assign the given
116      * <code>log</code>.
117      */
DebugeeBinder(DebugeeArgumentHandler argumentHandler, Log log)118     public DebugeeBinder (DebugeeArgumentHandler argumentHandler, Log log) {
119         super(log, LOG_PREFIX);
120         this.argumentHandler = argumentHandler;
121         Finalizer finalizer = new Finalizer(this);
122         finalizer.activate();
123     }
124 
125     /**
126      * Get argument handler of this binder object.
127      */
getArgumentHandler()128     DebugeeArgumentHandler getArgumentHandler() {
129         return argumentHandler;
130     }
131 
132     // -------------------------------------------------- //
133 
134     /**
135      * Wait for given thread finished for THREAD_TIMEOUT timeout and
136      * interrupt this thread if not finished.
137      *
138      * @param thr thread to wait for
139      * @param logger to write log messages to
140      */
waitForThread(Thread thr, Log.Logger logger)141     public static void waitForThread(Thread thr, Log.Logger logger) {
142         waitForThread(thr, THREAD_TIMEOUT, logger);
143     }
144 
145     /**
146      * Wait for given thread finished for specified timeout and
147      * interrupt this thread if not finished.
148      *
149      * @param thr thread to wait for
150      * @param millisecs timeout in milliseconds
151      * @param logger to write log messages to
152      */
waitForThread(Thread thr, long millisecs, Log.Logger logger)153     public static void waitForThread(Thread thr, long millisecs, Log.Logger logger) {
154         if (thr != null) {
155             if (thr.isAlive()) {
156                 try {
157                     logger.trace(TRACE_LEVEL_THREADS, "Waiting for thread: " + thr.getName());
158                     thr.join(millisecs);
159                 } catch (InterruptedException e) {
160                     e.printStackTrace(logger.getOutStream());
161                     throw new Failure ("Thread interrupted while waiting for another thread:\n\t"
162                                          + e);
163                 } finally {
164                     if (thr.isAlive()) {
165                         logger.trace(TRACE_LEVEL_THREADS, "Interrupting not finished thread: " + thr);
166                         thr.interrupt();
167                     }
168                 }
169             }
170         }
171     }
172 
173 
174     /**
175      * Make preperation for IOPipe connection before starting debugee VM process.
176      * May change options in the passed <code>argumentHandler</code>.
177      */
prepareForPipeConnection(DebugeeArgumentHandler argumentHandler)178     public void prepareForPipeConnection(DebugeeArgumentHandler argumentHandler) {
179         if (argumentHandler.isTransportAddressDynamic()) {
180             try {
181                 pipeServerSocket = new ServerSocket();
182                 pipeServerSocket.setReuseAddress(false);
183                 pipeServerSocket.bind(null);
184 
185             } catch (IOException e) {
186                 e.printStackTrace(getOutStream());
187                 throw new Failure("Caught IOException while binding for IOPipe connection: \n\t"
188                                 + e);
189             }
190 
191             int port = pipeServerSocket.getLocalPort();
192             argumentHandler.setPipePortNumber(port);
193         }
194     }
195 
196     /**
197      * Return already bound ServerSocket for IOPipe connection or null.
198      */
getPipeServerSocket()199     protected ServerSocket getPipeServerSocket() {
200         return pipeServerSocket;
201     }
202 
203     /**
204      * Close ServerSocket used for IOPipeConnection if any.
205      */
closePipeServerSocket()206     private void closePipeServerSocket() {
207         if (pipeServerSocket != null) {
208             try {
209                 pipeServerSocket.close();
210             } catch (IOException e) {
211                 println("# WARNING: Caught IOException while closing ServerSocket used for IOPipe connection: \n\t"
212                         + e);
213             }
214         }
215     }
216 
217     // -------------------------------------------------- //
218 
219     /**
220      * Make environment for launching JVM process.
221      */
makeProcessEnvironment()222     public String[] makeProcessEnvironment() {
223 /*
224         String env = new String[0];
225         return env;
226  */
227         return null;
228     }
229 
230     /**
231      * Launch process by the specified command line.
232      *
233      * @throws IOException if I/O error occured while launching process
234      */
launchProcess(String cmdLine)235     public Process launchProcess(String cmdLine) throws IOException {
236         String env[] = makeProcessEnvironment();
237         return Runtime.getRuntime().exec(cmdLine, env);
238     }
239 
240     /**
241      * Launch process by the arguments array.
242      *
243      * @throws IOException if I/O error occured while launching process
244      */
launchProcess(String[] args)245     public Process launchProcess(String[] args) throws IOException {
246         String env[] = makeProcessEnvironment();
247         return Runtime.getRuntime().exec(args, env);
248     }
249 
250     /**
251      * Make string representation of debuggee VM transport address according
252      * to current command line options.
253      */
makeTransportAddress()254     public String makeTransportAddress() {
255         String address = null;
256         if (argumentHandler.isSocketTransport()) {
257             if (argumentHandler.isListeningConnector()) {
258                 address = argumentHandler.getTestHost()
259                         + ":" + argumentHandler.getTransportPort();
260             } else {
261                 address = argumentHandler.getTransportPort();
262             }
263         } else if (argumentHandler.isShmemTransport() ) {
264             address = argumentHandler.getTransportSharedName();
265         } else {
266             throw new TestBug("Undefined transport type: "
267                         + argumentHandler.getTransportType());
268         }
269         return address;
270     }
271 
272     /**
273      * Make command line to launch debugee VM as a string using given quote symbol,
274      * using specified <code>transportAddress</code> for JDWP connection.
275      */
makeCommandLineString(String classToExecute, String transportAddress, String quote)276     public String makeCommandLineString(String classToExecute, String transportAddress, String quote) {
277         String[] args = makeCommandLineArgs(classToExecute, transportAddress);
278         return ArgumentParser.joinArguments(args, quote);
279     }
280 
281     /**
282      * Make command line to launch debugee VM as a string using given quote symbol.
283      */
makeCommandLineString(String classToExecute, String quote)284     public String makeCommandLineString(String classToExecute, String quote) {
285         return makeCommandLineString(classToExecute, makeTransportAddress(), quote);
286     }
287 
288     /**
289      * Make command line to launch debugee VM as a string using default quote symbol,
290      * using specified <code>transportAddress</code> for JDWP connection.
291      */
292 /*
293     public String makeCommandLineString(String classToExecute, String transportAddress) {
294         return makeCommandLineString(classToExecute, transportAddress, "\"");
295     }
296  */
297 
298     /**
299      * Make command line to launch debugee VM as a string using default quote symbol.
300      */
301 /*
302     public String makeCommandLineString(String classToExecute) {
303         return makeCommandLineString(classToExecute, makeTransportAddress());
304     }
305  */
306 
307     /**
308      * Make command line to launch debugee VM as an array of arguments,
309      * using specified <code>transportAddress</code> for JDWP connection.
310      */
makeCommandLineArgs(String classToExecute, String transportAddress)311     public String[] makeCommandLineArgs(String classToExecute, String transportAddress) {
312         Vector<String> args = new Vector<String>();
313 
314         args.add(argumentHandler.getLaunchExecPath());
315 
316         String javaOpts = argumentHandler.getLaunchOptions();
317         if (javaOpts != null && javaOpts.length() > 0) {
318             StringTokenizer st = new StringTokenizer(javaOpts);
319 
320             while (st.hasMoreTokens()) {
321                 args.add(st.nextToken());
322             }
323         }
324 
325 /*
326         String classPath = System.getProperty("java.class.path");
327         args.add("-classpath")
328         args.add(classPath);
329  */
330 
331         args.add("-Xdebug");
332 
333         String server;
334         if (argumentHandler.isAttachingConnector()) {
335             server = "y";
336         } else {
337             server = "n";
338         }
339 
340         String jdwpArgs = "-Xrunjdwp:"
341                         + "server=" + server
342                         + ",transport=" + argumentHandler.getTransportName()
343                         + ",address=" + transportAddress;
344 
345         if (! argumentHandler.isDefaultJVMDIStrictMode()) {
346             if (argumentHandler.isJVMDIStrictMode())
347                 jdwpArgs += ",strict=y";
348             else
349                 jdwpArgs += ",strict=n";
350         }
351 
352         args.add(jdwpArgs);
353 
354         if (classToExecute != null) {
355             StringTokenizer st = new StringTokenizer(classToExecute);
356 
357             while (st.hasMoreTokens()) {
358                 args.add(st.nextToken());
359             }
360         }
361 
362         String[] rawArgs = argumentHandler.getRawArguments();
363         for (int i = 0; i < rawArgs.length; i++) {
364             String rawArg = rawArgs[i];
365             // " has to be escaped on windows
366             if (IS_WINDOWS) {
367                 rawArg = rawArg.replace("\"", "\\\"");
368             }
369             args.add(rawArg);
370         }
371 
372         String[] argsArray = new String[args.size()];
373         for (int i = 0; i < args.size(); i++) {
374             argsArray[i] = (String) args.elementAt(i);
375         }
376 
377         return argsArray;
378     }
379 
380     /**
381      * Make command line to launch debugee VM as an array of arguments.
382      */
makeCommandLineArgs(String classToExecute)383     public String[] makeCommandLineArgs(String classToExecute) {
384         return makeCommandLineArgs(classToExecute, makeTransportAddress());
385     }
386 
387     /**
388      * Make connection to remote BindServer and start BindServerListener thread.
389      *
390      * @throws IOException if I/O error occured while connecting
391      */
connectToBindServer(String taskID)392     public void connectToBindServer(String taskID) {
393         if (bindServerListener != null) {
394             throw new Failure("Connection to BindServer already exists");
395         }
396         try {
397             bindServerListener = new BindServerListener(this);
398             bindServerListener.setDaemon(true);
399             bindServerListener.connect(taskID);
400             bindServerListener.start();
401         } catch (IOException e) {
402             e.printStackTrace(getOutStream());
403             throw new Failure("Caught exception while connecting to BindServer:\n\t" + e);
404         }
405     }
406 
407     /**
408      * Split string into list of substrings using specified separator.
409      */
splitString(String givenString, String separator)410     private static String[] splitString(String givenString, String separator) {
411         Vector<String> tmpList = new Vector<String>();
412         StringTokenizer tokenizer = new StringTokenizer(givenString, separator);
413         while(tokenizer.hasMoreTokens()) {
414             tmpList.add(tokenizer.nextToken());
415         }
416         String[] list = new String[tmpList.size()];
417         for (int i = 0; i < tmpList.size(); i++) {
418             list[i] = tmpList.elementAt(i);
419         }
420         return list;
421     }
422 
423     /**
424      * Send command to remote <code>BindServer</code> and receive reply.
425      *
426      * @throws IOException if I/O error occured while launching process
427      */
sendRemoteCommand(Object command)428     public synchronized Object sendRemoteCommand(Object command) {
429         try {
430             bindServerListener.sendCommand(command);
431             Object reply = bindServerListener.getReply();
432             return reply;
433         } catch (IOException e) {
434             e.printStackTrace(log.getOutStream());
435             throw new Failure("Unexpected exception while sending command to BindServer:\n\t"
436                             + e);
437         }
438     }
439 
440     /**
441      * Launch remote process using request to <code>BindServer</code>.
442      *
443      * @throws IOException if I/O error occured
444      */
launchRemoteProcess(String[] args)445     public void launchRemoteProcess(String[] args) throws IOException {
446         String pathSeparator = System.getProperty("path.separator");
447         BindServer.LaunchDebugee command =
448             new BindServer.LaunchDebugee(args,
449                     System.getProperty("file.separator"),
450                     System.getProperty("user.dir"),
451                     splitString(System.getProperty("java.library.path"), pathSeparator),
452                     splitString(System.getProperty("java.class.path"), pathSeparator),
453                     splitString(System.getProperty("java.library.path"), pathSeparator));
454 
455         Object reply = sendRemoteCommand(command);
456         if (reply instanceof BindServer.OK) {
457             // do nothing
458         } else if (reply instanceof BindServer.RequestFailed) {
459             BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;
460             throw new Failure("BindServer error: " + castedReply.reason);
461         } else {
462             throw new Failure("Wrong reply from BindServer: " + reply);
463         }
464     }
465 
466     /**
467      * Return exit status of the remotely launched process
468      * using request to <code>BindServer</code>.
469      */
getRemoteProcessStatus()470     public int getRemoteProcessStatus () {
471         Object reply = sendRemoteCommand(new BindServer.DebugeeExitCode());
472         if (reply instanceof BindServer.OK) {
473             BindServer.OK castedReply = (BindServer.OK)reply;
474             return (int)castedReply.info;
475         } else if (reply instanceof BindServer.CaughtException) {
476             BindServer.CaughtException castedReply = (BindServer.CaughtException)reply;
477             throw new IllegalThreadStateException(castedReply.reason);
478         } else if (reply instanceof BindServer.RequestFailed) {
479             BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;
480             throw new Failure("BindServer error: " + castedReply.reason);
481         } else {
482             throw new Failure("Wrong reply from BindServer: " + reply);
483         }
484     }
485 
486     /**
487      * Check whether the remotely launched process has been terminated
488      * using request to <code>BindServer</code>.
489      */
isRemoteProcessTerminated()490     public boolean isRemoteProcessTerminated () {
491         try {
492             int value = getRemoteProcessStatus();
493             return true;
494         } catch (IllegalThreadStateException e) {
495             return false;
496         }
497     }
498 
499     // ---------------------------------------------- //
500 
501     /**
502      * Kill the remotely launched process
503      * using request to <code>BindServer</code>.
504      */
killRemoteProcess()505     public void killRemoteProcess () {
506         Object reply = sendRemoteCommand(new BindServer.KillDebugee());
507         if (reply instanceof BindServer.OK) {
508             return;
509         } else if (reply instanceof BindServer.RequestFailed) {
510             BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;
511             throw new Failure("BindServer error: " + castedReply.reason);
512         } else {
513             throw new Failure("Wrong reply from BindServer: " + reply);
514         }
515     }
516 
517     /**
518      * Wait until the remotely launched process exits or crashes
519      * using request to <code>BindServer</code>.
520      */
waitForRemoteProcess()521     public int waitForRemoteProcess () {
522 
523         Object reply = sendRemoteCommand(new BindServer.WaitForDebugee(0));
524         if (reply instanceof BindServer.OK) {
525             BindServer.OK castedReply = (BindServer.OK)reply;
526             return (int)castedReply.info;
527         } else if (reply instanceof BindServer.RequestFailed) {
528             BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;
529             throw new Failure("BindServer error: " + castedReply.reason);
530         } else {
531             throw new Failure("Wrong reply from BindServer: " + reply);
532         }
533     }
534 
535     /**
536      * Close binder by closing all started threads.
537      */
close()538     public void close() {
539         if (bindServerListener != null) {
540             bindServerListener.close();
541         }
542         closePipeServerSocket();
543     }
544 
545     /**
546      * Finalize binder by invoking <code>close()</code>.
547      *
548      * @throws Throwable if any throwable exception is thrown during finalization
549      */
finalize()550     protected void finalize() throws Throwable {
551         close();
552         super.finalize();
553     }
554 
555     /**
556      * Finalize binder at exit by invoking <code>finalize()</code>.
557      *
558      * @throws Throwable if any throwable exception is thrown during finalization
559      */
finalizeAtExit()560     public void finalizeAtExit() throws Throwable {
561         finalize();
562     }
563 
564     /**
565      * Separate thread for listening connection from <code>BindServer</code>.
566      */
567     private class BindServerListener extends Thread {
568         private SocketConnection connection = null;
569         private Log.Logger logger = null;
570 
571         /** List of received responses from <code>BindServer</code>. */
572         private LinkedList<BindServer.Response> replies = new LinkedList<BindServer.Response>();
573 
574         /**
575          * Make thread.
576          */
BindServerListener(Log.Logger logger)577         public BindServerListener(Log.Logger logger) {
578             this.logger = logger;
579         }
580 
581         /**
582          * Establish connection to <code>BindServer</code>.
583          */
connect(String taskID)584         public void connect(String taskID) throws IOException {
585             String host = argumentHandler.getDebugeeHost();
586             int port = argumentHandler.getBindPortNumber();
587             display("Connecting to BindServer: " + host + ":" + port);
588             connection = new SocketConnection(logger, "BindServer");
589 //            connection.setPingTimeout(DebugeeBinder.PING_TIMEOUT);
590             connection.attach(host, port);
591             handshake(taskID);
592         }
593 
594         /**
595          * Receive OK(version) from BindServer and check received version number.
596          */
handshake(String taskID)597         private void handshake(String taskID) {
598             // receive OK(version)
599             trace(TRACE_LEVEL_ACTIONS, "Waiting for initial OK(version) from BindServer");
600             Object reply = connection.readObject();
601             trace(TRACE_LEVEL_ACTIONS, "Got initial OK(version) from BindServer: " + reply);
602             if (reply instanceof BindServer.RequestFailed) {
603                 BindServer.RequestFailed castedReply = (BindServer.RequestFailed)reply;
604                 trace(TRACE_LEVEL_ACTIONS, "Reply is RequestFailed: throw Failure");
605                 throw new Failure("BindServer error: " + castedReply.reason);
606             } else if (reply instanceof BindServer.OK) {
607                 BindServer.OK castedReply = (BindServer.OK)reply;
608                 trace(TRACE_LEVEL_ACTIONS, "Reply is OK: check BindServer version");
609                 if (castedReply.info != BindServer.VERSION) {
610                     throw new Failure("Wrong version of BindServer: " + castedReply.info
611                                     + " (expected: " + BindServer.VERSION + ")");
612                 }
613                 display("Connected to BindServer: version " + castedReply.info);
614             } else {
615                 trace(TRACE_LEVEL_ACTIONS, "Reply is unknown: throw Failure");
616                 throw new Failure("Wrong reply from BindServer: " + reply);
617             }
618 
619             // send TaskID(id)
620             try {
621                 trace(TRACE_LEVEL_ACTIONS, "Sending TaskID(id) to BindServer");
622                 sendCommand(new BindServer.TaskID(taskID));
623                 trace(TRACE_LEVEL_ACTIONS, "Sent TaskID(id) to BindServer");
624             } catch (IOException e) {
625                 throw new Failure("Caught IOException while sending TaskID(id) to BindServer:\n\t"
626                                 + e);
627             }
628         }
629 
630         /**
631          * Check if thread is connected to <code>BindServer</code>.
632          */
isConnected()633         public boolean isConnected() {
634             return (connection != null && connection.isConnected());
635         }
636 
637         /**
638          * Send a command to </code>BindServer</code>.
639          */
sendCommand(Object command)640         public synchronized void sendCommand(Object command) throws IOException {
641             connection.writeObject(command);
642         }
643 
644         /**
645          * Receive response from <code>BindServer</code>.
646          */
getReply()647         public Object getReply() {
648             synchronized (replies) {
649                 while (replies.isEmpty()) {
650                     if (!isConnected()) {
651                         throw new Failure("No reply from BindServer: connection lost");
652                     }
653                     try {
654                         replies.wait(TRY_DELAY);
655                     } catch (InterruptedException e) {
656                         e.printStackTrace(getOutStream());
657                         throw new Failure("Thread interrupted while waiting for reply from BindServer:\n\t"
658                                         + e);
659                     }
660                 }
661                 Object reply = replies.removeFirst();
662                 if (reply == null) {
663                     throw new Failure("No reply from BindServer: connection lost");
664                 }
665                 return reply;
666             }
667         }
668 
669         /**
670          * Add response object to the list of received responses.
671          */
addReply(BindServer.Response reply)672         private void addReply(BindServer.Response reply) {
673             synchronized (replies) {
674                 replies.add(reply);
675                 replies.notifyAll();
676             }
677         }
678 
679         /**
680          * Read packets from <code>BindServer<code> connection and
681          * notify waiting thread if response or IOPipe message received.
682          * Received lines of redirected streams are put into log.
683          */
run()684         public void run() {
685             trace(TRACE_LEVEL_THREADS, "BindServerListener thread started");
686             try {
687                 for (;;) {
688                     Object reply = connection.readObject();
689                     if (reply == null) {
690                         break;
691                     } else if (reply instanceof BindServer.Disconnect) {
692                         reply = null;
693                         trace(TRACE_LEVEL_ACTIONS, "Packet is Disconnect: close connection");
694                         break;
695                     } else if (reply instanceof BindServer.RedirectedStream) {
696                         BindServer.RedirectedStream castedReply = (BindServer.RedirectedStream)reply;
697                         trace(TRACE_LEVEL_ACTIONS, "Packet is RedirectedStream: put message into log");
698                         log.println(castedReply.line);
699                     } else if (reply instanceof BindServer.Response) {
700                         BindServer.Response castedReply = (BindServer.Response)reply;
701                         trace(TRACE_LEVEL_ACTIONS, "Packet is reply: notify all threads waiting for reply");
702                         addReply(castedReply);
703                     } else {
704                         trace(TRACE_LEVEL_ACTIONS, "Packet is unknown: throw Failure");
705                         throw new Failure("Wrong reply from BindServer: " + reply);
706                     }
707                 }
708             } catch (Exception e) {
709                 e.printStackTrace(getOutStream());
710                 complain("Caught exception while reading packets from BindServer:\n\t" + e);
711             } finally {
712                 closeConnection();
713                 addReply(null);
714                 trace(TRACE_LEVEL_THREADS, "BindServerListener thread finished");
715             }
716         }
717 
718         /**
719          * Send Disconnect command to </code>BindServer</code>.
720          */
disconnect()721         public void disconnect() {
722             if (connection == null) return;
723             try {
724                 sendCommand(new BindServer.Disconnect());
725             } catch (IOException e) {
726                 display("Caught IOException while requesting disconnection with BindServer");
727             }
728         }
729 
730         /**
731          * Close socket connection.
732          */
closeConnection()733         public void closeConnection() {
734             if (connection != null) {
735                 connection.close();
736             }
737         }
738 
739         /**
740          * Wait for thread finished in the specified timeout or interrupt it.
741          */
waitForThread(long millis)742         public void waitForThread(long millis) {
743             DebugeeBinder.waitForThread(this, millis, logger);
744         }
745 
746         /**
747          * Close this thread by waiting for it finishes or interrupt it
748          * and close socket connection.
749          */
close()750         public void close() {
751             disconnect();
752             waitForThread(DebugeeBinder.THREAD_TIMEOUT);
753             closeConnection();
754         }
755 
756     } // BindServerListener
757 
758 } // DebugeeBinder
759