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 }