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 }