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