| /* |
| * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package com.sun.tools.jdi; |
| |
| import com.sun.jdi.*; |
| import com.sun.jdi.ModuleReference; |
| import com.sun.jdi.connect.spi.Connection; |
| import com.sun.jdi.request.EventRequestManager; |
| import com.sun.jdi.request.EventRequest; |
| import com.sun.jdi.request.BreakpointRequest; |
| import com.sun.jdi.event.EventQueue; |
| |
| import java.util.*; |
| import java.text.MessageFormat; |
| import java.lang.ref.ReferenceQueue; |
| import java.lang.ref.Reference; |
| import java.lang.ref.SoftReference; |
| import java.lang.ref.WeakReference; |
| |
| class VirtualMachineImpl extends MirrorImpl |
| implements PathSearchingVirtualMachine, ThreadListener { |
| // VM Level exported variables, these |
| // are unique to a given vm |
| public final int sizeofFieldRef; |
| public final int sizeofMethodRef; |
| public final int sizeofObjectRef; |
| public final int sizeofClassRef; |
| public final int sizeofFrameRef; |
| public final int sizeofModuleRef; |
| |
| final int sequenceNumber; |
| |
| private final TargetVM target; |
| private final EventQueueImpl eventQueue; |
| private final EventRequestManagerImpl internalEventRequestManager; |
| private final EventRequestManagerImpl eventRequestManager; |
| final VirtualMachineManagerImpl vmManager; |
| private final ThreadGroup threadGroupForJDI; |
| |
| // Allow direct access to this field so that that tracing code slows down |
| // JDI as little as possible when not enabled. |
| int traceFlags = TRACE_NONE; |
| |
| static int TRACE_RAW_SENDS = 0x01000000; |
| static int TRACE_RAW_RECEIVES = 0x02000000; |
| |
| boolean traceReceives = false; // pre-compute because of frequency |
| |
| // ReferenceType access - updated with class prepare and unload events |
| // Protected by "synchronized(this)". "retrievedAllTypes" may be |
| // tested unsynchronized (since once true, it stays true), but must |
| // be set synchronously |
| private Map<Long, ReferenceType> typesByID; |
| private TreeSet<ReferenceType> typesBySignature; |
| private boolean retrievedAllTypes = false; |
| |
| private Map<Long, ModuleReference> modulesByID; |
| |
| // For other languages support |
| private String defaultStratum = null; |
| |
| // ObjectReference cache |
| // "objectsByID" protected by "synchronized(this)". |
| private final Map<Long, SoftObjectReference> objectsByID = new HashMap<Long, SoftObjectReference>(); |
| private final ReferenceQueue<ObjectReferenceImpl> referenceQueue = new ReferenceQueue<ObjectReferenceImpl>(); |
| static private final int DISPOSE_THRESHOLD = 50; |
| private final List<SoftObjectReference> batchedDisposeRequests = |
| Collections.synchronizedList(new ArrayList<SoftObjectReference>(DISPOSE_THRESHOLD + 10)); |
| |
| // These are cached once for the life of the VM |
| private JDWP.VirtualMachine.Version versionInfo; |
| private JDWP.VirtualMachine.ClassPaths pathInfo; |
| private JDWP.VirtualMachine.Capabilities capabilities = null; |
| private JDWP.VirtualMachine.CapabilitiesNew capabilitiesNew = null; |
| |
| // Per-vm singletons for primitive types and for void. |
| // singleton-ness protected by "synchronized(this)". |
| private BooleanType theBooleanType; |
| private ByteType theByteType; |
| private CharType theCharType; |
| private ShortType theShortType; |
| private IntegerType theIntegerType; |
| private LongType theLongType; |
| private FloatType theFloatType; |
| private DoubleType theDoubleType; |
| |
| private VoidType theVoidType; |
| |
| private VoidValue voidVal; |
| |
| // Launched debuggee process |
| private Process process; |
| |
| // coordinates state changes and corresponding listener notifications |
| private VMState state = new VMState(this); |
| |
| private Object initMonitor = new Object(); |
| private boolean initComplete = false; |
| private boolean shutdown = false; |
| |
| private void notifyInitCompletion() { |
| synchronized(initMonitor) { |
| initComplete = true; |
| initMonitor.notifyAll(); |
| } |
| } |
| |
| void waitInitCompletion() { |
| synchronized(initMonitor) { |
| while (!initComplete) { |
| try { |
| initMonitor.wait(); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| } |
| } |
| } |
| |
| VMState state() { |
| return state; |
| } |
| |
| /* |
| * ThreadListener implementation |
| */ |
| public boolean threadResumable(ThreadAction action) { |
| /* |
| * If any thread is resumed, the VM is considered not suspended. |
| * Just one thread is being resumed so pass it to thaw. |
| */ |
| state.thaw(action.thread()); |
| return true; |
| } |
| |
| VirtualMachineImpl(VirtualMachineManager manager, |
| Connection connection, Process process, |
| int sequenceNumber) { |
| super(null); // Can't use super(this) |
| vm = this; |
| |
| this.vmManager = (VirtualMachineManagerImpl)manager; |
| this.process = process; |
| this.sequenceNumber = sequenceNumber; |
| |
| /* Create ThreadGroup to be used by all threads servicing |
| * this VM. |
| */ |
| threadGroupForJDI = new ThreadGroup(vmManager.mainGroupForJDI(), |
| "JDI [" + |
| this.hashCode() + "]"); |
| |
| /* |
| * Set up a thread to communicate with the target VM over |
| * the specified transport. |
| */ |
| target = new TargetVM(this, connection); |
| |
| /* |
| * Set up a thread to handle events processed internally |
| * the JDI implementation. |
| */ |
| EventQueueImpl internalEventQueue = new EventQueueImpl(this, target); |
| new InternalEventHandler(this, internalEventQueue); |
| /* |
| * Initialize client access to event setting and handling |
| */ |
| eventQueue = new EventQueueImpl(this, target); |
| eventRequestManager = new EventRequestManagerImpl(this); |
| |
| target.start(); |
| |
| /* |
| * Many ids are variably sized, depending on target VM. |
| * Find out the sizes right away. |
| */ |
| JDWP.VirtualMachine.IDSizes idSizes; |
| try { |
| idSizes = JDWP.VirtualMachine.IDSizes.process(vm); |
| } catch (JDWPException exc) { |
| throw exc.toJDIException(); |
| } |
| sizeofFieldRef = idSizes.fieldIDSize; |
| sizeofMethodRef = idSizes.methodIDSize; |
| sizeofObjectRef = idSizes.objectIDSize; |
| sizeofClassRef = idSizes.referenceTypeIDSize; |
| sizeofFrameRef = idSizes.frameIDSize; |
| sizeofModuleRef = idSizes.objectIDSize; |
| |
| /** |
| * Set up requests needed by internal event handler. |
| * Make sure they are distinguished by creating them with |
| * an internal event request manager. |
| * |
| * Warning: create events only with SUSPEND_NONE policy. |
| * In the current implementation other policies will not |
| * be handled correctly when the event comes in. (notfiySuspend() |
| * will not be properly called, and if the event is combined |
| * with external events in the same set, suspend policy is not |
| * correctly determined for the internal vs. external event sets) |
| */ |
| internalEventRequestManager = new EventRequestManagerImpl(this); |
| EventRequest er = internalEventRequestManager.createClassPrepareRequest(); |
| er.setSuspendPolicy(EventRequest.SUSPEND_NONE); |
| er.enable(); |
| er = internalEventRequestManager.createClassUnloadRequest(); |
| er.setSuspendPolicy(EventRequest.SUSPEND_NONE); |
| er.enable(); |
| |
| /* |
| * Tell other threads, notably TargetVM, that initialization |
| * is complete. |
| */ |
| notifyInitCompletion(); |
| } |
| |
| EventRequestManagerImpl getInternalEventRequestManager() { |
| return internalEventRequestManager; |
| } |
| |
| void validateVM() { |
| /* |
| * We no longer need to do this. The spec now says |
| * that a VMDisconnected _may_ be thrown in these |
| * cases, not that it _will_ be thrown. |
| * So, to simplify things we will just let the |
| * caller's of this method proceed with their business. |
| * If the debuggee is disconnected, either because it |
| * crashed or finished or something, or because the |
| * debugger called exit() or dispose(), then if |
| * we end up trying to communicate with the debuggee, |
| * code in TargetVM will throw a VMDisconnectedException. |
| * This means that if we can satisfy a request without |
| * talking to the debuggee, (eg, with cached data) then |
| * VMDisconnectedException will _not_ be thrown. |
| * if (shutdown) { |
| * throw new VMDisconnectedException(); |
| * } |
| */ |
| } |
| |
| public boolean equals(Object obj) { |
| return this == obj; |
| } |
| |
| public int hashCode() { |
| return System.identityHashCode(this); |
| } |
| |
| public List<ModuleReference> allModules() { |
| validateVM(); |
| List<ModuleReference> modules = retrieveAllModules(); |
| return Collections.unmodifiableList(modules); |
| } |
| |
| public List<ReferenceType> classesByName(String className) { |
| validateVM(); |
| String signature = JNITypeParser.typeNameToSignature(className); |
| List<ReferenceType> list; |
| if (retrievedAllTypes) { |
| list = findReferenceTypes(signature); |
| } else { |
| list = retrieveClassesBySignature(signature); |
| } |
| return Collections.unmodifiableList(list); |
| } |
| |
| public List<ReferenceType> allClasses() { |
| validateVM(); |
| |
| if (!retrievedAllTypes) { |
| retrieveAllClasses(); |
| } |
| ArrayList<ReferenceType> a; |
| synchronized (this) { |
| a = new ArrayList<ReferenceType>(typesBySignature); |
| } |
| return Collections.unmodifiableList(a); |
| } |
| |
| public void |
| redefineClasses(Map<? extends ReferenceType,byte[]> classToBytes) |
| { |
| int cnt = classToBytes.size(); |
| JDWP.VirtualMachine.RedefineClasses.ClassDef[] defs = |
| new JDWP.VirtualMachine.RedefineClasses.ClassDef[cnt]; |
| validateVM(); |
| if (!canRedefineClasses()) { |
| throw new UnsupportedOperationException(); |
| } |
| Iterator<?> it = classToBytes.entrySet().iterator(); |
| for (int i = 0; it.hasNext(); i++) { |
| Map.Entry<?,?> entry = (Map.Entry)it.next(); |
| ReferenceTypeImpl refType = (ReferenceTypeImpl)entry.getKey(); |
| validateMirror(refType); |
| defs[i] = new JDWP.VirtualMachine.RedefineClasses |
| .ClassDef(refType, (byte[])entry.getValue()); |
| } |
| |
| // flush caches and disable caching until the next suspend |
| vm.state().thaw(); |
| |
| try { |
| JDWP.VirtualMachine.RedefineClasses. |
| process(vm, defs); |
| } catch (JDWPException exc) { |
| switch (exc.errorCode()) { |
| case JDWP.Error.INVALID_CLASS_FORMAT : |
| throw new ClassFormatError( |
| "class not in class file format"); |
| case JDWP.Error.CIRCULAR_CLASS_DEFINITION : |
| throw new ClassCircularityError( |
| "circularity has been detected while initializing a class"); |
| case JDWP.Error.FAILS_VERIFICATION : |
| throw new VerifyError( |
| "verifier detected internal inconsistency or security problem"); |
| case JDWP.Error.UNSUPPORTED_VERSION : |
| throw new UnsupportedClassVersionError( |
| "version numbers of class are not supported"); |
| case JDWP.Error.ADD_METHOD_NOT_IMPLEMENTED: |
| throw new UnsupportedOperationException( |
| "add method not implemented"); |
| case JDWP.Error.SCHEMA_CHANGE_NOT_IMPLEMENTED : |
| throw new UnsupportedOperationException( |
| "schema change not implemented"); |
| case JDWP.Error.HIERARCHY_CHANGE_NOT_IMPLEMENTED: |
| throw new UnsupportedOperationException( |
| "hierarchy change not implemented"); |
| case JDWP.Error.DELETE_METHOD_NOT_IMPLEMENTED : |
| throw new UnsupportedOperationException( |
| "delete method not implemented"); |
| case JDWP.Error.CLASS_MODIFIERS_CHANGE_NOT_IMPLEMENTED: |
| throw new UnsupportedOperationException( |
| "changes to class modifiers not implemented"); |
| case JDWP.Error.METHOD_MODIFIERS_CHANGE_NOT_IMPLEMENTED : |
| throw new UnsupportedOperationException( |
| "changes to method modifiers not implemented"); |
| case JDWP.Error.NAMES_DONT_MATCH : |
| throw new NoClassDefFoundError( |
| "class names do not match"); |
| default: |
| throw exc.toJDIException(); |
| } |
| } |
| |
| // Delete any record of the breakpoints |
| List<BreakpointRequest> toDelete = new ArrayList<BreakpointRequest>(); |
| EventRequestManager erm = eventRequestManager(); |
| it = erm.breakpointRequests().iterator(); |
| while (it.hasNext()) { |
| BreakpointRequest req = (BreakpointRequest)it.next(); |
| if (classToBytes.containsKey(req.location().declaringType())) { |
| toDelete.add(req); |
| } |
| } |
| erm.deleteEventRequests(toDelete); |
| |
| // Invalidate any information cached for the classes just redefined. |
| it = classToBytes.keySet().iterator(); |
| while (it.hasNext()) { |
| ReferenceTypeImpl rti = (ReferenceTypeImpl)it.next(); |
| rti.noticeRedefineClass(); |
| } |
| } |
| |
| public List<ThreadReference> allThreads() { |
| validateVM(); |
| return state.allThreads(); |
| } |
| |
| public List<ThreadGroupReference> topLevelThreadGroups() { |
| validateVM(); |
| return state.topLevelThreadGroups(); |
| } |
| |
| /* |
| * Sends a command to the back end which is defined to do an |
| * implicit vm-wide resume. The VM can no longer be considered |
| * suspended, so certain cached data must be invalidated. |
| */ |
| PacketStream sendResumingCommand(CommandSender sender) { |
| return state.thawCommand(sender); |
| } |
| |
| /* |
| * The VM has been suspended. Additional caching can be done |
| * as long as there are no pending resumes. |
| */ |
| void notifySuspend() { |
| state.freeze(); |
| } |
| |
| public void suspend() { |
| validateVM(); |
| try { |
| JDWP.VirtualMachine.Suspend.process(vm); |
| } catch (JDWPException exc) { |
| throw exc.toJDIException(); |
| } |
| notifySuspend(); |
| } |
| |
| public void resume() { |
| validateVM(); |
| CommandSender sender = |
| new CommandSender() { |
| public PacketStream send() { |
| return JDWP.VirtualMachine.Resume.enqueueCommand(vm); |
| } |
| }; |
| try { |
| PacketStream stream = state.thawCommand(sender); |
| JDWP.VirtualMachine.Resume.waitForReply(vm, stream); |
| } catch (VMDisconnectedException exc) { |
| /* |
| * If the debugger makes a VMDeathRequest with SUSPEND_ALL, |
| * then when it does an EventSet.resume after getting the |
| * VMDeathEvent, the normal flow of events is that the |
| * BE shuts down, but the waitForReply comes back ok. In this |
| * case, the run loop in TargetVM that is waiting for a packet |
| * gets an EOF because the socket closes. It generates a |
| * VMDisconnectedEvent and everyone is happy. |
| * However, sometimes, the BE gets shutdown before this |
| * waitForReply completes. In this case, TargetVM.waitForReply |
| * gets awakened with no reply and so gens a VMDisconnectedException |
| * which is not what we want. It might be possible to fix this |
| * in the BE, but it is ok to just ignore the VMDisconnectedException |
| * here. This will allow the VMDisconnectedEvent to be generated |
| * correctly. And, if the debugger should happen to make another |
| * request, it will get a VMDisconnectedException at that time. |
| */ |
| } catch (JDWPException exc) { |
| switch (exc.errorCode()) { |
| case JDWP.Error.VM_DEAD: |
| return; |
| default: |
| throw exc.toJDIException(); |
| } |
| } |
| } |
| |
| public EventQueue eventQueue() { |
| /* |
| * No VM validation here. We allow access to the event queue |
| * after disconnection, so that there is access to the terminating |
| * events. |
| */ |
| return eventQueue; |
| } |
| |
| public EventRequestManager eventRequestManager() { |
| validateVM(); |
| return eventRequestManager; |
| } |
| |
| EventRequestManagerImpl eventRequestManagerImpl() { |
| return eventRequestManager; |
| } |
| |
| public BooleanValue mirrorOf(boolean value) { |
| validateVM(); |
| return new BooleanValueImpl(this,value); |
| } |
| |
| public ByteValue mirrorOf(byte value) { |
| validateVM(); |
| return new ByteValueImpl(this,value); |
| } |
| |
| public CharValue mirrorOf(char value) { |
| validateVM(); |
| return new CharValueImpl(this,value); |
| } |
| |
| public ShortValue mirrorOf(short value) { |
| validateVM(); |
| return new ShortValueImpl(this,value); |
| } |
| |
| public IntegerValue mirrorOf(int value) { |
| validateVM(); |
| return new IntegerValueImpl(this,value); |
| } |
| |
| public LongValue mirrorOf(long value) { |
| validateVM(); |
| return new LongValueImpl(this,value); |
| } |
| |
| public FloatValue mirrorOf(float value) { |
| validateVM(); |
| return new FloatValueImpl(this,value); |
| } |
| |
| public DoubleValue mirrorOf(double value) { |
| validateVM(); |
| return new DoubleValueImpl(this,value); |
| } |
| |
| public StringReference mirrorOf(String value) { |
| validateVM(); |
| try { |
| return (StringReference)JDWP.VirtualMachine.CreateString. |
| process(vm, value).stringObject; |
| } catch (JDWPException exc) { |
| throw exc.toJDIException(); |
| } |
| } |
| |
| public VoidValue mirrorOfVoid() { |
| if (voidVal == null) { |
| voidVal = new VoidValueImpl(this); |
| } |
| return voidVal; |
| } |
| |
| public long[] instanceCounts(List<? extends ReferenceType> classes) { |
| if (!canGetInstanceInfo()) { |
| throw new UnsupportedOperationException( |
| "target does not support getting instances"); |
| } |
| long[] retValue ; |
| ReferenceTypeImpl[] rtArray = new ReferenceTypeImpl[classes.size()]; |
| int ii = 0; |
| for (ReferenceType rti: classes) { |
| validateMirror(rti); |
| rtArray[ii++] = (ReferenceTypeImpl)rti; |
| } |
| try { |
| retValue = JDWP.VirtualMachine.InstanceCounts. |
| process(vm, rtArray).counts; |
| } catch (JDWPException exc) { |
| throw exc.toJDIException(); |
| } |
| |
| return retValue; |
| } |
| |
| public void dispose() { |
| validateVM(); |
| shutdown = true; |
| try { |
| JDWP.VirtualMachine.Dispose.process(vm); |
| } catch (JDWPException exc) { |
| throw exc.toJDIException(); |
| } |
| target.stopListening(); |
| } |
| |
| public void exit(int exitCode) { |
| validateVM(); |
| shutdown = true; |
| try { |
| JDWP.VirtualMachine.Exit.process(vm, exitCode); |
| } catch (JDWPException exc) { |
| throw exc.toJDIException(); |
| } |
| target.stopListening(); |
| } |
| |
| public Process process() { |
| validateVM(); |
| return process; |
| } |
| |
| private JDWP.VirtualMachine.Version versionInfo() { |
| try { |
| if (versionInfo == null) { |
| // Need not be synchronized since it is static information |
| versionInfo = JDWP.VirtualMachine.Version.process(vm); |
| } |
| return versionInfo; |
| } catch (JDWPException exc) { |
| throw exc.toJDIException(); |
| } |
| } |
| |
| public String description() { |
| validateVM(); |
| |
| return MessageFormat.format(vmManager.getString("version_format"), |
| "" + vmManager.majorInterfaceVersion(), |
| "" + vmManager.minorInterfaceVersion(), |
| versionInfo().description); |
| } |
| |
| public String version() { |
| validateVM(); |
| return versionInfo().vmVersion; |
| } |
| |
| public String name() { |
| validateVM(); |
| return versionInfo().vmName; |
| } |
| |
| public boolean canWatchFieldModification() { |
| validateVM(); |
| return capabilities().canWatchFieldModification; |
| } |
| public boolean canWatchFieldAccess() { |
| validateVM(); |
| return capabilities().canWatchFieldAccess; |
| } |
| public boolean canGetBytecodes() { |
| validateVM(); |
| return capabilities().canGetBytecodes; |
| } |
| public boolean canGetSyntheticAttribute() { |
| validateVM(); |
| return capabilities().canGetSyntheticAttribute; |
| } |
| public boolean canGetOwnedMonitorInfo() { |
| validateVM(); |
| return capabilities().canGetOwnedMonitorInfo; |
| } |
| public boolean canGetCurrentContendedMonitor() { |
| validateVM(); |
| return capabilities().canGetCurrentContendedMonitor; |
| } |
| public boolean canGetMonitorInfo() { |
| validateVM(); |
| return capabilities().canGetMonitorInfo; |
| } |
| |
| private boolean hasNewCapabilities() { |
| return versionInfo().jdwpMajor > 1 || |
| versionInfo().jdwpMinor >= 4; |
| } |
| |
| boolean canGet1_5LanguageFeatures() { |
| return versionInfo().jdwpMajor > 1 || |
| versionInfo().jdwpMinor >= 5; |
| } |
| |
| public boolean canUseInstanceFilters() { |
| validateVM(); |
| return hasNewCapabilities() && |
| capabilitiesNew().canUseInstanceFilters; |
| } |
| public boolean canRedefineClasses() { |
| validateVM(); |
| return hasNewCapabilities() && |
| capabilitiesNew().canRedefineClasses; |
| } |
| public boolean canAddMethod() { |
| validateVM(); |
| return hasNewCapabilities() && |
| capabilitiesNew().canAddMethod; |
| } |
| public boolean canUnrestrictedlyRedefineClasses() { |
| validateVM(); |
| return hasNewCapabilities() && |
| capabilitiesNew().canUnrestrictedlyRedefineClasses; |
| } |
| public boolean canPopFrames() { |
| validateVM(); |
| return hasNewCapabilities() && |
| capabilitiesNew().canPopFrames; |
| } |
| public boolean canGetMethodReturnValues() { |
| return versionInfo().jdwpMajor > 1 || |
| versionInfo().jdwpMinor >= 6; |
| } |
| public boolean canGetInstanceInfo() { |
| if (versionInfo().jdwpMajor > 1 || |
| versionInfo().jdwpMinor >= 6) { |
| validateVM(); |
| return hasNewCapabilities() && |
| capabilitiesNew().canGetInstanceInfo; |
| } else { |
| return false; |
| } |
| } |
| public boolean canUseSourceNameFilters() { |
| return versionInfo().jdwpMajor > 1 || |
| versionInfo().jdwpMinor >= 6; |
| } |
| public boolean canForceEarlyReturn() { |
| validateVM(); |
| return hasNewCapabilities() && |
| capabilitiesNew().canForceEarlyReturn; |
| } |
| public boolean canBeModified() { |
| return true; |
| } |
| public boolean canGetSourceDebugExtension() { |
| validateVM(); |
| return hasNewCapabilities() && |
| capabilitiesNew().canGetSourceDebugExtension; |
| } |
| public boolean canGetClassFileVersion() { |
| return versionInfo().jdwpMajor > 1 || |
| versionInfo().jdwpMinor >= 6; |
| } |
| public boolean canGetConstantPool() { |
| validateVM(); |
| return hasNewCapabilities() && |
| capabilitiesNew().canGetConstantPool; |
| } |
| public boolean canRequestVMDeathEvent() { |
| validateVM(); |
| return hasNewCapabilities() && |
| capabilitiesNew().canRequestVMDeathEvent; |
| } |
| public boolean canRequestMonitorEvents() { |
| validateVM(); |
| return hasNewCapabilities() && |
| capabilitiesNew().canRequestMonitorEvents; |
| } |
| public boolean canGetMonitorFrameInfo() { |
| validateVM(); |
| return hasNewCapabilities() && |
| capabilitiesNew().canGetMonitorFrameInfo; |
| } |
| public boolean canGetModuleInfo() { |
| validateVM(); |
| return versionInfo().jdwpMajor >= 9; |
| } |
| |
| public void setDebugTraceMode(int traceFlags) { |
| validateVM(); |
| this.traceFlags = traceFlags; |
| this.traceReceives = (traceFlags & TRACE_RECEIVES) != 0; |
| } |
| |
| void printTrace(String string) { |
| System.err.println("[JDI: " + string + "]"); |
| } |
| |
| void printReceiveTrace(int depth, String string) { |
| StringBuilder sb = new StringBuilder("Receiving:"); |
| for (int i = depth; i > 0; --i) { |
| sb.append(" "); |
| } |
| sb.append(string); |
| printTrace(sb.toString()); |
| } |
| |
| private synchronized ReferenceTypeImpl addReferenceType(long id, |
| int tag, |
| String signature) { |
| if (typesByID == null) { |
| initReferenceTypes(); |
| } |
| ReferenceTypeImpl type = null; |
| switch(tag) { |
| case JDWP.TypeTag.CLASS: |
| type = new ClassTypeImpl(vm, id); |
| break; |
| case JDWP.TypeTag.INTERFACE: |
| type = new InterfaceTypeImpl(vm, id); |
| break; |
| case JDWP.TypeTag.ARRAY: |
| type = new ArrayTypeImpl(vm, id); |
| break; |
| default: |
| throw new InternalException("Invalid reference type tag"); |
| } |
| |
| /* |
| * If a signature was specified, make sure to set it ASAP, to |
| * prevent any needless JDWP command to retrieve it. (for example, |
| * typesBySignature.add needs the signature, to maintain proper |
| * ordering. |
| */ |
| if (signature != null) { |
| type.setSignature(signature); |
| } |
| |
| typesByID.put(id, type); |
| typesBySignature.add(type); |
| |
| if ((vm.traceFlags & VirtualMachine.TRACE_REFTYPES) != 0) { |
| vm.printTrace("Caching new ReferenceType, sig=" + signature + |
| ", id=" + id); |
| } |
| |
| return type; |
| } |
| |
| synchronized void removeReferenceType(String signature) { |
| if (typesByID == null) { |
| return; |
| } |
| /* |
| * There can be multiple classes with the same name. Since |
| * we can't differentiate here, we first remove all |
| * matching classes from our cache... |
| */ |
| Iterator<ReferenceType> iter = typesBySignature.iterator(); |
| int matches = 0; |
| while (iter.hasNext()) { |
| ReferenceTypeImpl type = (ReferenceTypeImpl)iter.next(); |
| int comp = signature.compareTo(type.signature()); |
| if (comp == 0) { |
| matches++; |
| iter.remove(); |
| typesByID.remove(type.ref()); |
| if ((vm.traceFlags & VirtualMachine.TRACE_REFTYPES) != 0) { |
| vm.printTrace("Uncaching ReferenceType, sig=" + signature + |
| ", id=" + type.ref()); |
| } |
| /* fix for 4359077 , don't break out. list is no longer sorted |
| in the order we think |
| */ |
| } |
| } |
| |
| /* |
| * ...and if there was more than one, re-retrieve the classes |
| * with that name |
| */ |
| if (matches > 1) { |
| retrieveClassesBySignature(signature); |
| } |
| } |
| |
| private synchronized List<ReferenceType> findReferenceTypes(String signature) { |
| if (typesByID == null) { |
| return new ArrayList<ReferenceType>(0); |
| } |
| Iterator<ReferenceType> iter = typesBySignature.iterator(); |
| List<ReferenceType> list = new ArrayList<ReferenceType>(); |
| while (iter.hasNext()) { |
| ReferenceTypeImpl type = (ReferenceTypeImpl)iter.next(); |
| int comp = signature.compareTo(type.signature()); |
| if (comp == 0) { |
| list.add(type); |
| /* fix for 4359077 , don't break out. list is no longer sorted |
| in the order we think |
| */ |
| } |
| } |
| return list; |
| } |
| |
| private void initReferenceTypes() { |
| typesByID = new HashMap<Long, ReferenceType>(300); |
| typesBySignature = new TreeSet<ReferenceType>(); |
| } |
| |
| ReferenceTypeImpl referenceType(long ref, byte tag) { |
| return referenceType(ref, tag, null); |
| } |
| |
| ClassTypeImpl classType(long ref) { |
| return (ClassTypeImpl)referenceType(ref, JDWP.TypeTag.CLASS, null); |
| } |
| |
| InterfaceTypeImpl interfaceType(long ref) { |
| return (InterfaceTypeImpl)referenceType(ref, JDWP.TypeTag.INTERFACE, null); |
| } |
| |
| ArrayTypeImpl arrayType(long ref) { |
| return (ArrayTypeImpl)referenceType(ref, JDWP.TypeTag.ARRAY, null); |
| } |
| |
| ReferenceTypeImpl referenceType(long id, int tag, |
| String signature) { |
| if ((vm.traceFlags & VirtualMachine.TRACE_REFTYPES) != 0) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("Looking up "); |
| if (tag == JDWP.TypeTag.CLASS) { |
| sb.append("Class"); |
| } else if (tag == JDWP.TypeTag.INTERFACE) { |
| sb.append("Interface"); |
| } else if (tag == JDWP.TypeTag.ARRAY) { |
| sb.append("ArrayType"); |
| } else { |
| sb.append("UNKNOWN TAG: ").append(tag); |
| } |
| if (signature != null) { |
| sb.append(", signature='").append(signature).append('\''); |
| } |
| sb.append(", id=").append(id); |
| vm.printTrace(sb.toString()); |
| } |
| if (id == 0) { |
| return null; |
| } else { |
| ReferenceTypeImpl retType = null; |
| synchronized (this) { |
| if (typesByID != null) { |
| retType = (ReferenceTypeImpl)typesByID.get(id); |
| } |
| if (retType == null) { |
| retType = addReferenceType(id, tag, signature); |
| } |
| } |
| return retType; |
| } |
| } |
| |
| private JDWP.VirtualMachine.Capabilities capabilities() { |
| if (capabilities == null) { |
| try { |
| capabilities = JDWP.VirtualMachine |
| .Capabilities.process(vm); |
| } catch (JDWPException exc) { |
| throw exc.toJDIException(); |
| } |
| } |
| return capabilities; |
| } |
| |
| private JDWP.VirtualMachine.CapabilitiesNew capabilitiesNew() { |
| if (capabilitiesNew == null) { |
| try { |
| capabilitiesNew = JDWP.VirtualMachine |
| .CapabilitiesNew.process(vm); |
| } catch (JDWPException exc) { |
| throw exc.toJDIException(); |
| } |
| } |
| return capabilitiesNew; |
| } |
| |
| private synchronized ModuleReference addModule(long id) { |
| if (modulesByID == null) { |
| modulesByID = new HashMap<Long, ModuleReference>(77); |
| } |
| ModuleReference module = new ModuleReferenceImpl(vm, id); |
| modulesByID.put(id, module); |
| return module; |
| } |
| |
| ModuleReference getModule(long id) { |
| if (id == 0) { |
| return null; |
| } else { |
| ModuleReference module = null; |
| synchronized (this) { |
| if (modulesByID != null) { |
| module = modulesByID.get(id); |
| } |
| if (module == null) { |
| module = addModule(id); |
| } |
| } |
| return module; |
| } |
| } |
| |
| private synchronized List<ModuleReference> retrieveAllModules() { |
| ModuleReferenceImpl[] reqModules; |
| try { |
| reqModules = JDWP.VirtualMachine.AllModules.process(vm).modules; |
| } catch (JDWPException exc) { |
| throw exc.toJDIException(); |
| } |
| ArrayList<ModuleReference> modules = new ArrayList<>(); |
| for (int i = 0; i < reqModules.length; i++) { |
| long moduleRef = reqModules[i].ref(); |
| ModuleReference module = getModule(moduleRef); |
| modules.add(module); |
| } |
| return modules; |
| } |
| |
| private List<ReferenceType> retrieveClassesBySignature(String signature) { |
| if ((vm.traceFlags & VirtualMachine.TRACE_REFTYPES) != 0) { |
| vm.printTrace("Retrieving matching ReferenceTypes, sig=" + signature); |
| } |
| JDWP.VirtualMachine.ClassesBySignature.ClassInfo[] cinfos; |
| try { |
| cinfos = JDWP.VirtualMachine.ClassesBySignature. |
| process(vm, signature).classes; |
| } catch (JDWPException exc) { |
| throw exc.toJDIException(); |
| } |
| |
| int count = cinfos.length; |
| List<ReferenceType> list = new ArrayList<ReferenceType>(count); |
| |
| // Hold lock during processing to improve performance |
| synchronized (this) { |
| for (int i = 0; i < count; i++) { |
| JDWP.VirtualMachine.ClassesBySignature.ClassInfo ci = |
| cinfos[i]; |
| ReferenceTypeImpl type = referenceType(ci.typeID, |
| ci.refTypeTag, |
| signature); |
| type.setStatus(ci.status); |
| list.add(type); |
| } |
| } |
| return list; |
| } |
| |
| private void retrieveAllClasses1_4() { |
| JDWP.VirtualMachine.AllClasses.ClassInfo[] cinfos; |
| try { |
| cinfos = JDWP.VirtualMachine.AllClasses.process(vm).classes; |
| } catch (JDWPException exc) { |
| throw exc.toJDIException(); |
| } |
| |
| // Hold lock during processing to improve performance |
| // and to have safe check/set of retrievedAllTypes |
| synchronized (this) { |
| if (!retrievedAllTypes) { |
| // Number of classes |
| int count = cinfos.length; |
| for (int i=0; i<count; i++) { |
| JDWP.VirtualMachine.AllClasses.ClassInfo ci = |
| cinfos[i]; |
| ReferenceTypeImpl type = referenceType(ci.typeID, |
| ci.refTypeTag, |
| ci.signature); |
| type.setStatus(ci.status); |
| } |
| retrievedAllTypes = true; |
| } |
| } |
| } |
| |
| private void retrieveAllClasses() { |
| if ((vm.traceFlags & VirtualMachine.TRACE_REFTYPES) != 0) { |
| vm.printTrace("Retrieving all ReferenceTypes"); |
| } |
| |
| if (!vm.canGet1_5LanguageFeatures()) { |
| retrieveAllClasses1_4(); |
| return; |
| } |
| |
| /* |
| * To save time (assuming the caller will be |
| * using then) we will get the generic sigs too. |
| */ |
| |
| JDWP.VirtualMachine.AllClassesWithGeneric.ClassInfo[] cinfos; |
| try { |
| cinfos = JDWP.VirtualMachine.AllClassesWithGeneric.process(vm).classes; |
| } catch (JDWPException exc) { |
| throw exc.toJDIException(); |
| } |
| |
| // Hold lock during processing to improve performance |
| // and to have safe check/set of retrievedAllTypes |
| synchronized (this) { |
| if (!retrievedAllTypes) { |
| // Number of classes |
| int count = cinfos.length; |
| for (int i=0; i<count; i++) { |
| JDWP.VirtualMachine.AllClassesWithGeneric.ClassInfo ci = |
| cinfos[i]; |
| ReferenceTypeImpl type = referenceType(ci.typeID, |
| ci.refTypeTag, |
| ci.signature); |
| type.setGenericSignature(ci.genericSignature); |
| type.setStatus(ci.status); |
| } |
| retrievedAllTypes = true; |
| } |
| } |
| } |
| |
| void sendToTarget(Packet packet) { |
| target.send(packet); |
| } |
| |
| void waitForTargetReply(Packet packet) { |
| target.waitForReply(packet); |
| /* |
| * If any object disposes have been batched up, send them now. |
| */ |
| processBatchedDisposes(); |
| } |
| |
| Type findBootType(String signature) throws ClassNotLoadedException { |
| List<ReferenceType> types = retrieveClassesBySignature(signature); |
| Iterator<ReferenceType> iter = types.iterator(); |
| while (iter.hasNext()) { |
| ReferenceType type = iter.next(); |
| if (type.classLoader() == null) { |
| return type; |
| } |
| } |
| JNITypeParser parser = new JNITypeParser(signature); |
| throw new ClassNotLoadedException(parser.typeName(), |
| "Type " + parser.typeName() + " not loaded"); |
| } |
| |
| BooleanType theBooleanType() { |
| if (theBooleanType == null) { |
| synchronized(this) { |
| if (theBooleanType == null) { |
| theBooleanType = new BooleanTypeImpl(this); |
| } |
| } |
| } |
| return theBooleanType; |
| } |
| |
| ByteType theByteType() { |
| if (theByteType == null) { |
| synchronized(this) { |
| if (theByteType == null) { |
| theByteType = new ByteTypeImpl(this); |
| } |
| } |
| } |
| return theByteType; |
| } |
| |
| CharType theCharType() { |
| if (theCharType == null) { |
| synchronized(this) { |
| if (theCharType == null) { |
| theCharType = new CharTypeImpl(this); |
| } |
| } |
| } |
| return theCharType; |
| } |
| |
| ShortType theShortType() { |
| if (theShortType == null) { |
| synchronized(this) { |
| if (theShortType == null) { |
| theShortType = new ShortTypeImpl(this); |
| } |
| } |
| } |
| return theShortType; |
| } |
| |
| IntegerType theIntegerType() { |
| if (theIntegerType == null) { |
| synchronized(this) { |
| if (theIntegerType == null) { |
| theIntegerType = new IntegerTypeImpl(this); |
| } |
| } |
| } |
| return theIntegerType; |
| } |
| |
| LongType theLongType() { |
| if (theLongType == null) { |
| synchronized(this) { |
| if (theLongType == null) { |
| theLongType = new LongTypeImpl(this); |
| } |
| } |
| } |
| return theLongType; |
| } |
| |
| FloatType theFloatType() { |
| if (theFloatType == null) { |
| synchronized(this) { |
| if (theFloatType == null) { |
| theFloatType = new FloatTypeImpl(this); |
| } |
| } |
| } |
| return theFloatType; |
| } |
| |
| DoubleType theDoubleType() { |
| if (theDoubleType == null) { |
| synchronized(this) { |
| if (theDoubleType == null) { |
| theDoubleType = new DoubleTypeImpl(this); |
| } |
| } |
| } |
| return theDoubleType; |
| } |
| |
| VoidType theVoidType() { |
| if (theVoidType == null) { |
| synchronized(this) { |
| if (theVoidType == null) { |
| theVoidType = new VoidTypeImpl(this); |
| } |
| } |
| } |
| return theVoidType; |
| } |
| |
| PrimitiveType primitiveTypeMirror(byte tag) { |
| switch (tag) { |
| case JDWP.Tag.BOOLEAN: |
| return theBooleanType(); |
| case JDWP.Tag.BYTE: |
| return theByteType(); |
| case JDWP.Tag.CHAR: |
| return theCharType(); |
| case JDWP.Tag.SHORT: |
| return theShortType(); |
| case JDWP.Tag.INT: |
| return theIntegerType(); |
| case JDWP.Tag.LONG: |
| return theLongType(); |
| case JDWP.Tag.FLOAT: |
| return theFloatType(); |
| case JDWP.Tag.DOUBLE: |
| return theDoubleType(); |
| default: |
| throw new IllegalArgumentException("Unrecognized primitive tag " + tag); |
| } |
| } |
| |
| private void processBatchedDisposes() { |
| if (shutdown) { |
| return; |
| } |
| |
| JDWP.VirtualMachine.DisposeObjects.Request[] requests = null; |
| synchronized(batchedDisposeRequests) { |
| int size = batchedDisposeRequests.size(); |
| if (size >= DISPOSE_THRESHOLD) { |
| if ((traceFlags & TRACE_OBJREFS) != 0) { |
| printTrace("Dispose threashold reached. Will dispose " |
| + size + " object references..."); |
| } |
| requests = new JDWP.VirtualMachine.DisposeObjects.Request[size]; |
| for (int i = 0; i < requests.length; i++) { |
| SoftObjectReference ref = batchedDisposeRequests.get(i); |
| if ((traceFlags & TRACE_OBJREFS) != 0) { |
| printTrace("Disposing object " + ref.key().longValue() + |
| " (ref count = " + ref.count() + ")"); |
| } |
| |
| // This is kludgy. We temporarily re-create an object |
| // reference so that we can correctly pass its id to the |
| // JDWP command. |
| requests[i] = |
| new JDWP.VirtualMachine.DisposeObjects.Request( |
| new ObjectReferenceImpl(this, ref.key().longValue()), |
| ref.count()); |
| } |
| batchedDisposeRequests.clear(); |
| } |
| } |
| if (requests != null) { |
| try { |
| JDWP.VirtualMachine.DisposeObjects.process(vm, requests); |
| } catch (JDWPException exc) { |
| throw exc.toJDIException(); |
| } |
| } |
| } |
| |
| private void batchForDispose(SoftObjectReference ref) { |
| if ((traceFlags & TRACE_OBJREFS) != 0) { |
| printTrace("Batching object " + ref.key().longValue() + |
| " for dispose (ref count = " + ref.count() + ")"); |
| } |
| batchedDisposeRequests.add(ref); |
| } |
| |
| private void processQueue() { |
| Reference<?> ref; |
| //if ((traceFlags & TRACE_OBJREFS) != 0) { |
| // printTrace("Checking for softly reachable objects"); |
| //} |
| while ((ref = referenceQueue.poll()) != null) { |
| SoftObjectReference softRef = (SoftObjectReference)ref; |
| removeObjectMirror(softRef); |
| batchForDispose(softRef); |
| } |
| } |
| |
| synchronized ObjectReferenceImpl objectMirror(long id, int tag) { |
| |
| // Handle any queue elements that are not strongly reachable |
| processQueue(); |
| |
| if (id == 0) { |
| return null; |
| } |
| ObjectReferenceImpl object = null; |
| Long key = id; |
| |
| /* |
| * Attempt to retrieve an existing object reference |
| */ |
| SoftObjectReference ref = objectsByID.get(key); |
| if (ref != null) { |
| object = ref.object(); |
| } |
| |
| /* |
| * If the object wasn't in the table, or it's soft reference was |
| * cleared, create a new instance. |
| */ |
| if (object == null) { |
| switch (tag) { |
| case JDWP.Tag.OBJECT: |
| object = new ObjectReferenceImpl(vm, id); |
| break; |
| case JDWP.Tag.STRING: |
| object = new StringReferenceImpl(vm, id); |
| break; |
| case JDWP.Tag.ARRAY: |
| object = new ArrayReferenceImpl(vm, id); |
| break; |
| case JDWP.Tag.THREAD: |
| ThreadReferenceImpl thread = |
| new ThreadReferenceImpl(vm, id); |
| thread.addListener(this); |
| object = thread; |
| break; |
| case JDWP.Tag.THREAD_GROUP: |
| object = new ThreadGroupReferenceImpl(vm, id); |
| break; |
| case JDWP.Tag.CLASS_LOADER: |
| object = new ClassLoaderReferenceImpl(vm, id); |
| break; |
| case JDWP.Tag.CLASS_OBJECT: |
| object = new ClassObjectReferenceImpl(vm, id); |
| break; |
| default: |
| throw new IllegalArgumentException("Invalid object tag: " + tag); |
| } |
| ref = new SoftObjectReference(key, object, referenceQueue); |
| |
| /* |
| * If there was no previous entry in the table, we add one here |
| * If the previous entry was cleared, we replace it here. |
| */ |
| objectsByID.put(key, ref); |
| if ((traceFlags & TRACE_OBJREFS) != 0) { |
| printTrace("Creating new " + |
| object.getClass().getName() + " (id = " + id + ")"); |
| } |
| } else { |
| ref.incrementCount(); |
| } |
| |
| return object; |
| } |
| |
| synchronized void removeObjectMirror(ObjectReferenceImpl object) { |
| |
| // Handle any queue elements that are not strongly reachable |
| processQueue(); |
| |
| SoftObjectReference ref = objectsByID.remove(object.ref()); |
| if (ref != null) { |
| batchForDispose(ref); |
| } else { |
| /* |
| * If there's a live ObjectReference about, it better be part |
| * of the cache. |
| */ |
| throw new InternalException("ObjectReference " + object.ref() + |
| " not found in object cache"); |
| } |
| } |
| |
| synchronized void removeObjectMirror(SoftObjectReference ref) { |
| /* |
| * This will remove the soft reference if it has not been |
| * replaced in the cache. |
| */ |
| objectsByID.remove(ref.key()); |
| } |
| |
| ObjectReferenceImpl objectMirror(long id) { |
| return objectMirror(id, JDWP.Tag.OBJECT); |
| } |
| |
| StringReferenceImpl stringMirror(long id) { |
| return (StringReferenceImpl)objectMirror(id, JDWP.Tag.STRING); |
| } |
| |
| ArrayReferenceImpl arrayMirror(long id) { |
| return (ArrayReferenceImpl)objectMirror(id, JDWP.Tag.ARRAY); |
| } |
| |
| ThreadReferenceImpl threadMirror(long id) { |
| return (ThreadReferenceImpl)objectMirror(id, JDWP.Tag.THREAD); |
| } |
| |
| ThreadGroupReferenceImpl threadGroupMirror(long id) { |
| return (ThreadGroupReferenceImpl)objectMirror(id, |
| JDWP.Tag.THREAD_GROUP); |
| } |
| |
| ClassLoaderReferenceImpl classLoaderMirror(long id) { |
| return (ClassLoaderReferenceImpl)objectMirror(id, |
| JDWP.Tag.CLASS_LOADER); |
| } |
| |
| ClassObjectReferenceImpl classObjectMirror(long id) { |
| return (ClassObjectReferenceImpl)objectMirror(id, |
| JDWP.Tag.CLASS_OBJECT); |
| } |
| |
| ModuleReferenceImpl moduleMirror(long id) { |
| return (ModuleReferenceImpl)getModule(id); |
| } |
| |
| /* |
| * Implementation of PathSearchingVirtualMachine |
| */ |
| private JDWP.VirtualMachine.ClassPaths getClasspath() { |
| if (pathInfo == null) { |
| try { |
| pathInfo = JDWP.VirtualMachine.ClassPaths.process(vm); |
| } catch (JDWPException exc) { |
| throw exc.toJDIException(); |
| } |
| } |
| return pathInfo; |
| } |
| |
| public List<String> classPath() { |
| return Arrays.asList(getClasspath().classpaths); |
| } |
| |
| public List<String> bootClassPath() { |
| return Collections.emptyList(); |
| } |
| |
| public String baseDirectory() { |
| return getClasspath().baseDir; |
| } |
| |
| public void setDefaultStratum(String stratum) { |
| defaultStratum = stratum; |
| if (stratum == null) { |
| stratum = ""; |
| } |
| try { |
| JDWP.VirtualMachine.SetDefaultStratum.process(vm, |
| stratum); |
| } catch (JDWPException exc) { |
| throw exc.toJDIException(); |
| } |
| } |
| |
| public String getDefaultStratum() { |
| return defaultStratum; |
| } |
| |
| ThreadGroup threadGroupForJDI() { |
| return threadGroupForJDI; |
| } |
| |
| static private class SoftObjectReference extends SoftReference<ObjectReferenceImpl> { |
| int count; |
| Long key; |
| |
| SoftObjectReference(Long key, ObjectReferenceImpl mirror, |
| ReferenceQueue<ObjectReferenceImpl> queue) { |
| super(mirror, queue); |
| this.count = 1; |
| this.key = key; |
| } |
| |
| int count() { |
| return count; |
| } |
| |
| void incrementCount() { |
| count++; |
| } |
| |
| Long key() { |
| return key; |
| } |
| |
| ObjectReferenceImpl object() { |
| return get(); |
| } |
| } |
| } |