1 /*
   2  * Copyright (c) 1998, 2008, 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 
  57     synchronized void notifyOutputComplete() {
  58         outputCompleteCount++;
  59         notifyAll();
  60     }
  61 
  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 
  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 
  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              *  unquote values for options (single and/or double quotes)
 118              *  needed for quote enclosed delimited substrings
 119              */
 120             if (name.equals("options")) {
 121                 StringBuilder sb = new StringBuilder();
 122                 for (String s : value.split("\\s+")) {
 123                     while (isEnclosed(s, "\"") || isEnclosed(s, "'")) {
 124                         s = s.substring(1, s.length() - 1);
 125                     }
 126                     sb.append(s);
 127                     sb.append(" ");
 128                 }
 129                 value = sb.toString();
 130             }
 131 
 132             Connector.Argument argument = arguments.get(name);
 133             if (argument == null) {
 134                 throw new IllegalArgumentException
 135                     (MessageOutput.format("Argument is not defined for connector:",
 136                                           new Object [] {name, connector.name()}));
 137             }
 138             argument.setValue(value);
 139 
 140             argString = argString.substring(endPosition); // Remove what was just parsed...
 141             m = p.matcher(argString);                     //    and parse again on what is left.
 142         }
 143         if ((! argString.equals(",")) && (argString.length() > 0)) {
 144             /*
 145              * It is an error if any part of argString is left over,
 146              * unless it was empty to begin with.
 147              */
 148             throw new IllegalArgumentException
 149                 (MessageOutput.format("Illegal connector argument", argString));
 150         }
 151         return arguments;
 152     }
 153 
 154     private static boolean isEnclosed(String value, String enclosingChar) {
 155         if (value.indexOf(enclosingChar) == 0) {
 156             int lastIndex = value.lastIndexOf(enclosingChar);
 157             if (lastIndex > 0 && lastIndex  == value.length() - 1) {
 158                 return true;
 159             }
 160         }
 161         return false;
 162     }
 163 
 164     VMConnection(String connectSpec, int traceFlags) {
 165         String nameString;
 166         String argString;
 167         int index = connectSpec.indexOf(':');
 168         if (index == -1) {
 169             nameString = connectSpec;
 170             argString = "";
 171         } else {
 172             nameString = connectSpec.substring(0, index);
 173             argString = connectSpec.substring(index + 1);
 174         }
 175 
 176         connector = findConnector(nameString);
 177         if (connector == null) {
 178             throw new IllegalArgumentException
 179                 (MessageOutput.format("No connector named:", nameString));
 180         }
 181 
 182         connectorArgs = parseConnectorArgs(connector, argString);
 183         this.traceFlags = traceFlags;
 184     }
 185 
 186     synchronized VirtualMachine open() {
 187         if (connector instanceof LaunchingConnector) {
 188             vm = launchTarget();
 189         } else if (connector instanceof AttachingConnector) {
 190             vm = attachTarget();
 191         } else if (connector instanceof ListeningConnector) {
 192             vm = listenTarget();
 193         } else {
 194             throw new InternalError
 195                 (MessageOutput.format("Invalid connect type"));
 196         }
 197         vm.setDebugTraceMode(traceFlags);
 198         if (vm.canBeModified()){
 199             setEventRequests(vm);
 200             resolveEventRequests();
 201         }
 202         /*
 203          * Now that the vm connection is open, fetch the debugee
 204          * classpath and set up a default sourcepath.
 205          * (Unless user supplied a sourcepath on the command line)
 206          * (Bug ID 4186582)
 207          */
 208         if (Env.getSourcePath().length() == 0) {
 209             if (vm instanceof PathSearchingVirtualMachine) {
 210                 PathSearchingVirtualMachine psvm =
 211                     (PathSearchingVirtualMachine) vm;
 212                 Env.setSourcePath(psvm.classPath());
 213             } else {
 214                 Env.setSourcePath(".");
 215             }
 216         }
 217 
 218         return vm;
 219     }
 220 
 221     boolean setConnectorArg(String name, String value) {
 222         /*
 223          * Too late if the connection already made
 224          */
 225         if (vm != null) {
 226             return false;
 227         }
 228 
 229         Connector.Argument argument = connectorArgs.get(name);
 230         if (argument == null) {
 231             return false;
 232         }
 233         argument.setValue(value);
 234         return true;
 235     }
 236 
 237     String connectorArg(String name) {
 238         Connector.Argument argument = connectorArgs.get(name);
 239         if (argument == null) {
 240             return "";
 241         }
 242         return argument.value();
 243     }
 244 
 245     public synchronized VirtualMachine vm() {
 246         if (vm == null) {
 247             throw new VMNotConnectedException();
 248         } else {
 249             return vm;
 250         }
 251     }
 252 
 253     boolean isOpen() {
 254         return (vm != null);
 255     }
 256 
 257     boolean isLaunch() {
 258         return (connector instanceof LaunchingConnector);
 259     }
 260 
 261     public void disposeVM() {
 262         try {
 263             if (vm != null) {
 264                 vm.dispose();
 265                 vm = null;
 266             }
 267         } finally {
 268             if (process != null) {
 269                 process.destroy();
 270                 process = null;
 271             }
 272             waitOutputComplete();
 273         }
 274     }
 275 
 276     private void setEventRequests(VirtualMachine vm) {
 277         EventRequestManager erm = vm.eventRequestManager();
 278 
 279         // Normally, we want all uncaught exceptions.  We request them
 280         // via the same mechanism as Commands.commandCatchException()
 281         // so the user can ignore them later if they are not
 282         // interested.
 283         // FIXME: this works but generates spurious messages on stdout
 284         //        during startup:
 285         //          Set uncaught java.lang.Throwable
 286         //          Set deferred uncaught java.lang.Throwable
 287         Commands evaluator = new Commands();
 288         evaluator.commandCatchException
 289             (new StringTokenizer("uncaught java.lang.Throwable"));
 290 
 291         ThreadStartRequest tsr = erm.createThreadStartRequest();
 292         tsr.enable();
 293         ThreadDeathRequest tdr = erm.createThreadDeathRequest();
 294         tdr.enable();
 295     }
 296 
 297     private void resolveEventRequests() {
 298         Env.specList.resolveAll();
 299     }
 300 
 301     private void dumpStream(InputStream stream) throws IOException {
 302         BufferedReader in =
 303             new BufferedReader(new InputStreamReader(stream));
 304         int i;
 305         try {
 306             while ((i = in.read()) != -1) {
 307                    MessageOutput.printDirect((char)i);// Special case: use
 308                                                       //   printDirect()
 309             }
 310         } catch (IOException ex) {
 311             String s = ex.getMessage();
 312             if (!s.startsWith("Bad file number")) {
 313                   throw ex;
 314             }
 315             // else we got a Bad file number IOException which just means
 316             // that the debuggee has gone away.  We'll just treat it the
 317             // same as if we got an EOF.
 318         }
 319     }
 320 
 321     /**
 322      *  Create a Thread that will retrieve and display any output.
 323      *  Needs to be high priority, else debugger may exit before
 324      *  it can be displayed.
 325      */
 326     private void displayRemoteOutput(final InputStream stream) {
 327         Thread thr = new Thread("output reader") {
 328             @Override
 329             public void run() {
 330                 try {
 331                     dumpStream(stream);
 332                 } catch (IOException ex) {
 333                     MessageOutput.fatalError("Failed reading output");
 334                 } finally {
 335                     notifyOutputComplete();
 336                 }
 337             }
 338         };
 339         thr.setPriority(Thread.MAX_PRIORITY-1);
 340         thr.start();
 341     }
 342 
 343     private void dumpFailedLaunchInfo(Process process) {
 344         try {
 345             dumpStream(process.getErrorStream());
 346             dumpStream(process.getInputStream());
 347         } catch (IOException e) {
 348             MessageOutput.println("Unable to display process output:",
 349                                   e.getMessage());
 350         }
 351     }
 352 
 353     /* launch child target vm */
 354     private VirtualMachine launchTarget() {
 355         LaunchingConnector launcher = (LaunchingConnector)connector;
 356         try {
 357             VirtualMachine vm = launcher.launch(connectorArgs);
 358             process = vm.process();
 359             displayRemoteOutput(process.getErrorStream());
 360             displayRemoteOutput(process.getInputStream());
 361             return vm;
 362         } catch (IOException ioe) {
 363             ioe.printStackTrace();
 364             MessageOutput.fatalError("Unable to launch target VM.");
 365         } catch (IllegalConnectorArgumentsException icae) {
 366             icae.printStackTrace();
 367             MessageOutput.fatalError("Internal debugger error.");
 368         } catch (VMStartException vmse) {
 369             MessageOutput.println("vmstartexception", vmse.getMessage());
 370             MessageOutput.println();
 371             dumpFailedLaunchInfo(vmse.process());
 372             MessageOutput.fatalError("Target VM failed to initialize.");
 373         }
 374         return null; // Shuts up the compiler
 375     }
 376 
 377     /* attach to running target vm */
 378     private VirtualMachine attachTarget() {
 379         AttachingConnector attacher = (AttachingConnector)connector;
 380         try {
 381             return attacher.attach(connectorArgs);
 382         } catch (IOException ioe) {
 383             ioe.printStackTrace();
 384             MessageOutput.fatalError("Unable to attach to target VM.");
 385         } catch (IllegalConnectorArgumentsException icae) {
 386             icae.printStackTrace();
 387             MessageOutput.fatalError("Internal debugger error.");
 388         }
 389         return null; // Shuts up the compiler
 390     }
 391 
 392     /* listen for connection from target vm */
 393     private VirtualMachine listenTarget() {
 394         ListeningConnector listener = (ListeningConnector)connector;
 395         try {
 396             String retAddress = listener.startListening(connectorArgs);
 397             MessageOutput.println("Listening at address:", retAddress);
 398             vm = listener.accept(connectorArgs);
 399             listener.stopListening(connectorArgs);
 400             return vm;
 401         } catch (IOException ioe) {
 402             ioe.printStackTrace();
 403             MessageOutput.fatalError("Unable to attach to target VM.");
 404         } catch (IllegalConnectorArgumentsException icae) {
 405             icae.printStackTrace();
 406             MessageOutput.fatalError("Internal debugger error.");
 407         }
 408         return null; // Shuts up the compiler
 409     }
 410 }