This is the mail archive of the
java-patches@gcc.gnu.org
mailing list for the Java project.
Re: [RFA/JDWP] VirtualMachineCommandSet.java
On Tue, 2005-07-12 at 16:03 -0400, Bryce McKinlay wrote:
> Aaron Luchko wrote:
>
> >Ok this is a touch more complicated but not too bad, there's also a
> >couple possibly minor issues. This implements the VirtualMachine
> >CommandSet
> >http://java.sun.com/j2se/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_VirtualMachine
> >
> >The VERSION command is the first one that is a bit of an interpretation
> >http://java.sun.com/j2se/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_VirtualMachine_Version
> >All the fields are pretty direct except for the first one.
> >"description: Text information on the VM version"
> >Sun along with IBM and BEA (they all actually return the exact same
> >response for this command) return
> >"Java Debug Wire Protocol (Reference Implementation) version 1.4?JVM
> >Debug Interface version 1.3?JVM version 1.4.2_06 (Java HotSpot(TM)
> >Client VM' mixed mode)"
> >
> >
> IBM and BEA claim to be HotSpot(TM)? That is odd - sounds like a bug in
> Sun's JDWP implementation if so.
Yeah, I figured they probably just hard-coded the string in there :)
>
> >Which seems pretty generic/non-standard so I just did
> >
> >Properties props = System.getProperties();
> >
> >// The description field is pretty loosely defined
> >String description = "JVM version " + props.getProperty("java.vm.name")
> > + " " + props.getProperty("java.vm.version") + " "
> > + props.getProperty("java.version");
> >
> >which for my rpm installed gcj gave,
> >"JVM version GNU libgcj 4.0.0 20050622 (Red Hat 4.0.0-13) 1.4.2"
> >
> >
>
> Looks good, but should the JDWP version be incorporated into this string
> as well?
Why not, I'm guessing this is just meant to be displayed to the user by
some debuggers.
Changed description to
"JDWP version 1.4, JVM version GNU libgcj 4.0.0 20050622 (Red Hat
4.0.0-13) 1.4.2"
>
> >The next issue, the commands
> >AllThreads and TopLevelThreadGroups
> >http://java.sun.com/j2se/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_VirtualMachine_Version
> >
> >Rely on two things:
> >1. There can only be 1 top level thread group.
> >2. The aforesaid top level group can be attained via recursively going
> >group.getParent().
> >
> >If those assumptions are incorrect then things could get more
> >complicated.
> >
> >
>
> It is correct that there can only be 1 top level threadgroup for user
> code (since a ThreadGroup cannot be created without a parent), but
> perhaps VMs could implement hidden threadgroups for system code? The
> name of the command, "TopLevelThreadGroups" seems to suggest this.
Not sure. I ran a little debugger I wrote against ibm-jvm with a
HelloWorld app and it only reported a single thread group.
Another concern along this line I have is whether we should return our
jdwp threads to the debugger. I implemented executeAllThreads to filter
them out but the spec is vague.
"The returned list contains threads created through java.lang.Thread,
all native threads attached to the target VM through JNI, and system
threads created by the target VM"
I tried allThreads against ibm and it reported 3 threads for the same
HelloWorld app. What these threads are I'm unsure.
> >There's also the theoretical concern if these are true of a user run
> >program being able to find our Jdwp threads via the same way I do
> >executeAllTheads().
> >
> >
>
> Access to the thread lists is a secure operation - untrusted code will
> not be able to access these threads if the AccessController's security
> policy doesn't allow it. Likewise, it would be very silly for an
> application to rely on only certain threads existing, VMs make threads
> to do things "behind the back" of the application all the time.
cool
> >release/holdEvents
> >http://java.sun.com/j2se/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_VirtualMachine_HoldEvents
> >should be fine being left on the back burner until later.
> >
> >
> OK. It looks like to implement this, you'll need to be able to queue
> outgoing packets after all.
>
> >2005-07-12 Aaron Luchko <aluchko@redhat.com>
> >
> > * gnu/classpath/jdwp/processor/VirtualMachineCommandSet.java:
> > New file.
> >
> >
>
> You could add a brief description of what the file does to the ChangeLog
> entry, if you like.
>
> >+ // The description field is pretty loosely defined
> >+ String description = "JVM version " + props.getProperty("java.vm.name")
> >+ + " " + props.getProperty("java.vm.version") + " "
> >+ + props.getProperty("java.version");
> >+ int jdwpMajor = 1; // Get from jvm?
> >+ int jdwpMinor = 5; // Get from jvm?
> >
> >
> Should this version be 1.4? Also, since this is the implementation, the
> JDWP code itself probably know better than anywhere else what JDWP
> version is implemented.
The // Get from jvm? comment is a bit of a red herring from an old
version I should of removed. I'll toss them in JdwpConstants. The reason
I did 1.5 instead of 1.4 is I've been working from the 1.5 spec and
implementing the 1.5 commands where possible though come to think of it
it probably makes more sense to claim 1.4 in case some debuggers mistake
a 1.5 jdwp for a 1.5 java.
>
> >+ String vmVersion = props.getProperty("java.version"); // Get from jvm?
> >
> >
> The java.version property comes from the VM, at least in libgcj, so it
> should be considered the canonical source.
Oops, another obsolete comment, the spec pretty much demands we use that
"Target VM JRE version, as in the java.version property" :)
>
> >+ private void executeClassesBySignature(ByteBuffer bb, DataOutputStream os)
> >+ throws JdwpException, IOException
> >+ {
> >+ String sig = JdwpString.readString(bb);
> >+
> >+ ArrayList allLoadedClasses, allMatchingClasses;
> >+
> >+ // This will be a vector of all loaded Classes
> >+ allLoadedClasses = vm.getAllLoadedClasses();
> >
> >
>
> This is another example of something that could probably be implemented
> a lot more efficiently in the VM rather than in Java. The problem is
> that the VM will have to enumerate the list of all loaded classes into
> an array/ArrayList/etc, which means quite a bit of memory usage and copying.
>
> If it must be done in Java then it might make sense to have the vm
> interface return an Iterator rather than a List or ArrayList. This way,
> a VM which maintains its own internal class lists anyway could implement
> an iterator around them instead of having to make a copy.
I'd like to keep it in Java just for portability issues and reducing
calls to the VM. As to the Iterator I had to add an extra method,
getAllLoadedClassesCount() to the VM and disable the garbage collection
while making the two calls so we don't end up with fewer classes than we
can write. In the long run it might be better to return an Object which
pairs the count and the Iterator rather than shutting down garbage
collection but this seems better for the moment.
> >+ private void executeDispose(ByteBuffer bb, DataOutputStream os)
> >+ throws JdwpException
> >+ {
> >+ // TODO: resumeAllThreads isn't sufficient as a thread may have been
> >+ // suspended multiple times, we likely need a way to keep track of how many
> >+ // times a thread has been suspended or else a stronger resume method for
> >+ // this purpose
> >+ vm.resumeAllThreadsExcept(jdwp.getJdwpThreadGroup());
> >+
> >+ // Simply shutting down the jdwp layer will take care of the rest of the
> >+ // shutdown other than disabling debugging in the VM
> >+ vm.disableDebugging();
> >+ }
> >+
> >+ private void executeIDsizes(ByteBuffer bb, DataOutputStream os)
> >+ throws JdwpException, IOException
> >+ {
> >+ ObjectId oid = new ObjectId();
> >+ os.writeInt(oid.size()); // fieldId
> >+ os.writeInt(oid.size()); // methodId
> >+ os.writeInt(oid.size()); // objectId
> >+ os.writeInt(new ReferenceTypeId((byte) 0x00).size()); // referenceTypeId
> >+ os.writeInt(oid.size()); // frameId
> >+ }
> >+
> >+ private void executeSuspend(ByteBuffer bb, DataOutputStream os)
> >+ throws JdwpException
> >+ {
> >+ vm.suspendAllThreadsExcept(jdwp.getJdwpThreadGroup());
> >+ }
> >
> >
>
> Unfortunately, I think its going to be hard to make the JDWP code
> completely robust in the face of possible deadlocks caused by user code
> holding locks while suspended. For example, suppose that a user thread
> is in the middle of initializing some class that is also (directly or
> indirectly) used by JDWP. That thread will hold a lock on that class,
> and the JDWP will deadlock. We already try to minimize the number of
> user-visible locks used by libgcj in order to try and avoid the
> possibility of this, but when you allow thread suspension it becomes
> much more difficult. Whether this will be a real problem that is
> encountered in practice, I'm not sure.
Hrm, I'll keep that in the back of my mind, hopefully we'll think of a
nice solution.
>
> Again, these are just a few things to think about. Patch is OK to commit
> with the version ID and ChangeLog changes I mentioned.
Cool though from a discussion with Keith I've got a few more changes as
well. I've changed getRootThreadGroop into a loop (from recursion).
Removed the ThreadGroup hook from Jdwp (getting it from the current
thread instead), and threw a NotImplemented for executeDispose since
we'll need a better idea about where everything ends up wrt the vm
before we can do a clean shutdown.
thanks,
Aaron
ChangeLog
2005-07-12 Aaron Luchko <aluchko@redhat.com>
* gnu/classpath/jdwp/processor/VirtualMachineCommandSet.java:
Implemented VirtualMachine Command Set.
--- /dev/null 2005-06-09 16:29:11.371620296 -0400
+++ gnu/classpath/jdwp/processor/VirtualMachineCommandSet.java 2005-07-12 19:33:51.000000000 -0400
@@ -0,0 +1,473 @@
+/* VirtualMachineCommandSet.java -- class to implement the VirtualMachine
+ Command Set
+ Copyright (C) 2005 Free Software Foundation
+
+ This file is part of GNU Classpath.
+
+ GNU Classpath is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ GNU Classpath 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 for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with GNU Classpath; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ 02111-1307 USA.
+
+ Linking this library statically or dynamically with other modules is
+ making a combined work based on this library. Thus, the terms and
+ conditions of the GNU General Public License cover the whole
+ combination.
+
+ As a special exception, the copyright holders of this library give you
+ permission to link this library with independent modules to produce an
+ executable, regardless of the license terms of these independent
+ modules, and to copy and distribute the resulting executable under
+ terms of your choice, provided that you also meet, for each linked
+ terms of your choice, provided that you also meet, for each linked
+ independent module, the terms and conditions of the license of that
+ module. An independent module is a module which is not derived from
+ or based on this library. If you modify this library, you may extend
+ this exception to your version of the library, but you are not
+ obligated to do so. If you do not wish to do so, delete this
+ exception statement from your version. */
+
+
+package gnu.classpath.jdwp.processor;
+
+import gnu.classpath.jdwp.IVirtualMachine;
+import gnu.classpath.jdwp.Jdwp;
+import gnu.classpath.jdwp.JdwpConstants;
+import gnu.classpath.jdwp.exception.JdwpException;
+import gnu.classpath.jdwp.exception.JdwpInternalErrorException;
+import gnu.classpath.jdwp.exception.NotImplementedException;
+import gnu.classpath.jdwp.id.IdManager;
+import gnu.classpath.jdwp.id.JdwpId;
+import gnu.classpath.jdwp.id.ObjectId;
+import gnu.classpath.jdwp.id.ReferenceTypeId;
+import gnu.classpath.jdwp.util.JdwpString;
+import gnu.classpath.jdwp.util.Signature;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Properties;
+
+/**
+ * A class representing the VirtualMachine Command Set.
+ *
+ * @author Aaron Luchko <aluchko@redhat.com>
+ */
+public class VirtualMachineCommandSet implements CommandSet
+{
+ // Our hook into the jvm
+ private final IVirtualMachine vm = Jdwp.getIVirtualMachine();
+
+ // Manages all the different ids that are assigned by jdwp
+ private final IdManager idMan = Jdwp.getIdManager();
+
+ // The Jdwp object
+ private final Jdwp jdwp = Jdwp.getDefault();
+
+ public boolean runCommand(ByteBuffer bb, DataOutputStream os, byte command)
+ throws JdwpException
+ {
+ boolean keepRunning = true;
+ try
+ {
+ switch (command)
+ {
+ case JdwpConstants.CommandSet.VirtualMachine.VERSION:
+ executeVersion(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.CLASSES_BY_SIGNATURE:
+ executeClassesBySignature(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.ALL_CLASSES:
+ executeAllClasses(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.ALL_THREADS:
+ executeAllThreads(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.TOP_LEVEL_THREAD_GROUPS:
+ executeTopLevelThreadGroups(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.IDSIZES:
+ executeIDsizes(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.DISPOSE:
+ keepRunning = false;
+ executeDispose(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.SUSPEND:
+ executeSuspend(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.RESUME:
+ executeResume(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.EXIT:
+ keepRunning = false;
+ executeExit(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.CREATE_STRING:
+ executeCreateString(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.CAPABILITIES:
+ executeCapabilities(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.CLASS_PATHS:
+ executeClassPaths(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.DISPOSE_OBJECTS:
+ executeDisposeObjects(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.HOLD_EVENTS:
+ executeHoldEvents(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.RELEASE_EVENTS:
+ executeReleaseEvents(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.CAPABILITIES_NEW:
+ executeCapabilitiesNew(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.REDEFINE_CLASSES:
+ executeRedefineClasses(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.SET_DEFAULT_STRATUM:
+ executeSetDefaultStratum(bb, os);
+ break;
+ case JdwpConstants.CommandSet.VirtualMachine.ALL_CLASSES_WITH_GENERIC:
+ executeAllClassesWithGeneric(bb, os);
+ break;
+
+ default:
+ break;
+ }
+ }
+ catch (IOException ex)
+ {
+ // The DataOutputStream we're using isn't talking to a socket at all
+ // So if we throw an IOException we're in serious trouble
+ throw new JdwpInternalErrorException(ex);
+ }
+ return keepRunning;
+ }
+
+ private void executeVersion(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException, IOException
+ {
+
+ Properties props = System.getProperties();
+
+ int jdwpMajor = JdwpConstants.Version.MAJOR;
+ int jdwpMinor = JdwpConstants.Version.MINOR;
+ // The description field is pretty loosely defined
+ String description = "JDWP version " + jdwpMajor + "." + jdwpMinor
+ + ", JVM version " + props.getProperty("java.vm.name")
+ + " " + props.getProperty("java.vm.version") + " "
+ + props.getProperty("java.version");
+ String vmVersion = props.getProperty("java.version");
+ String vmName = props.getProperty("java.vm.name");
+ JdwpString.writeString(os, description);
+ os.write(jdwpMajor);
+ os.write(jdwpMinor);
+ JdwpString.writeString(os, vmName);
+ JdwpString.writeString(os, vmVersion);
+ }
+
+ private void executeClassesBySignature(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException, IOException
+ {
+ String sig = JdwpString.readString(bb);
+ ArrayList allMatchingClasses = new ArrayList();
+
+ // This will be an Iterator over all loaded Classes
+ Iterator iter = vm.getAllLoadedClasses();
+
+ while (iter.hasNext())
+ {
+ Class clazz = (Class) iter.next();
+ String clazzSig = Signature.computeClassSignature(clazz);
+ if (clazzSig.equals(sig))
+ allMatchingClasses.add(clazz);
+ }
+
+ os.writeInt(allMatchingClasses.size());
+ for (int i = 0; i < allMatchingClasses.size(); i++)
+ {
+ Class clazz = (Class) allMatchingClasses.get(i);
+ ReferenceTypeId id = idMan.getReferenceTypeId(clazz);
+ id.writeTagged(os);
+ int status = vm.getStatus(clazz);
+ os.writeInt(status);
+ }
+ }
+
+ private void executeAllClasses(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException, IOException
+ {
+ // Disable garbage collection while we're collecting the info on loaded
+ // classes so we some classes don't get collected between the time we get
+ // the count and the time we get the list
+ vm.disableGarbageCollection();
+
+ int classCount = vm.getAllLoadedClassesCount();
+ os.writeInt(classCount);
+
+ // This will be an Iterator over all loaded Classes
+ Iterator iter = vm.getAllLoadedClasses();
+ vm.enableGarbageCollection();
+ int count = 0;
+
+ // Note it's possible classes were created since out classCount so make
+ // sure we don't write more classes than we told the debugger
+ while (iter.hasNext() && count++ < classCount)
+ {
+ Class clazz = (Class) iter.next();
+ ReferenceTypeId id = idMan.getReferenceTypeId(clazz);
+ id.writeTagged(os);
+ String sig = Signature.computeClassSignature(clazz);
+ JdwpString.writeString(os, sig);
+ int status = vm.getStatus(clazz);
+ os.writeInt(status);
+ }
+ }
+
+ private void executeAllThreads(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException, IOException
+ {
+ ThreadGroup jdwpGroup = Thread.currentThread().getThreadGroup();
+ ThreadGroup root = getRootThreadGroup(jdwpGroup);
+
+ int numThreads = root.activeCount();
+ Thread allThreads[] = new Thread[numThreads];
+ root.enumerate(allThreads, true);
+
+ // We need to loop through for the true count since some threads may have
+ // been destroyed since we got
+ // activeCount so those spots in the array will be null. As well we must
+ // ignore any threads that belong to jdwp
+ numThreads = 0;
+ for (int i = 0; i < allThreads.length; i++)
+ {
+ Thread thread = allThreads[i];
+ if (thread == null)
+ break; // No threads after this point
+ if (!thread.getThreadGroup().equals(jdwpGroup))
+ numThreads++;
+ }
+
+ os.writeInt(numThreads);
+
+ for (int i = 0; i < allThreads.length; i++)
+ {
+ Thread thread = allThreads[i];
+ if (thread == null)
+ break; // No threads after this point
+ if (!thread.getThreadGroup().equals(jdwpGroup))
+ idMan.getId(thread).write(os);
+ }
+ }
+
+ private void executeTopLevelThreadGroups(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException, IOException
+ {
+ ThreadGroup jdwpGroup = jdwp.getJdwpThreadGroup();
+ ThreadGroup root = getRootThreadGroup(jdwpGroup);
+
+ os.writeInt(1); // Just one top level group allowed?
+ idMan.getId(root);
+ }
+
+ private void executeDispose(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException
+ {
+ // resumeAllThreads isn't sufficient as a thread may have been
+ // suspended multiple times, we likely need a way to keep track of how many
+ // times a thread has been suspended or else a stronger resume method for
+ // this purpose
+ // vm.resumeAllThreadsExcept(jdwp.getJdwpThreadGroup());
+
+ // Simply shutting down the jdwp layer will take care of the rest of the
+ // shutdown other than disabling debugging in the VM
+ // vm.disableDebugging();
+
+ // Don't implement this until we're sure how to remove all the debugging
+ // effects from the VM.
+ throw new NotImplementedException(
+ "Command VirtualMachine.Dispose not implemented");
+
+ }
+
+ private void executeIDsizes(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException, IOException
+ {
+ ObjectId oid = new ObjectId();
+ os.writeInt(oid.size()); // fieldId
+ os.writeInt(oid.size()); // methodId
+ os.writeInt(oid.size()); // objectId
+ os.writeInt(new ReferenceTypeId((byte) 0x00).size()); // referenceTypeId
+ os.writeInt(oid.size()); // frameId
+ }
+
+ private void executeSuspend(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException
+ {
+ vm.suspendAllThreadsExcept(jdwp.getJdwpThreadGroup());
+ }
+
+ private void executeResume(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException
+ {
+ vm.resumeAllThreadsExcept(jdwp.getJdwpThreadGroup());
+ }
+
+ private void executeExit(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException, IOException
+ {
+ int exitCode = bb.getInt();
+ jdwp.setExit(exitCode);
+ }
+
+ private void executeCreateString(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException, IOException
+ {
+ String string = JdwpString.readString(bb);
+ String internString = string.intern();
+ JdwpId stringId = Jdwp.getIdManager().getId(internString);
+ stringId.write(os);
+ }
+
+ private void executeCapabilities(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException, IOException
+ {
+ // Store these somewhere?
+ os.writeBoolean(false); // canWatchFieldModification
+ os.writeBoolean(false); // canWatchFieldAccess
+ os.writeBoolean(false); // canGetBytecodes
+ os.writeBoolean(false); // canGetSyntheticAttribute
+ os.writeBoolean(false); // canGetOwnedMonitorInfo
+ os.writeBoolean(false); // canGetCurrentContendedMonitor
+ os.writeBoolean(false); // canGetMonitorInfo
+ }
+
+ private void executeClassPaths(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException, IOException
+ {
+ String baseDir = System.getProperty("user.dir");
+ JdwpString.writeString(os, baseDir);
+
+ // Find and write the classpath
+ String classPath = System.getProperty("java.class.path");
+ String[] paths = classPath.split(":");
+
+ os.writeInt(paths.length);
+ for (int i = 0; i < paths.length; i++)
+ JdwpString.writeString(os, paths[i]);
+
+ // Now the bootpath
+ String bootPath = System.getProperty("sun.boot.class.path");
+ paths = bootPath.split(":");
+ os.writeInt(paths.length);
+ for (int i = 0; i < paths.length; i++)
+ JdwpString.writeString(os, paths[i]);
+ }
+
+ private void executeDisposeObjects(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException
+ {
+ // Instead of going through the list of objects they give us it's probably
+ // better just to find the garbage collected objects ourselves
+ idMan.update();
+ }
+
+ private void executeHoldEvents(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException
+ {
+ // Going to have to implement a send queue somewhere and do this without
+ // triggering events
+ // Until then just don't implement
+ throw new NotImplementedException(
+ "Command VirtualMachine.HoldEvents not implemented");
+ }
+
+ // Opposite of executeHoldEvents
+ private void executeReleaseEvents(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException
+ {
+ throw new NotImplementedException(
+ "Command VirtualMachine.ReleaseEvents not implemented");
+ }
+
+ private void executeCapabilitiesNew(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException, IOException
+ {
+ // Store these somewhere?
+ final int CAPABILITIES_NEW_SIZE = 32;
+ os.writeBoolean(false); // canWatchFieldModification
+ os.writeBoolean(false); // canWatchFieldAccess
+ os.writeBoolean(false); // canGetBytecodes
+ os.writeBoolean(false); // canGetSyntheticAttribute
+ os.writeBoolean(false); // canGetOwnedMonitorInfo
+ os.writeBoolean(false); // canGetCurrentContendedMonitor
+ os.writeBoolean(false); // canGetMonitorInfo
+ os.writeBoolean(false); // canRedefineClasses
+ os.writeBoolean(false); // canAddMethod
+ os.writeBoolean(false); // canUnrestrictedlyRedefineClasses
+ os.writeBoolean(false); // canPopFrames
+ os.writeBoolean(false); // canUseInstanceFilters
+ os.writeBoolean(false); // canGetSourceDebugExtension
+ os.writeBoolean(false); // canRequestVMDeathEvent
+ os.writeBoolean(false); // canSetDefaultStratum
+ for (int i = 15; i < CAPABILITIES_NEW_SIZE; i++)
+ // Future capabilities
+ // currently unused
+ os.writeBoolean(false); // Set to false
+ }
+
+ private void executeRedefineClasses(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException
+ {
+ // Optional command, don't implement
+ throw new NotImplementedException(
+ "Command VirtualMachine.RedefineClasses not implemented");
+ }
+
+ private void executeSetDefaultStratum(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException
+ {
+ // Optional command, don't implement
+ throw new NotImplementedException(
+ "Command VirtualMachine.SetDefaultStratum not implemented");
+ }
+
+ private void executeAllClassesWithGeneric(ByteBuffer bb, DataOutputStream os)
+ throws JdwpException
+ {
+ // We don't handle generics
+ throw new NotImplementedException(
+ "Command VirtualMachine.AllClassesWithGeneric not implemented");
+ }
+
+ /**
+ * Find the root ThreadGroup of this ThreadGroup
+ */
+ private ThreadGroup getRootThreadGroup(ThreadGroup group)
+ {
+ ThreadGroup parent = group.getParent();
+
+ while (parent != null)
+ {
+ group = parent;
+ parent = group.getParent();
+ }
+ return group; // This group was the root
+ }
+}