1/* 2 * Copyright (c) 1999, 2013, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24import com.sun.jdi.*; 25import com.sun.jdi.request.*; 26import com.sun.jdi.event.*; 27import java.util.*; 28import java.io.*; 29 30 31/** 32 * Framework used by all JDI regression tests 33 */ 34abstract public class JDIScaffold { 35 private boolean shouldTrace = false; 36 private VMConnection connection; 37 private VirtualMachine vm; 38 private EventRequestManager requestManager; 39 private List listeners = Collections.synchronizedList(new LinkedList()); 40 ThreadReference vmStartThread = null; 41 boolean vmDied = false; 42 boolean vmDisconnected = false; 43 44 static private class ArgInfo { 45 String targetVMArgs = ""; 46 String targetAppCommandLine = ""; 47 String connectorSpec = "com.sun.jdi.CommandLineLaunch:"; 48 int traceFlags = 0; 49 } 50 51 static public interface TargetListener { 52 boolean eventSetReceived(EventSet set); 53 boolean eventSetComplete(EventSet set); 54 boolean eventReceived(Event event); 55 boolean breakpointReached(BreakpointEvent event); 56 boolean exceptionThrown(ExceptionEvent event); 57 boolean stepCompleted(StepEvent event); 58 boolean classPrepared(ClassPrepareEvent event); 59 boolean classUnloaded(ClassUnloadEvent event); 60 boolean methodEntered(MethodEntryEvent event); 61 boolean methodExited(MethodExitEvent event); 62 boolean fieldAccessed(AccessWatchpointEvent event); 63 boolean fieldModified(ModificationWatchpointEvent event); 64 boolean threadStarted(ThreadStartEvent event); 65 boolean threadDied(ThreadDeathEvent event); 66 boolean vmStarted(VMStartEvent event); 67 boolean vmDied(VMDeathEvent event); 68 boolean vmDisconnected(VMDisconnectEvent event); 69 } 70 71 static public class TargetAdapter implements TargetListener { 72 public boolean eventSetReceived(EventSet set) { 73 return false; 74 } 75 public boolean eventSetComplete(EventSet set) { 76 return false; 77 } 78 public boolean eventReceived(Event event) { 79 return false; 80 } 81 public boolean breakpointReached(BreakpointEvent event) { 82 return false; 83 } 84 public boolean exceptionThrown(ExceptionEvent event) { 85 return false; 86 } 87 public boolean stepCompleted(StepEvent event) { 88 return false; 89 } 90 public boolean classPrepared(ClassPrepareEvent event) { 91 return false; 92 } 93 public boolean classUnloaded(ClassUnloadEvent event) { 94 return false; 95 } 96 public boolean methodEntered(MethodEntryEvent event) { 97 return false; 98 } 99 public boolean methodExited(MethodExitEvent event) { 100 return false; 101 } 102 public boolean fieldAccessed(AccessWatchpointEvent event) { 103 return false; 104 } 105 public boolean fieldModified(ModificationWatchpointEvent event) { 106 return false; 107 } 108 public boolean threadStarted(ThreadStartEvent event) { 109 return false; 110 } 111 public boolean threadDied(ThreadDeathEvent event) { 112 return false; 113 } 114 public boolean vmStarted(VMStartEvent event) { 115 return false; 116 } 117 public boolean vmDied(VMDeathEvent event) { 118 return false; 119 } 120 public boolean vmDisconnected(VMDisconnectEvent event) { 121 return false; 122 } 123 } 124 125 private class EventHandler implements Runnable { 126 EventHandler() { 127 Thread thread = new Thread(this); 128 thread.setDaemon(true); 129 thread.start(); 130 } 131 132 private boolean notifyEvent(TargetListener listener, Event event) { 133 if (listener.eventReceived(event) == true) { 134 return true; 135 } else if (event instanceof BreakpointEvent) { 136 return listener.breakpointReached((BreakpointEvent)event); 137 } else if (event instanceof ExceptionEvent) { 138 return listener.exceptionThrown((ExceptionEvent)event); 139 } else if (event instanceof StepEvent) { 140 return listener.stepCompleted((StepEvent)event); 141 } else if (event instanceof ClassPrepareEvent) { 142 return listener.classPrepared((ClassPrepareEvent)event); 143 } else if (event instanceof ClassUnloadEvent) { 144 return listener.classUnloaded((ClassUnloadEvent)event); 145 } else if (event instanceof MethodEntryEvent) { 146 return listener.methodEntered((MethodEntryEvent)event); 147 } else if (event instanceof MethodExitEvent) { 148 return listener.methodExited((MethodExitEvent)event); 149 } else if (event instanceof AccessWatchpointEvent) { 150 return listener.fieldAccessed((AccessWatchpointEvent)event); 151 } else if (event instanceof ModificationWatchpointEvent) { 152 return listener.fieldModified((ModificationWatchpointEvent)event); 153 } else if (event instanceof ThreadStartEvent) { 154 return listener.threadStarted((ThreadStartEvent)event); 155 } else if (event instanceof ThreadDeathEvent) { 156 return listener.threadDied((ThreadDeathEvent)event); 157 } else if (event instanceof VMStartEvent) { 158 return listener.vmStarted((VMStartEvent)event); 159 } else if (event instanceof VMDeathEvent) { 160 return listener.vmDied((VMDeathEvent)event); 161 } else if (event instanceof VMDisconnectEvent) { 162 return listener.vmDisconnected((VMDisconnectEvent)event); 163 } else { 164 throw new InternalError("Unknown event type: " + event.getClass()); 165 } 166 } 167 168 private void traceSuspendPolicy(int policy) { 169 if (shouldTrace) { 170 switch (policy) { 171 case EventRequest.SUSPEND_NONE: 172 traceln("JDI: runloop: suspend = SUSPEND_NONE"); 173 break; 174 case EventRequest.SUSPEND_ALL: 175 traceln("JDI: runloop: suspend = SUSPEND_ALL"); 176 break; 177 case EventRequest.SUSPEND_EVENT_THREAD: 178 traceln("JDI: runloop: suspend = SUSPEND_EVENT_THREAD"); 179 break; 180 } 181 } 182 } 183 184 public void run() { 185 boolean connected = true; 186 do { 187 try { 188 EventSet set = vm.eventQueue().remove(); 189 traceSuspendPolicy(set.suspendPolicy()); 190 synchronized (listeners) { 191 ListIterator iter = listeners.listIterator(); 192 while (iter.hasNext()) { 193 TargetListener listener = (TargetListener)iter.next(); 194 traceln("JDI: runloop: listener = " + listener); 195 if (listener.eventSetReceived(set) == true) { 196 iter.remove(); 197 } else { 198 Iterator jter = set.iterator(); 199 while (jter.hasNext()) { 200 Event event = (Event)jter.next(); 201 traceln("JDI: runloop: event = " + event.getClass()); 202 if (event instanceof VMDisconnectEvent) { 203 connected = false; 204 } 205 if (notifyEvent(listener, event) == true) { 206 iter.remove(); 207 break; 208 } 209 } 210 traceln("JDI: runloop: end of events loop"); 211 if (listener.eventSetComplete(set) == true) { 212 iter.remove(); 213 } 214 } 215 traceln("JDI: runloop: end of listener"); 216 } 217 } 218 } catch (InterruptedException e) { 219 } 220 traceln("JDI: runloop: end of outer loop"); 221 } while (connected); 222 } 223 } 224 225 /** 226 * Constructor 227 */ 228 public JDIScaffold() { 229 } 230 231 public void enableScaffoldTrace() { 232 this.shouldTrace = true; 233 } 234 235 public void disableScaffoldTrace() { 236 this.shouldTrace = false; 237 } 238 239 240 /* 241 * Test cases should implement tests in runTests and should 242 * initiate testing by calling run(). 243 */ 244 abstract protected void runTests() throws Exception; 245 246 final public void startTests() throws Exception { 247 try { 248 runTests(); 249 } finally { 250 shutdown(); 251 } 252 } 253 254 protected void println(String str) { 255 System.err.println(str); 256 } 257 258 protected void traceln(String str) { 259 if (shouldTrace) { 260 println(str); 261 } 262 } 263 264 private ArgInfo parseArgs(String args[]) { 265 ArgInfo argInfo = new ArgInfo(); 266 for (int i = 0; i < args.length; i++) { 267 if (args[i].equals("-connect")) { 268 i++; 269 argInfo.connectorSpec = args[i]; 270 } else if (args[i].equals("-trace")) { 271 i++; 272 argInfo.traceFlags = Integer.decode(args[i]).intValue(); 273 } else if (args[i].startsWith("-J")) { 274 argInfo.targetVMArgs += (args[i].substring(2) + ' '); 275 276 /* 277 * classpath can span two arguments so we need to handle 278 * it specially. 279 */ 280 if (args[i].equals("-J-classpath")) { 281 i++; 282 argInfo.targetVMArgs += (args[i] + ' '); 283 } 284 } else { 285 argInfo.targetAppCommandLine += (args[i] + ' '); 286 } 287 } 288 return argInfo; 289 } 290 291 public void connect(String args[]) { 292 ArgInfo argInfo = parseArgs(args); 293 294 argInfo.targetVMArgs += VMConnection.getDebuggeeVMOptions(); 295 connection = new VMConnection(argInfo.connectorSpec, 296 argInfo.traceFlags); 297 if (!connection.isLaunch()) { 298 throw new UnsupportedOperationException( 299 "Listening and Attaching not yet supported"); 300 } 301 302 /* 303 * Add a listener to track VM start/death/disconnection and 304 * to update status fields accordingly. 305 */ 306 addListener(new TargetAdapter() { 307 public boolean vmStarted(VMStartEvent event) { 308 traceln("JDI: listener1: got VMStart"); 309 synchronized(JDIScaffold.this) { 310 vmStartThread = event.thread(); 311 JDIScaffold.this.notifyAll(); 312 } 313 return false; 314 } 315 316 public boolean vmDied(VMDeathEvent event) { 317 traceln("JDI: listener1: got VMDeath"); 318 synchronized(JDIScaffold.this) { 319 vmDied = true; 320 JDIScaffold.this.notifyAll(); 321 } 322 return false; 323 } 324 325 public boolean vmDisconnected(VMDisconnectEvent event) { 326 traceln("JDI: listener1: got VMDisconnectedEvent"); 327 synchronized(JDIScaffold.this) { 328 vmDisconnected = true; 329 JDIScaffold.this.notifyAll(); 330 } 331 return false; 332 } 333 }); 334 335 if (connection.connector().name().equals("com.sun.jdi.CommandLineLaunch")) { 336 if (argInfo.targetVMArgs.length() > 0) { 337 if (connection.connectorArg("options").length() > 0) { 338 throw new IllegalArgumentException("VM options in two places"); 339 } 340 connection.setConnectorArg("options", argInfo.targetVMArgs); 341 } 342 if (argInfo.targetAppCommandLine.length() > 0) { 343 if (connection.connectorArg("main").length() > 0) { 344 throw new IllegalArgumentException("Command line in two places"); 345 } 346 connection.setConnectorArg("main", argInfo.targetAppCommandLine); 347 } 348 } 349 350 vm = connection.open(); 351 requestManager = vm.eventRequestManager(); 352 new EventHandler(); 353 } 354 355 356 public VirtualMachine vm() { 357 return vm; 358 } 359 360 public EventRequestManager eventRequestManager() { 361 return requestManager; 362 } 363 364 public void addListener(TargetListener listener) { 365 listeners.add(listener); 366 } 367 368 public void removeListener(TargetListener listener) { 369 listeners.remove(listener); 370 } 371 372 public synchronized ThreadReference waitForVMStart() { 373 while ((vmStartThread == null) && !vmDisconnected) { 374 try { 375 wait(); 376 } catch (InterruptedException e) { 377 } 378 } 379 380 if (vmStartThread == null) { 381 throw new VMDisconnectedException(); 382 } 383 384 return vmStartThread; 385 } 386 387 public synchronized void waitForVMDeath() { 388 while (!vmDied && !vmDisconnected) { 389 try { 390 traceln("JDI: waitForVMDeath: waiting"); 391 wait(); 392 } catch (InterruptedException e) { 393 } 394 } 395 traceln("JDI: waitForVMDeath: done waiting"); 396 397 if (!vmDied) { 398 throw new VMDisconnectedException(); 399 } 400 } 401 402 public Event waitForRequestedEvent(final EventRequest request) { 403 class EventNotification { 404 Event event; 405 boolean disconnected = false; 406 } 407 final EventNotification en = new EventNotification(); 408 409 TargetAdapter adapter = new TargetAdapter() { 410 public boolean eventReceived(Event event) { 411 if (request.equals(event.request())) { 412 synchronized (en) { 413 en.event = event; 414 en.notifyAll(); 415 } 416 return true; 417 } else if (event instanceof VMDisconnectEvent) { 418 synchronized (en) { 419 en.disconnected = true; 420 en.notifyAll(); 421 } 422 return true; 423 } else { 424 return false; 425 } 426 } 427 }; 428 429 addListener(adapter); 430 431 try { 432 synchronized (en) { 433 vm.resume(); 434 while (!en.disconnected && (en.event == null)) { 435 en.wait(); 436 } 437 } 438 } catch (InterruptedException e) { 439 return null; 440 } 441 442 if (en.disconnected) { 443 throw new RuntimeException("VM Disconnected before requested event occurred"); 444 } 445 return en.event; 446 } 447 448 private StepEvent doStep(ThreadReference thread, int gran, int depth) { 449 final StepRequest sr = 450 requestManager.createStepRequest(thread, gran, depth); 451 452 sr.addClassExclusionFilter("java.*"); 453 sr.addClassExclusionFilter("javax.*"); 454 sr.addClassExclusionFilter("sun.*"); 455 sr.addClassExclusionFilter("com.sun.*"); 456 sr.addClassExclusionFilter("com.oracle.*"); 457 sr.addClassExclusionFilter("oracle.*"); 458 sr.addClassExclusionFilter("jdk.internal.*"); 459 sr.addCountFilter(1); 460 sr.enable(); 461 StepEvent retEvent = (StepEvent)waitForRequestedEvent(sr); 462 requestManager.deleteEventRequest(sr); 463 return retEvent; 464 } 465 466 public StepEvent stepIntoInstruction(ThreadReference thread) { 467 return doStep(thread, StepRequest.STEP_MIN, StepRequest.STEP_INTO); 468 } 469 470 public StepEvent stepIntoLine(ThreadReference thread) { 471 return doStep(thread, StepRequest.STEP_LINE, StepRequest.STEP_INTO); 472 } 473 474 public StepEvent stepOverInstruction(ThreadReference thread) { 475 return doStep(thread, StepRequest.STEP_MIN, StepRequest.STEP_OVER); 476 } 477 478 public StepEvent stepOverLine(ThreadReference thread) { 479 return doStep(thread, StepRequest.STEP_LINE, StepRequest.STEP_OVER); 480 } 481 482 public StepEvent stepOut(ThreadReference thread) { 483 return doStep(thread, StepRequest.STEP_LINE, StepRequest.STEP_OUT); 484 } 485 486 public BreakpointEvent resumeTo(Location loc) { 487 final BreakpointRequest request = 488 requestManager.createBreakpointRequest(loc); 489 request.addCountFilter(1); 490 request.enable(); 491 return (BreakpointEvent)waitForRequestedEvent(request); 492 } 493 494 public ReferenceType findReferenceType(String name) { 495 List rts = vm.classesByName(name); 496 Iterator iter = rts.iterator(); 497 while (iter.hasNext()) { 498 ReferenceType rt = (ReferenceType)iter.next(); 499 if (rt.name().equals(name)) { 500 return rt; 501 } 502 } 503 return null; 504 } 505 506 public Method findMethod(ReferenceType rt, String name, String signature) { 507 List methods = rt.methods(); 508 Iterator iter = methods.iterator(); 509 while (iter.hasNext()) { 510 Method method = (Method)iter.next(); 511 if (method.name().equals(name) && 512 method.signature().equals(signature)) { 513 return method; 514 } 515 } 516 return null; 517 } 518 519 public Location findLocation(ReferenceType rt, int lineNumber) 520 throws AbsentInformationException { 521 List locs = rt.locationsOfLine(lineNumber); 522 if (locs.size() == 0) { 523 throw new IllegalArgumentException("Bad line number"); 524 } else if (locs.size() > 1) { 525 throw new IllegalArgumentException("Line number has multiple locations"); 526 } 527 528 return (Location)locs.get(0); 529 } 530 531 public BreakpointEvent resumeTo(String clsName, String methodName, 532 String methodSignature) { 533 ReferenceType rt = findReferenceType(clsName); 534 if (rt == null) { 535 rt = resumeToPrepareOf(clsName).referenceType(); 536 } 537 538 Method method = findMethod(rt, methodName, methodSignature); 539 if (method == null) { 540 throw new IllegalArgumentException("Bad method name/signature"); 541 } 542 543 return resumeTo(method.location()); 544 } 545 546 public BreakpointEvent resumeTo(String clsName, int lineNumber) throws AbsentInformationException { 547 ReferenceType rt = findReferenceType(clsName); 548 if (rt == null) { 549 rt = resumeToPrepareOf(clsName).referenceType(); 550 } 551 552 return resumeTo(findLocation(rt, lineNumber)); 553 } 554 555 public ClassPrepareEvent resumeToPrepareOf(String className) { 556 final ClassPrepareRequest request = 557 requestManager.createClassPrepareRequest(); 558 request.addClassFilter(className); 559 request.addCountFilter(1); 560 request.enable(); 561 return (ClassPrepareEvent)waitForRequestedEvent(request); 562 } 563 564 public void resumeToVMDeath() { 565 // If we are very close to VM death, we might get a VM disconnect 566 // before resume is complete. In that case ignore any VMDisconnectException 567 // and let the waitForVMDeath to clean up. 568 try { 569 traceln("JDI: resumeToVMDeath: resuming"); 570 vm.resume(); 571 traceln("JDI: resumeToVMDeath: resumed"); 572 } catch (VMDisconnectedException e) { 573 // clean up below 574 } 575 waitForVMDeath(); 576 } 577 578 public void shutdown() { 579 shutdown(null); 580 } 581 582 public void shutdown(String message) { 583 if ((connection != null) && !vmDied) { 584 try { 585 connection.disposeVM(); 586 } catch (VMDisconnectedException e) { 587 // Shutting down after the VM has gone away. This is 588 // not an error, and we just ignore it. 589 } 590 } 591 if (message != null) { 592 System.out.println(message); 593 } 594 } 595} 596