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