1/* 2 * Copyright (c) 1998, 2017, 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 26package com.sun.tools.jdi; 27 28import java.lang.ref.WeakReference; 29import java.util.ArrayList; 30import java.util.Arrays; 31import java.util.Collections; 32import java.util.Iterator; 33import java.util.List; 34 35import com.sun.jdi.ClassNotLoadedException; 36import com.sun.jdi.IncompatibleThreadStateException; 37import com.sun.jdi.InternalException; 38import com.sun.jdi.InvalidStackFrameException; 39import com.sun.jdi.InvalidTypeException; 40import com.sun.jdi.Location; 41import com.sun.jdi.MonitorInfo; 42import com.sun.jdi.NativeMethodException; 43import com.sun.jdi.ObjectReference; 44import com.sun.jdi.ReferenceType; 45import com.sun.jdi.StackFrame; 46import com.sun.jdi.ThreadGroupReference; 47import com.sun.jdi.ThreadReference; 48import com.sun.jdi.Value; 49import com.sun.jdi.VirtualMachine; 50import com.sun.jdi.request.BreakpointRequest; 51 52public class ThreadReferenceImpl extends ObjectReferenceImpl 53 implements ThreadReference { 54 static final int SUSPEND_STATUS_SUSPENDED = 0x1; 55 static final int SUSPEND_STATUS_BREAK = 0x2; 56 57 private int suspendedZombieCount = 0; 58 59 /* 60 * Some objects can only be created while a thread is suspended and are valid 61 * only while the thread remains suspended. Examples are StackFrameImpl 62 * and MonitorInfoImpl. When the thread resumes, these objects have to be 63 * marked as invalid so that their methods can throw 64 * InvalidStackFrameException if they are called. To do this, such objects 65 * register themselves as listeners of the associated thread. When the 66 * thread is resumed, its listeners are notified and mark themselves 67 * invalid. 68 * Also, note that ThreadReferenceImpl itself caches some info that 69 * is valid only as long as the thread is suspended. When the thread 70 * is resumed, that cache must be purged. 71 * Lastly, note that ThreadReferenceImpl and its super, ObjectReferenceImpl 72 * cache some info that is only valid as long as the entire VM is suspended. 73 * If _any_ thread is resumed, this cache must be purged. To handle this, 74 * both ThreadReferenceImpl and ObjectReferenceImpl register themselves as 75 * VMListeners so that they get notified when all threads are suspended and 76 * when any thread is resumed. 77 */ 78 79 // This is cached for the life of the thread 80 private ThreadGroupReference threadGroup; 81 82 // This is cached only while this one thread is suspended. Each time 83 // the thread is resumed, we abandon the current cache object and 84 // create a new initialized one. 85 private static class LocalCache { 86 JDWP.ThreadReference.Status status = null; 87 List<StackFrame> frames = null; 88 int framesStart = -1; 89 int framesLength = 0; 90 int frameCount = -1; 91 List<ObjectReference> ownedMonitors = null; 92 List<MonitorInfo> ownedMonitorsInfo = null; 93 ObjectReference contendedMonitor = null; 94 boolean triedCurrentContended = false; 95 } 96 97 /* 98 * The localCache instance var is set by resetLocalCache to an initialized 99 * object as shown above. This occurs when the ThreadReference 100 * object is created, and when the mirrored thread is resumed. 101 * The fields are then filled in by the relevant methods as they 102 * are called. A problem can occur if resetLocalCache is called 103 * (ie, a resume() is executed) at certain points in the execution 104 * of some of these methods - see 6751643. To avoid this, each 105 * method that wants to use this cache must make a local copy of 106 * this variable and use that. This means that each invocation of 107 * these methods will use a copy of the cache object that was in 108 * effect at the point that the copy was made; if a racy resume 109 * occurs, it won't affect the method's local copy. This means that 110 * the values returned by these calls may not match the state of 111 * the debuggee at the time the caller gets the values. EG, 112 * frameCount() is called and comes up with 5 frames. But before 113 * it returns this, a resume of the debuggee thread is executed in a 114 * different debugger thread. The thread is resumed and running at 115 * the time that the value 5 is returned. Or even worse, the thread 116 * could be suspended again and have a different number of frames, eg, 24, 117 * but this call will still return 5. 118 */ 119 private LocalCache localCache; 120 121 private void resetLocalCache() { 122 localCache = new LocalCache(); 123 } 124 125 // This is cached only while all threads in the VM are suspended 126 // Yes, someone could change the name of a thread while it is suspended. 127 private static class Cache extends ObjectReferenceImpl.Cache { 128 String name = null; 129 } 130 protected ObjectReferenceImpl.Cache newCache() { 131 return new Cache(); 132 } 133 134 // Listeners - synchronized on vm.state() 135 private List<WeakReference<ThreadListener>> listeners = new ArrayList<>(); 136 137 ThreadReferenceImpl(VirtualMachine aVm, long aRef) { 138 super(aVm, aRef); 139 resetLocalCache(); 140 vm.state().addListener(this); 141 } 142 143 protected String description() { 144 return "ThreadReference " + uniqueID(); 145 } 146 147 /* 148 * VMListener implementation 149 */ 150 public boolean vmNotSuspended(VMAction action) { 151 if (action.resumingThread() == null) { 152 // all threads are being resumed 153 synchronized (vm.state()) { 154 processThreadAction(new ThreadAction(this, 155 ThreadAction.THREAD_RESUMABLE)); 156 } 157 158 } 159 160 /* 161 * Othewise, only one thread is being resumed: 162 * if it is us, 163 * we have already done our processThreadAction to notify our 164 * listeners when we processed the resume. 165 * if it is not us, 166 * we don't want to notify our listeners 167 * because we are not being resumed. 168 */ 169 return super.vmNotSuspended(action); 170 } 171 172 /** 173 * Note that we only cache the name string while the entire VM is suspended 174 * because the name can change via Thread.setName arbitrarily while this 175 * thread is running. 176 */ 177 public String name() { 178 String name = null; 179 try { 180 Cache local = (Cache)getCache(); 181 182 if (local != null) { 183 name = local.name; 184 } 185 if (name == null) { 186 name = JDWP.ThreadReference.Name.process(vm, this).threadName; 187 if (local != null) { 188 local.name = name; 189 } 190 } 191 } catch (JDWPException exc) { 192 throw exc.toJDIException(); 193 } 194 return name; 195 } 196 197 /* 198 * Sends a command to the back end which is defined to do an 199 * implicit vm-wide resume. 200 */ 201 PacketStream sendResumingCommand(CommandSender sender) { 202 synchronized (vm.state()) { 203 processThreadAction(new ThreadAction(this, 204 ThreadAction.THREAD_RESUMABLE)); 205 return sender.send(); 206 } 207 } 208 209 public void suspend() { 210 try { 211 JDWP.ThreadReference.Suspend.process(vm, this); 212 } catch (JDWPException exc) { 213 throw exc.toJDIException(); 214 } 215 // Don't consider the thread suspended yet. On reply, notifySuspend() 216 // will be called. 217 } 218 219 public void resume() { 220 /* 221 * If it's a zombie, we can just update internal state without 222 * going to back end. 223 */ 224 if (suspendedZombieCount > 0) { 225 suspendedZombieCount--; 226 return; 227 } 228 229 PacketStream stream; 230 synchronized (vm.state()) { 231 processThreadAction(new ThreadAction(this, 232 ThreadAction.THREAD_RESUMABLE)); 233 stream = JDWP.ThreadReference.Resume.enqueueCommand(vm, this); 234 } 235 try { 236 JDWP.ThreadReference.Resume.waitForReply(vm, stream); 237 } catch (JDWPException exc) { 238 throw exc.toJDIException(); 239 } 240 } 241 242 public int suspendCount() { 243 /* 244 * If it's a zombie, we maintain the count in the front end. 245 */ 246 if (suspendedZombieCount > 0) { 247 return suspendedZombieCount; 248 } 249 250 try { 251 return JDWP.ThreadReference.SuspendCount.process(vm, this).suspendCount; 252 } catch (JDWPException exc) { 253 throw exc.toJDIException(); 254 } 255 } 256 257 public void stop(ObjectReference throwable) throws InvalidTypeException { 258 validateMirrorOrNull(throwable); 259 // Verify that the given object is a Throwable instance 260 List<ReferenceType> list = vm.classesByName("java.lang.Throwable"); 261 ClassTypeImpl throwableClass = (ClassTypeImpl)list.get(0); 262 if ((throwable == null) || 263 !throwableClass.isAssignableFrom(throwable)) { 264 throw new InvalidTypeException("Not an instance of Throwable"); 265 } 266 267 try { 268 JDWP.ThreadReference.Stop.process(vm, this, 269 (ObjectReferenceImpl)throwable); 270 } catch (JDWPException exc) { 271 throw exc.toJDIException(); 272 } 273 } 274 275 public void interrupt() { 276 try { 277 JDWP.ThreadReference.Interrupt.process(vm, this); 278 } catch (JDWPException exc) { 279 throw exc.toJDIException(); 280 } 281 } 282 283 private JDWP.ThreadReference.Status jdwpStatus() { 284 LocalCache snapshot = localCache; 285 JDWP.ThreadReference.Status myStatus = snapshot.status; 286 try { 287 if (myStatus == null) { 288 myStatus = JDWP.ThreadReference.Status.process(vm, this); 289 if ((myStatus.suspendStatus & SUSPEND_STATUS_SUSPENDED) != 0) { 290 // thread is suspended, we can cache the status. 291 snapshot.status = myStatus; 292 } 293 } 294 } catch (JDWPException exc) { 295 throw exc.toJDIException(); 296 } 297 return myStatus; 298 } 299 300 public int status() { 301 return jdwpStatus().threadStatus; 302 } 303 304 public boolean isSuspended() { 305 return ((suspendedZombieCount > 0) || 306 ((jdwpStatus().suspendStatus & SUSPEND_STATUS_SUSPENDED) != 0)); 307 } 308 309 public boolean isAtBreakpoint() { 310 /* 311 * TO DO: This fails to take filters into account. 312 */ 313 try { 314 StackFrame frame = frame(0); 315 Location location = frame.location(); 316 List<BreakpointRequest> requests = vm.eventRequestManager().breakpointRequests(); 317 Iterator<BreakpointRequest> iter = requests.iterator(); 318 while (iter.hasNext()) { 319 BreakpointRequest request = iter.next(); 320 if (location.equals(request.location())) { 321 return true; 322 } 323 } 324 return false; 325 } catch (IndexOutOfBoundsException iobe) { 326 return false; // no frames on stack => not at breakpoint 327 } catch (IncompatibleThreadStateException itse) { 328 // Per the javadoc, not suspended => return false 329 return false; 330 } 331 } 332 333 public ThreadGroupReference threadGroup() { 334 /* 335 * Thread group can't change, so it's cached once and for all. 336 */ 337 if (threadGroup == null) { 338 try { 339 threadGroup = JDWP.ThreadReference.ThreadGroup. 340 process(vm, this).group; 341 } catch (JDWPException exc) { 342 throw exc.toJDIException(); 343 } 344 } 345 return threadGroup; 346 } 347 348 public int frameCount() throws IncompatibleThreadStateException { 349 LocalCache snapshot = localCache; 350 try { 351 if (snapshot.frameCount == -1) { 352 snapshot.frameCount = JDWP.ThreadReference.FrameCount 353 .process(vm, this).frameCount; 354 } 355 } catch (JDWPException exc) { 356 switch (exc.errorCode()) { 357 case JDWP.Error.THREAD_NOT_SUSPENDED: 358 case JDWP.Error.INVALID_THREAD: /* zombie */ 359 throw new IncompatibleThreadStateException(); 360 default: 361 throw exc.toJDIException(); 362 } 363 } 364 return snapshot.frameCount; 365 } 366 367 public List<StackFrame> frames() throws IncompatibleThreadStateException { 368 return privateFrames(0, -1); 369 } 370 371 public StackFrame frame(int index) throws IncompatibleThreadStateException { 372 List<StackFrame> list = privateFrames(index, 1); 373 return list.get(0); 374 } 375 376 /** 377 * Is the requested subrange within what has been retrieved? 378 * local is known to be non-null. Should only be called from 379 * a sync method. 380 */ 381 private boolean isSubrange(LocalCache snapshot, 382 int start, int length) { 383 if (start < snapshot.framesStart) { 384 return false; 385 } 386 if (length == -1) { 387 return (snapshot.framesLength == -1); 388 } 389 if (snapshot.framesLength == -1) { 390 if ((start + length) > (snapshot.framesStart + 391 snapshot.frames.size())) { 392 throw new IndexOutOfBoundsException(); 393 } 394 return true; 395 } 396 return ((start + length) <= (snapshot.framesStart + snapshot.framesLength)); 397 } 398 399 public List<StackFrame> frames(int start, int length) 400 throws IncompatibleThreadStateException { 401 if (length < 0) { 402 throw new IndexOutOfBoundsException( 403 "length must be greater than or equal to zero"); 404 } 405 return privateFrames(start, length); 406 } 407 408 /** 409 * Private version of frames() allows "-1" to specify all 410 * remaining frames. 411 */ 412 synchronized private List<StackFrame> privateFrames(int start, int length) 413 throws IncompatibleThreadStateException { 414 415 // Lock must be held while creating stack frames so if that two threads 416 // do this at the same time, one won't clobber the subset created by the other. 417 LocalCache snapshot = localCache; 418 try { 419 if (snapshot.frames == null || !isSubrange(snapshot, start, length)) { 420 JDWP.ThreadReference.Frames.Frame[] jdwpFrames 421 = JDWP.ThreadReference.Frames. 422 process(vm, this, start, length).frames; 423 int count = jdwpFrames.length; 424 snapshot.frames = new ArrayList<>(count); 425 426 for (int i = 0; i<count; i++) { 427 if (jdwpFrames[i].location == null) { 428 throw new InternalException("Invalid frame location"); 429 } 430 StackFrame frame = new StackFrameImpl(vm, this, 431 jdwpFrames[i].frameID, 432 jdwpFrames[i].location); 433 // Add to the frame list 434 snapshot.frames.add(frame); 435 } 436 snapshot.framesStart = start; 437 snapshot.framesLength = length; 438 return Collections.unmodifiableList(snapshot.frames); 439 } else { 440 int fromIndex = start - snapshot.framesStart; 441 int toIndex; 442 if (length == -1) { 443 toIndex = snapshot.frames.size() - fromIndex; 444 } else { 445 toIndex = fromIndex + length; 446 } 447 return Collections.unmodifiableList(snapshot.frames.subList(fromIndex, toIndex)); 448 } 449 } catch (JDWPException exc) { 450 switch (exc.errorCode()) { 451 case JDWP.Error.THREAD_NOT_SUSPENDED: 452 case JDWP.Error.INVALID_THREAD: /* zombie */ 453 throw new IncompatibleThreadStateException(); 454 default: 455 throw exc.toJDIException(); 456 } 457 } 458 } 459 460 public List<ObjectReference> ownedMonitors() throws IncompatibleThreadStateException { 461 LocalCache snapshot = localCache; 462 try { 463 if (snapshot.ownedMonitors == null) { 464 snapshot.ownedMonitors = Arrays.asList( 465 (ObjectReference[])JDWP.ThreadReference.OwnedMonitors. 466 process(vm, this).owned); 467 if ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) { 468 vm.printTrace(description() + 469 " temporarily caching owned monitors"+ 470 " (count = " + snapshot.ownedMonitors.size() + ")"); 471 } 472 } 473 } catch (JDWPException exc) { 474 switch (exc.errorCode()) { 475 case JDWP.Error.THREAD_NOT_SUSPENDED: 476 case JDWP.Error.INVALID_THREAD: /* zombie */ 477 throw new IncompatibleThreadStateException(); 478 default: 479 throw exc.toJDIException(); 480 } 481 } 482 return snapshot.ownedMonitors; 483 } 484 485 public ObjectReference currentContendedMonitor() 486 throws IncompatibleThreadStateException { 487 LocalCache snapshot = localCache; 488 try { 489 if (snapshot.contendedMonitor == null && 490 !snapshot.triedCurrentContended) { 491 snapshot.contendedMonitor = JDWP.ThreadReference.CurrentContendedMonitor. 492 process(vm, this).monitor; 493 snapshot.triedCurrentContended = true; 494 if ((snapshot.contendedMonitor != null) && 495 ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0)) { 496 vm.printTrace(description() + 497 " temporarily caching contended monitor"+ 498 " (id = " + snapshot.contendedMonitor.uniqueID() + ")"); 499 } 500 } 501 } catch (JDWPException exc) { 502 switch (exc.errorCode()) { 503 case JDWP.Error.THREAD_NOT_SUSPENDED: 504 case JDWP.Error.INVALID_THREAD: /* zombie */ 505 throw new IncompatibleThreadStateException(); 506 default: 507 throw exc.toJDIException(); 508 } 509 } 510 return snapshot.contendedMonitor; 511 } 512 513 public List<MonitorInfo> ownedMonitorsAndFrames() throws IncompatibleThreadStateException { 514 LocalCache snapshot = localCache; 515 try { 516 if (snapshot.ownedMonitorsInfo == null) { 517 JDWP.ThreadReference.OwnedMonitorsStackDepthInfo.monitor[] minfo; 518 minfo = JDWP.ThreadReference.OwnedMonitorsStackDepthInfo.process(vm, this).owned; 519 520 snapshot.ownedMonitorsInfo = new ArrayList<>(minfo.length); 521 522 for (int i=0; i < minfo.length; i++) { 523 MonitorInfo mon = new MonitorInfoImpl(vm, minfo[i].monitor, this, minfo[i].stack_depth); 524 snapshot.ownedMonitorsInfo.add(mon); 525 } 526 527 if ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) { 528 vm.printTrace(description() + 529 " temporarily caching owned monitors"+ 530 " (count = " + snapshot.ownedMonitorsInfo.size() + ")"); 531 } 532 } 533 534 } catch (JDWPException exc) { 535 switch (exc.errorCode()) { 536 case JDWP.Error.THREAD_NOT_SUSPENDED: 537 case JDWP.Error.INVALID_THREAD: /* zombie */ 538 throw new IncompatibleThreadStateException(); 539 default: 540 throw exc.toJDIException(); 541 } 542 } 543 return snapshot.ownedMonitorsInfo; 544 } 545 546 public void popFrames(StackFrame frame) throws IncompatibleThreadStateException { 547 // Note that interface-wise this functionality belongs 548 // here in ThreadReference, but implementation-wise it 549 // belongs in StackFrame, so we just forward it. 550 if (!frame.thread().equals(this)) { 551 throw new IllegalArgumentException("frame does not belong to this thread"); 552 } 553 if (!vm.canPopFrames()) { 554 throw new UnsupportedOperationException( 555 "target does not support popping frames"); 556 } 557 ((StackFrameImpl)frame).pop(); 558 } 559 560 public void forceEarlyReturn(Value returnValue) throws InvalidTypeException, 561 ClassNotLoadedException, 562 IncompatibleThreadStateException { 563 if (!vm.canForceEarlyReturn()) { 564 throw new UnsupportedOperationException( 565 "target does not support the forcing of a method to return early"); 566 } 567 568 validateMirrorOrNull(returnValue); 569 570 StackFrameImpl sf; 571 try { 572 sf = (StackFrameImpl)frame(0); 573 } catch (IndexOutOfBoundsException exc) { 574 throw new InvalidStackFrameException("No more frames on the stack"); 575 } 576 sf.validateStackFrame(); 577 MethodImpl meth = (MethodImpl)sf.location().method(); 578 ValueImpl convertedValue = ValueImpl.prepareForAssignment(returnValue, 579 meth.getReturnValueContainer()); 580 581 try { 582 JDWP.ThreadReference.ForceEarlyReturn.process(vm, this, convertedValue); 583 } catch (JDWPException exc) { 584 switch (exc.errorCode()) { 585 case JDWP.Error.OPAQUE_FRAME: 586 throw new NativeMethodException(); 587 case JDWP.Error.THREAD_NOT_SUSPENDED: 588 throw new IncompatibleThreadStateException( 589 "Thread not suspended"); 590 case JDWP.Error.THREAD_NOT_ALIVE: 591 throw new IncompatibleThreadStateException( 592 "Thread has not started or has finished"); 593 case JDWP.Error.NO_MORE_FRAMES: 594 throw new InvalidStackFrameException( 595 "No more frames on the stack"); 596 default: 597 throw exc.toJDIException(); 598 } 599 } 600 } 601 602 public String toString() { 603 return "instance of " + referenceType().name() + 604 "(name='" + name() + "', " + "id=" + uniqueID() + ")"; 605 } 606 607 byte typeValueKey() { 608 return JDWP.Tag.THREAD; 609 } 610 611 void addListener(ThreadListener listener) { 612 synchronized (vm.state()) { 613 listeners.add(new WeakReference<>(listener)); 614 } 615 } 616 617 void removeListener(ThreadListener listener) { 618 synchronized (vm.state()) { 619 Iterator<WeakReference<ThreadListener>> iter = listeners.iterator(); 620 while (iter.hasNext()) { 621 WeakReference<ThreadListener> ref = iter.next(); 622 if (listener.equals(ref.get())) { 623 iter.remove(); 624 break; 625 } 626 } 627 } 628 } 629 630 /** 631 * Propagate the thread state change information 632 * to registered listeners. 633 * Must be entered while synchronized on vm.state() 634 */ 635 private void processThreadAction(ThreadAction action) { 636 synchronized (vm.state()) { 637 Iterator<WeakReference<ThreadListener>> iter = listeners.iterator(); 638 while (iter.hasNext()) { 639 WeakReference<ThreadListener> ref = iter.next(); 640 ThreadListener listener = ref.get(); 641 if (listener != null) { 642 switch (action.id()) { 643 case ThreadAction.THREAD_RESUMABLE: 644 if (!listener.threadResumable(action)) { 645 iter.remove(); 646 } 647 break; 648 } 649 } else { 650 // Listener is unreachable; clean up 651 iter.remove(); 652 } 653 } 654 655 // Discard our local cache 656 resetLocalCache(); 657 } 658 } 659} 660