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