1 /*
2  * Copyright (c) 1998, 2012, 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.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 /*
27  * This source code is provided to illustrate the usage of a given feature
28  * or technique and has been deliberately simplified. Additional steps
29  * required for a production-quality application, such as security checks,
30  * input validation and proper error handling, might not be present in
31  * this sample code.
32  */
33 
34 
35 package com.sun.tools.example.debug.tty;
36 
37 import com.sun.jdi.*;
38 import com.sun.jdi.connect.*;
39 import com.sun.jdi.request.EventRequestManager;
40 import com.sun.jdi.request.ThreadStartRequest;
41 import com.sun.jdi.request.ThreadDeathRequest;
42 
43 import java.util.*;
44 import java.util.regex.*;
45 import java.io.*;
46 
47 class VMConnection {
48 
49     private VirtualMachine vm;
50     private Process process = null;
51     private int outputCompleteCount = 0;
52 
53     private final Connector connector;
54     private final Map<String, com.sun.jdi.connect.Connector.Argument> connectorArgs;
55     private final int traceFlags;
56 
notifyOutputComplete()57     synchronized void notifyOutputComplete() {
58         outputCompleteCount++;
59         notifyAll();
60     }
61 
waitOutputComplete()62     synchronized void waitOutputComplete() {
63         // Wait for stderr and stdout
64         if (process != null) {
65             while (outputCompleteCount < 2) {
66                 try {wait();} catch (InterruptedException e) {}
67             }
68         }
69     }
70 
findConnector(String name)71     private Connector findConnector(String name) {
72         for (Connector connector :
73                  Bootstrap.virtualMachineManager().allConnectors()) {
74             if (connector.name().equals(name)) {
75                 return connector;
76             }
77         }
78         return null;
79     }
80 
parseConnectorArgs(Connector connector, String argString)81     private Map <String, com.sun.jdi.connect.Connector.Argument> parseConnectorArgs(Connector connector, String argString) {
82         Map<String, com.sun.jdi.connect.Connector.Argument> arguments = connector.defaultArguments();
83 
84         /*
85          * We are parsing strings of the form:
86          *    name1=value1,[name2=value2,...]
87          * However, the value1...valuen substrings may contain
88          * embedded comma(s), so make provision for quoting inside
89          * the value substrings. (Bug ID 4285874)
90          */
91         String regexPattern =
92             "(quote=[^,]+,)|" +           // special case for quote=.,
93             "(\\w+=)" +                   // name=
94             "(((\"[^\"]*\")|" +           //   ( "l , ue"
95             "('[^']*')|" +                //     'l , ue'
96             "([^,'\"]+))+,)";             //     v a l u e )+ ,
97         Pattern p = Pattern.compile(regexPattern);
98         Matcher m = p.matcher(argString);
99         while (m.find()) {
100             int startPosition = m.start();
101             int endPosition = m.end();
102             if (startPosition > 0) {
103                 /*
104                  * It is an error if parsing skips over any part of argString.
105                  */
106                 throw new IllegalArgumentException
107                     (MessageOutput.format("Illegal connector argument",
108                                           argString));
109             }
110 
111             String token = argString.substring(startPosition, endPosition);
112             int index = token.indexOf('=');
113             String name = token.substring(0, index);
114             String value = token.substring(index + 1,
115                                            token.length() - 1); // Remove comma delimiter
116 
117             /*
118              * for values enclosed in quotes (single and/or double quotes)
119              * strip off enclosing quote chars
120              * needed for quote enclosed delimited substrings
121              */
122             if (name.equals("options")) {
123                 StringBuilder sb = new StringBuilder();
124                 for (String s : splitStringAtNonEnclosedWhiteSpace(value)) {
125                     while (isEnclosed(s, "\"") || isEnclosed(s, "'")) {
126                         s = s.substring(1, s.length() - 1);
127                     }
128                     sb.append(s);
129                     sb.append(" ");
130                 }
131                 value = sb.toString();
132             }
133 
134             Connector.Argument argument = arguments.get(name);
135             if (argument == null) {
136                 throw new IllegalArgumentException
137                     (MessageOutput.format("Argument is not defined for connector:",
138                                           new Object [] {name, connector.name()}));
139             }
140             argument.setValue(value);
141 
142             argString = argString.substring(endPosition); // Remove what was just parsed...
143             m = p.matcher(argString);                     //    and parse again on what is left.
144         }
145         if ((! argString.equals(",")) && (argString.length() > 0)) {
146             /*
147              * It is an error if any part of argString is left over,
148              * unless it was empty to begin with.
149              */
150             throw new IllegalArgumentException
151                 (MessageOutput.format("Illegal connector argument", argString));
152         }
153         return arguments;
154     }
155 
isEnclosed(String value, String enclosingChar)156     private static boolean isEnclosed(String value, String enclosingChar) {
157         if (value.indexOf(enclosingChar) == 0) {
158             int lastIndex = value.lastIndexOf(enclosingChar);
159             if (lastIndex > 0 && lastIndex  == value.length() - 1) {
160                 return true;
161             }
162         }
163         return false;
164     }
165 
splitStringAtNonEnclosedWhiteSpace(String value)166     private static List<String> splitStringAtNonEnclosedWhiteSpace(String value) throws IllegalArgumentException {
167         List<String> al = new ArrayList<String>();
168         char[] arr;
169         int startPosition = 0;
170         int endPosition = 0;
171         final char SPACE = ' ';
172         final char DOUBLEQ = '"';
173         final char SINGLEQ = '\'';
174 
175         /*
176          * An "open" or "active" enclosing state is where
177          * the first valid start quote qualifier is found,
178          * and there is a search in progress for the
179          * relevant end matching quote
180          *
181          * enclosingTargetChar set to SPACE
182          * is used to signal a non open enclosing state
183          */
184         char enclosingTargetChar = SPACE;
185 
186         if (value == null) {
187             throw new IllegalArgumentException
188                 (MessageOutput.format("value string is null"));
189         }
190 
191         // split parameter string into individual chars
192         arr = value.toCharArray();
193 
194         for (int i = 0; i < arr.length; i++) {
195             switch (arr[i]) {
196                 case SPACE: {
197                     // do nothing for spaces
198                     // unless last in array
199                     if (isLastChar(arr, i)) {
200                         endPosition = i;
201                         // break for substring creation
202                         break;
203                     }
204                     continue;
205                 }
206                 case DOUBLEQ:
207                 case SINGLEQ: {
208                     if (enclosingTargetChar == arr[i]) {
209                         // potential match to close open enclosing
210                         if (isNextCharWhitespace(arr, i)) {
211                             // if peek next is whitespace
212                             // then enclosing is a valid substring
213                             endPosition = i;
214                             // reset enclosing target char
215                             enclosingTargetChar = SPACE;
216                             // break for substring creation
217                             break;
218                         }
219                     }
220                     if (enclosingTargetChar == SPACE) {
221                         // no open enclosing state
222                         // handle as normal char
223                         if (isPreviousCharWhitespace(arr, i)) {
224                             startPosition = i;
225                             // peek forward for end candidates
226                             if (value.indexOf(arr[i], i + 1) >= 0) {
227                                 // set open enclosing state by
228                                 // setting up the target char
229                                 enclosingTargetChar = arr[i];
230                             } else {
231                                 // no more target chars left to match
232                                 // end enclosing, handle as normal char
233                                 if (isNextCharWhitespace(arr, i)) {
234                                     endPosition = i;
235                                     // break for substring creation
236                                     break;
237                                 }
238                             }
239                         }
240                     }
241                     continue;
242                 }
243                 default: {
244                     // normal non-space, non-" and non-' chars
245                     if (enclosingTargetChar == SPACE) {
246                         // no open enclosing state
247                         if (isPreviousCharWhitespace(arr, i)) {
248                             // start of space delim substring
249                             startPosition = i;
250                         }
251                         if (isNextCharWhitespace(arr, i)) {
252                             // end of space delim substring
253                             endPosition = i;
254                             // break for substring creation
255                             break;
256                         }
257                     }
258                     continue;
259                 }
260             }
261 
262             // break's end up here
263             if (startPosition > endPosition) {
264                 throw new IllegalArgumentException
265                     (MessageOutput.format("Illegal option values"));
266             }
267 
268             // extract substring and add to List<String>
269             al.add(value.substring(startPosition, ++endPosition));
270 
271             // set new start position
272             i = startPosition = endPosition;
273 
274         } // for loop
275 
276         return al;
277     }
278 
isPreviousCharWhitespace(char[] arr, int curr_pos)279     static private boolean isPreviousCharWhitespace(char[] arr, int curr_pos) {
280         return isCharWhitespace(arr, curr_pos - 1);
281     }
282 
isNextCharWhitespace(char[] arr, int curr_pos)283     static private boolean isNextCharWhitespace(char[] arr, int curr_pos) {
284         return isCharWhitespace(arr, curr_pos + 1);
285     }
286 
isCharWhitespace(char[] arr, int pos)287     static private boolean isCharWhitespace(char[] arr, int pos) {
288         if (pos < 0 || pos >= arr.length) {
289             // outside arraybounds is considered an implicit space
290             return true;
291         }
292         if (arr[pos] == ' ') {
293             return true;
294         }
295         return false;
296     }
297 
isLastChar(char[] arr, int pos)298     static private boolean isLastChar(char[] arr, int pos) {
299         return (pos + 1 == arr.length);
300     }
301 
VMConnection(String connectSpec, int traceFlags)302     VMConnection(String connectSpec, int traceFlags) {
303         String nameString;
304         String argString;
305         int index = connectSpec.indexOf(':');
306         if (index == -1) {
307             nameString = connectSpec;
308             argString = "";
309         } else {
310             nameString = connectSpec.substring(0, index);
311             argString = connectSpec.substring(index + 1);
312         }
313 
314         connector = findConnector(nameString);
315         if (connector == null) {
316             throw new IllegalArgumentException
317                 (MessageOutput.format("No connector named:", nameString));
318         }
319 
320         connectorArgs = parseConnectorArgs(connector, argString);
321         this.traceFlags = traceFlags;
322     }
323 
open()324     synchronized VirtualMachine open() {
325         if (connector instanceof LaunchingConnector) {
326             vm = launchTarget();
327         } else if (connector instanceof AttachingConnector) {
328             vm = attachTarget();
329         } else if (connector instanceof ListeningConnector) {
330             vm = listenTarget();
331         } else {
332             throw new InternalError
333                 (MessageOutput.format("Invalid connect type"));
334         }
335         vm.setDebugTraceMode(traceFlags);
336         if (vm.canBeModified()){
337             setEventRequests(vm);
338             resolveEventRequests();
339         }
340         /*
341          * Now that the vm connection is open, fetch the debugee
342          * classpath and set up a default sourcepath.
343          * (Unless user supplied a sourcepath on the command line)
344          * (Bug ID 4186582)
345          */
346         if (Env.getSourcePath().length() == 0) {
347             if (vm instanceof PathSearchingVirtualMachine) {
348                 PathSearchingVirtualMachine psvm =
349                     (PathSearchingVirtualMachine) vm;
350                 Env.setSourcePath(psvm.classPath());
351             } else {
352                 Env.setSourcePath(".");
353             }
354         }
355 
356         return vm;
357     }
358 
setConnectorArg(String name, String value)359     boolean setConnectorArg(String name, String value) {
360         /*
361          * Too late if the connection already made
362          */
363         if (vm != null) {
364             return false;
365         }
366 
367         Connector.Argument argument = connectorArgs.get(name);
368         if (argument == null) {
369             return false;
370         }
371         argument.setValue(value);
372         return true;
373     }
374 
connectorArg(String name)375     String connectorArg(String name) {
376         Connector.Argument argument = connectorArgs.get(name);
377         if (argument == null) {
378             return "";
379         }
380         return argument.value();
381     }
382 
vm()383     public synchronized VirtualMachine vm() {
384         if (vm == null) {
385             throw new VMNotConnectedException();
386         } else {
387             return vm;
388         }
389     }
390 
isOpen()391     boolean isOpen() {
392         return (vm != null);
393     }
394 
isLaunch()395     boolean isLaunch() {
396         return (connector instanceof LaunchingConnector);
397     }
398 
disposeVM()399     public void disposeVM() {
400         try {
401             if (vm != null) {
402                 vm.dispose();
403                 vm = null;
404             }
405         } finally {
406             if (process != null) {
407                 process.destroy();
408                 process = null;
409             }
410             waitOutputComplete();
411         }
412     }
413 
setEventRequests(VirtualMachine vm)414     private void setEventRequests(VirtualMachine vm) {
415         EventRequestManager erm = vm.eventRequestManager();
416 
417         // Normally, we want all uncaught exceptions.  We request them
418         // via the same mechanism as Commands.commandCatchException()
419         // so the user can ignore them later if they are not
420         // interested.
421         // FIXME: this works but generates spurious messages on stdout
422         //        during startup:
423         //          Set uncaught java.lang.Throwable
424         //          Set deferred uncaught java.lang.Throwable
425         Commands evaluator = new Commands();
426         evaluator.commandCatchException
427             (new StringTokenizer("uncaught java.lang.Throwable"));
428 
429         ThreadStartRequest tsr = erm.createThreadStartRequest();
430         tsr.enable();
431         ThreadDeathRequest tdr = erm.createThreadDeathRequest();
432         tdr.enable();
433     }
434 
resolveEventRequests()435     private void resolveEventRequests() {
436         Env.specList.resolveAll();
437     }
438 
dumpStream(InputStream stream)439     private void dumpStream(InputStream stream) throws IOException {
440         BufferedReader in =
441             new BufferedReader(new InputStreamReader(stream));
442         int i;
443         try {
444             while ((i = in.read()) != -1) {
445                    MessageOutput.printDirect((char)i);// Special case: use
446                                                       //   printDirect()
447             }
448         } catch (IOException ex) {
449             String s = ex.getMessage();
450             if (!s.startsWith("Bad file number")) {
451                   throw ex;
452             }
453             // else we got a Bad file number IOException which just means
454             // that the debuggee has gone away.  We'll just treat it the
455             // same as if we got an EOF.
456         }
457     }
458 
459     /**
460      *  Create a Thread that will retrieve and display any output.
461      *  Needs to be high priority, else debugger may exit before
462      *  it can be displayed.
463      */
displayRemoteOutput(final InputStream stream)464     private void displayRemoteOutput(final InputStream stream) {
465         Thread thr = new Thread("output reader") {
466             @Override
467             public void run() {
468                 try {
469                     dumpStream(stream);
470                 } catch (IOException ex) {
471                     MessageOutput.fatalError("Failed reading output");
472                 } finally {
473                     notifyOutputComplete();
474                 }
475             }
476         };
477         thr.setPriority(Thread.MAX_PRIORITY-1);
478         thr.start();
479     }
480 
dumpFailedLaunchInfo(Process process)481     private void dumpFailedLaunchInfo(Process process) {
482         try {
483             dumpStream(process.getErrorStream());
484             dumpStream(process.getInputStream());
485         } catch (IOException e) {
486             MessageOutput.println("Unable to display process output:",
487                                   e.getMessage());
488         }
489     }
490 
491     /* launch child target vm */
launchTarget()492     private VirtualMachine launchTarget() {
493         LaunchingConnector launcher = (LaunchingConnector)connector;
494         try {
495             VirtualMachine vm = launcher.launch(connectorArgs);
496             process = vm.process();
497             displayRemoteOutput(process.getErrorStream());
498             displayRemoteOutput(process.getInputStream());
499             return vm;
500         } catch (IOException ioe) {
501             ioe.printStackTrace();
502             MessageOutput.fatalError("Unable to launch target VM.");
503         } catch (IllegalConnectorArgumentsException icae) {
504             icae.printStackTrace();
505             MessageOutput.fatalError("Internal debugger error.");
506         } catch (VMStartException vmse) {
507             MessageOutput.println("vmstartexception", vmse.getMessage());
508             MessageOutput.println();
509             dumpFailedLaunchInfo(vmse.process());
510             MessageOutput.fatalError("Target VM failed to initialize.");
511         }
512         return null; // Shuts up the compiler
513     }
514 
515     /* attach to running target vm */
attachTarget()516     private VirtualMachine attachTarget() {
517         AttachingConnector attacher = (AttachingConnector)connector;
518         try {
519             return attacher.attach(connectorArgs);
520         } catch (IOException ioe) {
521             ioe.printStackTrace();
522             MessageOutput.fatalError("Unable to attach to target VM.");
523         } catch (IllegalConnectorArgumentsException icae) {
524             icae.printStackTrace();
525             MessageOutput.fatalError("Internal debugger error.");
526         }
527         return null; // Shuts up the compiler
528     }
529 
530     /* listen for connection from target vm */
listenTarget()531     private VirtualMachine listenTarget() {
532         ListeningConnector listener = (ListeningConnector)connector;
533         try {
534             String retAddress = listener.startListening(connectorArgs);
535             MessageOutput.println("Listening at address:", retAddress);
536             vm = listener.accept(connectorArgs);
537             listener.stopListening(connectorArgs);
538             return vm;
539         } catch (IOException ioe) {
540             ioe.printStackTrace();
541             MessageOutput.fatalError("Unable to attach to target VM.");
542         } catch (IllegalConnectorArgumentsException icae) {
543             icae.printStackTrace();
544             MessageOutput.fatalError("Internal debugger error.");
545         }
546         return null; // Shuts up the compiler
547     }
548 }
549