/* * Copyright (c) 2012, 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. * * 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.max.vm.ext.jvmti; import static com.sun.max.vm.ext.jvmti.JVMTIConstants.*; import static com.sun.max.vm.intrinsics.Infopoints.*; import static com.sun.max.vm.intrinsics.MaxineIntrinsicIDs.*; import static com.sun.max.vm.runtime.VMRegister.*; import java.lang.reflect.*; import java.util.*; import com.sun.cri.ci.*; import com.sun.max.annotate.*; import com.sun.max.memory.*; import com.sun.max.program.*; import com.sun.max.unsafe.*; import com.sun.max.vm.*; import com.sun.max.vm.actor.holder.*; import com.sun.max.vm.actor.member.*; import com.sun.max.vm.classfile.*; import com.sun.max.vm.compiler.target.*; import com.sun.max.vm.compiler.target.TargetMethod.FrameAccess; import com.sun.max.vm.ext.jvmti.JJVMTI.*; import com.sun.max.vm.ext.jvmti.JVMTIUtil.*; import com.sun.max.vm.jni.*; import com.sun.max.vm.run.java.*; import com.sun.max.vm.runtime.*; import com.sun.max.vm.stack.*; import com.sun.max.vm.thread.*; /** * Support for the JVMTI functions related to {@link Thread}. */ public class JVMTIThreadFunctions { private static ClassActor vmThreadClassActor; private static ClassActor jniFunctionsClassActor; private static ClassActor vmThreadMapClassActor; private static ClassActor methodClassActor; private static ClassActor jvmtiClassActor; private static ClassActor javaRunSchemeClassActor; private static MethodActor[] stackBaseMethodActors; private static final LinkedList<StackElement> EMPTY_STACK = new LinkedList<StackElement>(); static ClassActor vmThreadClassActor() { if (vmThreadClassActor == null) { vmThreadClassActor = ClassActor.fromJava(VmThread.class); } return vmThreadClassActor; } private static ClassActor vmThreadMapClassActor() { if (vmThreadMapClassActor == null) { vmThreadMapClassActor = ClassActor.fromJava(VmThreadMap.class); } return vmThreadMapClassActor; } private static ClassActor jniFunctionsClassActor() { if (jniFunctionsClassActor == null) { jniFunctionsClassActor = ClassActor.fromJava(JniFunctions.class); } return jniFunctionsClassActor; } static ClassActor methodClassActor() { if (methodClassActor == null) { methodClassActor = ClassActor.fromJava(Method.class); } return methodClassActor; } private static ClassActor jvmtiClassActor() { if (jvmtiClassActor == null) { jvmtiClassActor = ClassActor.fromJava(JVMTI.class); } return jvmtiClassActor; } private static ClassActor javaRunSchemeClassActor() { if (javaRunSchemeClassActor == null) { javaRunSchemeClassActor = ClassActor.fromJava(JavaRunScheme.class); } return javaRunSchemeClassActor; } static { // Should search for @JVMTI_STACKBASE stackBaseMethodActors = new MethodActor[1]; try { stackBaseMethodActors[0] = MethodActor.fromJava(JVMTIBreakpoints.class.getDeclaredMethod("event", long.class)); } catch (NoSuchMethodException ex) { ProgramError.unexpected("failed to find method actor for JVMTIBreakpoints.event", ex); } } static int getAllThreads(Pointer threadsCountPtr, Pointer threadsPtr) { final Thread[] threads = VmThreadMap.getThreads(true); threadsCountPtr.setInt(threads.length); Pointer threadCArray = Memory.allocate(Size.fromInt(threads.length * Word.size())); if (threadCArray.isZero()) { return JVMTI_ERROR_OUT_OF_MEMORY; } threadsPtr.setWord(threadCArray); for (int i = 0; i < threads.length; i++) { Thread thread = threads[i]; threadCArray.setWord(i, JniHandles.createLocalHandle(thread)); } return JVMTI_ERROR_NONE; } static int getThreadState(Thread thread, Pointer threadStatePtr) { VmThread vmThread = checkVmThread(thread); if (vmThread == null) { return JVMTI_ERROR_THREAD_NOT_ALIVE; } threadStatePtr.setInt(getThreadState(vmThread)); return JVMTI_ERROR_NONE; } static int getThreadState(VmThread vmThread) { // TODO Maxine does not keep any state beyond Thread.State so the detailed JVMTI states are unavailable int state = 0; if (vmThread != null) { switch (vmThread.state()) { case TERMINATED: state = JVMTI_THREAD_STATE_TERMINATED; break; case NEW: state = JVMTI_THREAD_STATE_ALIVE; break; case RUNNABLE: state = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_RUNNABLE; break; case BLOCKED: state = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER; break; case WAITING: state = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_WAITING | JVMTI_THREAD_STATE_WAITING_INDEFINITELY; break; case TIMED_WAITING: state = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_WAITING | JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT; } if (VmOperation.isSuspendRequest(vmThread.tla())) { state |= JVMTI_THREAD_STATE_SUSPENDED; } } return state; } static ThreadInfo getThreadInfo(Thread thread) { VmThread vmThread = checkVmThread(thread); if (vmThread == null) { throw new JJVMTI.JJVMTIException(JVMTI_ERROR_THREAD_NOT_ALIVE); } thread = vmThread.javaThread(); return new ThreadInfo(thread.getName(), thread.getPriority(), thread.isDaemon(), thread.getThreadGroup(), thread.getContextClassLoader()); } static int getThreadInfo(Thread thread, Pointer threadInfoPtr) { VmThread vmThread = checkVmThread(thread); if (vmThread == null) { return JVMTI_ERROR_THREAD_NOT_ALIVE; } setJVMTIThreadInfo(threadInfoPtr, CString.utf8FromJava(thread.getName()), thread.getPriority(), thread.isDaemon(), JniHandles.createLocalHandle(thread.getThreadGroup()), JniHandles.createLocalHandle(thread.getContextClassLoader())); return JVMTI_ERROR_NONE; } /** * Type too complex to handle in Java, delegate to native code. */ @C_FUNCTION private static native void setJVMTIThreadInfo(Pointer threadInfoPtr, Pointer name, int priority, boolean isDaemon, JniHandle threadGroup, JniHandle contextClassLoader); // Stack trace handling // Single thread stack trace static FrameInfo[] getStackTrace(Thread thread, int startDepth, int maxFrameCount) { VmThread vmThread = checkVmThread(thread); if (vmThread == null) { throw new JJVMTI.JJVMTIException(JVMTI_ERROR_THREAD_NOT_ALIVE); } FrameInfo[] frameInfo = new FrameInfo[maxFrameCount]; for (int i = 0; i < frameInfo.length; i++) { frameInfo[i] = new FrameInfo(); } JavaThreadListStackTraceVisitor stackTraceVisitor = new JavaThreadListStackTraceVisitor(vmThread, startDepth, maxFrameCount, frameInfo); SingleThreadStackTraceVmOperation.invoke(vmThread, stackTraceVisitor); if (stackTraceVisitor.frameBufferIndex < frameInfo.length) { FrameInfo[] newFrameInfo = new FrameInfo[stackTraceVisitor.frameBufferIndex]; System.arraycopy(frameInfo, 0, newFrameInfo, 0, stackTraceVisitor.frameBufferIndex); frameInfo = newFrameInfo; } return frameInfo; } static int getStackTrace(Thread thread, int startDepth, int maxFrameCount, Pointer frameBuffer, Pointer countPtr) { VmThread vmThread = checkVmThread(thread); if (vmThread == null) { return JVMTI_ERROR_THREAD_NOT_ALIVE; } NativeThreadListStackTraceVisitor stackTraceVisitor = new NativeThreadListStackTraceVisitor(vmThread, startDepth, maxFrameCount, frameBuffer); new SingleThreadStackTraceVmOperation(vmThread, stackTraceVisitor).submit(); countPtr.setInt(stackTraceVisitor.frameBufferIndex); return JVMTI_ERROR_NONE; } private static class FrameAccessWithIP extends FrameAccess { CodePointer ip; @NEVER_INLINE //TODO remove void setCallerInfo(StackFrameCursor callerCursor) { this.setCallerInfo(callerCursor.sp(), callerCursor.fp()); } FrameAccessWithIP(StackFrameCursor currentCursor) { super(currentCursor.csl(), currentCursor.csa(), currentCursor.sp(), currentCursor.fp(), Pointer.zero(), Pointer.zero()); if (currentCursor.ip.targetMethod() != null) { this.ip = currentCursor.vmIP(); } } } static class StackElement { final ClassMethodActor classMethodActor; final int bci; final FrameAccessWithIP frameAccess; /** * The index of this frame, with the base (earliest) frame being zero, and increasing by one up the stack. * This value is stable (unlike {@code frameId}) across different stack * walks, provided the method activation is still active. */ int frameIndex; StackElement(ClassMethodActor classMethodActor, int bci, FrameAccessWithIP frameAccess) { this.classMethodActor = classMethodActor; this.frameAccess = frameAccess; if (bci < 0) { // outside the actual bytecode area, e.g. in method entry count overflow code // make it look like a call from first instruction bci = 0; } this.bci = bci; } } /** * A stack visitor that analyses the stack, potentially removing * frames that are VM related. If {@link JVMTI#JVMTI_VM} is {@code true} * then all frames are gathered, otherwise we ignore the frames that are on the stack * because of the way {@link VMOperation} brings a thread to a safepoint, and * "implementation" frames, i.e., VM frames, reflection stubs. * * Since the decision to include VM frames is optional, we abstract this into the * notion of a "visible" frame. * * The nature of the mechanisms for freezing threads in {@link VMOperation} * and entering native code, means that there are always VM frames on the * stack that we do not want to include. In addition because JVMTI is * implemented in Java, any calls from agents in response to JVMTI events * and callbacks will also have stacks containing VM frames. Plus the * base of every thread stack has VM frames from the thread startup. * * The stack walker visits the stack top down, but an accurate picture * requires a bottom up analysis. So in a first top down pass we build a list * of {@link StackElement} which is an approximation, then re-analyse it * bottom up. The resulting list is then easy to use to answer all the * JVMTI query variants. The process isn't allocation free. * * In the initial scan downwards all VM frames are dropped until a non-VM frame * is seen, then all frames are kept (except reflection stubs). */ static class FindAppFramesStackTraceVisitor extends SourceFrameVisitor { boolean seenVisibleFrame = JVMTI.JVMTI_VM; LinkedList<StackElement> stackElements = new LinkedList<StackElement>(); StackElement[] stackElementsArray; // strictly for ease of debugging in the Inspector boolean raw; FrameAccessWithIP calleeFrameAccess; // allows access to the physical frame for a vframe even (when inlined) @Override public boolean visitFrame(StackFrameCursor current, StackFrameCursor callee) { // This is the physical frame visit // since we walk down, we don't know the caller yet, but update the callee caller info to this frame. if (calleeFrameAccess != null) { calleeFrameAccess.setCallerInfo(current); } calleeFrameAccess = new FrameAccessWithIP(current); return super.visitFrame(current, callee); } @Override public boolean visitSourceFrame(ClassMethodActor methodActor, int bci, boolean trapped, long frameId) { // "trapped" indicates the frame in the safepoint trap handler. // In other stack visitors in the VM this causes a reset but, // in this context, it is subsumed by the check for VM frames. ClassMethodActor classMethodActor = methodActor.original(); // check for first visible frame if (seenVisibleFrame) { add(classMethodActor, bci, frameId); } else { if (JVMTIClassFunctions.isVisibleClass(classMethodActor.holder())) { seenVisibleFrame = true; add(classMethodActor, bci, frameId); } } return true; } StackElement getStackElement(int depth) { assert depth < stackElements.size(); return stackElements.get((stackElements.size() - 1) - depth); } private void add(ClassMethodActor classMethodActor, int bci, long frameId) { if (JVMTI.JVMTI_VM || raw || !classMethodActor.holder().isReflectionStub()) { stackElements.addFirst(new StackElement(classMethodActor, bci, getFrameAccessWithIP())); } } protected FrameAccessWithIP getFrameAccessWithIP() { return calleeFrameAccess; } /** * Resets state if the visitor is being reused. */ void reset() { seenVisibleFrame = JVMTI.JVMTI_VM; if (stackElements.size() > 0) { stackElements = new LinkedList<StackElement>(); } } /** * Standard walk removes VM frames. */ @Override public void walk(StackFrameWalker walker, Pointer ip, Pointer sp, Pointer fp) { walkVariant(walker, ip, sp, fp, false); } /** * Raw walk that does not remove VM frames. */ void walkRaw(StackFrameWalker walker, Pointer ip, Pointer sp, Pointer fp) { walkVariant(walker, ip, sp, fp, true); } void walkVariant(StackFrameWalker walker, Pointer ip, Pointer sp, Pointer fp, boolean raw) { this.raw = raw; super.walk(walker, ip, sp, fp); createDebugArray(); if (!raw) { removeVMFrames(); } numberFrames(); } /** * Allows the index of the frame to be determined from the {@link StackElement}, without * rescanning the list. */ private void numberFrames() { for (int i = 0; i < stackElements.size(); i++) { // Checkstyle: stop stackElements.get(i).frameIndex = i; // Checkstyle: resume } } /** * Remove VM frames from the stack trace in {@link #stackElements}. * Analysing the stack to remove the VM frames depends on knowledge * of the way that threads start up. The normal case is {@link VmThread#run} * calls {@link VmThread#executeRunnable} which calls {@link Thread#run}. * The unusual case is an attached native thread which starts with * {@link JNIFunctions#CallStaticVoidMethodA}. A special case is the * main thread which has a {@link Method#invoke}, since it is is called from * {@link JavaRunScheme}, unless it has returned but the VM hasn't terminated, in which * case it is in {@link VmThreadMap#joinAllNonDaemons}. This analysis gets the * correct base frame. We then scan upwards. If we hit another VM frame we throw everything * away after that. Reason being that the initial down scan may have hit a platform * class method used by the VM, but couldn't know there might be another VM class below, * so decided it was an app frame. * * TODO handle user-defined classloader callbacks, which have VM frames sandwiched * between app frames. * * N.B. if we are including VM frames then the processing is different. */ void removeVMFrames() { if (stackElements.size() == 0) { // some threads, e.g., SignalDispatcher, have only VM frames, so (usually) nothing to analyse. return; } if (JVMTI.JVMTI_VM) { // The usual dance to handle VM frame removal at the base is not appropriate. // However, some of the frames at the top of the stack should be removed, // e.g., those delivering a breakpoint event. stripJVMTIFrames(); return; } int startIndex = -1; StackElement base = stackElements.getFirst(); ClassActor classActor = base.classMethodActor.holder(); if (classActor == vmThreadClassActor()) { base = stackElements.get(1); classActor = base.classMethodActor.holder(); if (classActor == vmThreadClassActor) { if (stackElements.get(2).classMethodActor.holder() == javaRunSchemeClassActor()) { startIndex = 5; } else { startIndex = 2; } } else if (classActor == vmThreadMapClassActor()) { // main returned stackElements = EMPTY_STACK; return; } else if (classActor == methodClassActor()) { startIndex = 3; } else if (classActor == jvmtiClassActor()) { startIndex = 2; } } else if (classActor == jniFunctionsClassActor()) { startIndex = 3; } if (startIndex < 0) { // unexpected stack layout, log it and return empty Log.println("JVMTI: unexpected thread stack layout"); stackElements = EMPTY_STACK; return; } // discard VM frames below first app frame for (int i = 0; i < startIndex; i++) { stackElements.remove(); } // now 0 is the first app frame ListIterator<StackElement> iter = stackElements.listIterator(); while (iter.hasNext()) { StackElement e = iter.next(); classActor = e.classMethodActor.holder(); if (JVMTIClassFunctions.isVMClass(classActor)) { iter.remove(); } } } /** * It is infinitely more convenient to see the stack elements in an array in the Inspector. */ private void createDebugArray() { stackElementsArray = new StackElement[stackElements.size()]; stackElements.toArray(stackElementsArray); } /** * When {@link JVMTI#JVMTI_VM} is {@code true} we strip out * frames that are part of the JVMTI implementation of, e.g., breakpoints. */ private void stripJVMTIFrames() { // index 0 is base of stack int index = 0; for (int i = 0; i < stackElements.size(); i++) { MethodActor methodActor = stackElements.get(i).classMethodActor; for (MethodActor stackBaseMethodActor : stackBaseMethodActors) { if (methodActor == stackBaseMethodActor) { index = i; break; } } } if (index > 0) { removeElements(index); } } private void removeElements(int index) { int lastIndex = stackElements.size(); for (int i = index; i < lastIndex; i++) { // this reduces the index of everything after removed item // so we always remove the same index. stackElements.remove(index); } } } /** * Visitor for copying portions of a stack to an agent provided buffer. * We do this as a visitor because it is used in single/multiple thread variants. */ private static abstract class ThreadListStackTraceVisitor extends FindAppFramesStackTraceVisitor { int startDepth; // first frame to record; > 0 => from top, < 0 from bottom int maxCount; // max number of frames to record int frameBufferIndex; // in range 0 .. maxCount - 1 VmThread vmThread; // thread associated with this stack ThreadListStackTraceVisitor(VmThread vmThread, int startDepth, int maxCount) { this.startDepth = startDepth; this.maxCount = maxCount; this.vmThread = vmThread; } @Override public void walk(StackFrameWalker walker, Pointer ip, Pointer sp, Pointer fp) { super.walk(walker, ip, sp, fp); if (startDepth < 0) { startDepth = stackElements.size() + startDepth; } for (int i = 0; i < stackElements.size(); i++) { if (i >= startDepth) { if (store(getStackElement(i))) { break; } } } } /** * Subclass-specific mechanism for storing information. * @param se */ protected abstract void subStore(StackElement se); protected boolean store(StackElement se) { if (frameBufferIndex >= maxCount) { return true; } else { subStore(se); frameBufferIndex++; return false; } } } private static class NativeThreadListStackTraceVisitor extends ThreadListStackTraceVisitor { Pointer frameBuffer; // C struct for storing info NativeThreadListStackTraceVisitor(VmThread vmThread, int startDepth, int maxCount, Pointer frameBuffer) { super(vmThread, startDepth, maxCount); this.frameBuffer = frameBuffer; } @Override protected void subStore(StackElement se) { setJVMTIFrameInfo(frameBuffer, frameBufferIndex, MethodID.fromMethodActor(se.classMethodActor), se.bci); } } private static class JavaThreadListStackTraceVisitor extends ThreadListStackTraceVisitor { FrameInfo[] frameInfo; JavaThreadListStackTraceVisitor(VmThread vmThread, int startDepth, int maxCount, FrameInfo[] frameInfo) { super(vmThread, startDepth, maxCount); this.frameInfo = frameInfo; } @Override protected void subStore(StackElement se) { FrameInfo fi = frameInfo[frameBufferIndex]; fi.method = se.classMethodActor; fi.location = se.bci; } } /** * Invokes a {@link FindAppFramesStackTraceVisitor} on a single thread in a {@link VmOperation}. * N.B. To workaround a limitation in {@link VmOperation} where a GC cannot be run during the * {@link VmOperation} unless all threads are stopped, we run this as a multi-thread operation * even though that is not strictly necessary. Typically everything is stopped anyway. * * The case where the current thread is making the request is handled specially as no * {@link VmOperation}is necessary, just the stack walk. */ static class SingleThreadStackTraceVmOperation extends VmOperation { FindAppFramesStackTraceVisitor stackTraceVisitor; VmThread vmThread; /** * Create a {@link VmOperation} that runs the given {@link BaseStackTraceVisitor} on the given thread. * @param vmThread * @param stackTraceVisitor */ private SingleThreadStackTraceVmOperation(VmThread vmThread, FindAppFramesStackTraceVisitor stackTraceVisitor) { super("JVMTISingleStackTrace", null, Mode.Safepoint); this.vmThread = vmThread; this.stackTraceVisitor = stackTraceVisitor; } @Override protected boolean operateOnThread(VmThread vmThread) { return vmThread == this.vmThread; } @Override public void doThread(VmThread vmThread, Pointer ip, Pointer sp, Pointer fp) { stackTraceVisitor.walk(new VmStackFrameWalker(vmThread.tla()), ip, sp, fp); } /** * The preferred way to invoke the operation, that encapsulates the special case of the current thread. * @param vmThread * @param stackTraceVisitor * @return {@code stackTraceVisitor} for convenience */ static FindAppFramesStackTraceVisitor invoke(VmThread vmThread, FindAppFramesStackTraceVisitor stackTraceVisitor) { if (vmThread == VmThread.current()) { stackTraceVisitor.walk(new VmStackFrameWalker(vmThread.tla()), Address.fromLong(here()).asPointer(), getAbiStackPointer(), getCpuFramePointer()); } else { new SingleThreadStackTraceVmOperation(vmThread, stackTraceVisitor).submit(); } return stackTraceVisitor; } static FindAppFramesStackTraceVisitor invoke(VmThread vmThread) { return SingleThreadStackTraceVmOperation.invoke(vmThread, new FindAppFramesStackTraceVisitor()); } } // Stack traces for all threads private static class StackTraceUnion extends ModeUnion { // native Pointer stackInfoArrayPtr; // java StackInfo[] stackInfoArray; StackTraceUnion(boolean isNative) { super(isNative); } } static StackInfo[] getAllStackTraces(int maxFrameCount) { return getThreadListStackTraces(VmThreadMap.getThreads(false), maxFrameCount); } static StackInfo[] getThreadListStackTraces(Thread[] threads, int maxFrameCount) { StackTraceUnion stu = new StackTraceUnion(ModeUnion.JAVA); int error = getThreadListStackTraces(threads, maxFrameCount, stu); if (error != JVMTI_ERROR_NONE) { throw new JJVMTI.JJVMTIException(error); } return stu.stackInfoArray; } static int getAllStackTraces(int maxFrameCount, Pointer stackInfoPtrPtr, Pointer threadCountPtr) { return getThreadListStackTraces(VmThreadMap.getThreads(false), maxFrameCount, stackInfoPtrPtr, threadCountPtr); } static int getThreadListStackTraces(int threadCount, Pointer threadList, int maxFrameCount, Pointer stackInfoPtrPtr) { Thread[] threads = new Thread[threadCount]; for (int i = 0; i < threadCount; i++) { try { Thread thread = (Thread) threadList.getWord(i).asJniHandle().unhand(); threads[i] = thread; } catch (ClassCastException ex) { return JVMTI_ERROR_INVALID_THREAD; } } return getThreadListStackTraces(threads, maxFrameCount, stackInfoPtrPtr, Pointer.zero()); } private static int getThreadListStackTraces(Thread[] threads, int maxFrameCount, Pointer stackInfoPtrPtr, Pointer threadCountPtr) { StackTraceUnion stu = new StackTraceUnion(ModeUnion.NATIVE); int error = getThreadListStackTraces(threads, maxFrameCount, stu); if (error == JVMTI_ERROR_NONE) { stackInfoPtrPtr.setWord(stu.stackInfoArrayPtr); if (!threadCountPtr.isZero()) { threadCountPtr.setInt(threads.length); } } return error; } private static int getThreadListStackTraces(Thread[] threads, int maxFrameCount, StackTraceUnion stu) { int threadCount = threads.length; Pointer frameBuffersBasePtr = Pointer.zero(); if (stu.isNative) { // Have to preallocate all the memory in one contiguous chunk stu.stackInfoArrayPtr = Memory.allocate(Size.fromInt(threadCount * (stackInfoSize() + maxFrameCount * FRAME_INFO_STRUCT_SIZE))); if (stu.stackInfoArrayPtr.isZero()) { return JVMTI_ERROR_OUT_OF_MEMORY; } frameBuffersBasePtr = stu.stackInfoArrayPtr.plus(threadCount * stackInfoSize()); } else { stu.stackInfoArray = new StackInfo[threadCount]; for (int i = 0; i < stu.stackInfoArray.length; i++) { stu.stackInfoArray[i] = new StackInfo(); FrameInfo[] frameInfo = new FrameInfo[maxFrameCount]; for (int j = 0; j < maxFrameCount; j++) { frameInfo[j] = new FrameInfo(); } stu.stackInfoArray[i].frameInfo = frameInfo; } } ThreadListStackTraceVisitor[] stackTraceVisitors = null; if (stu.isNative) { stackTraceVisitors = new NativeThreadListStackTraceVisitor[threadCount]; for (int i = 0; i < threadCount; i++) { stackTraceVisitors[i] = new NativeThreadListStackTraceVisitor(VmThread.fromJava(threads[i]), 0, maxFrameCount, frameBuffersBasePtr.plus(i * maxFrameCount * FRAME_INFO_STRUCT_SIZE)); } } else { stackTraceVisitors = new JavaThreadListStackTraceVisitor[threadCount]; for (int i = 0; i < threadCount; i++) { stackTraceVisitors[i] = new JavaThreadListStackTraceVisitor(VmThread.fromJava(threads[i]), 0, maxFrameCount, stu.stackInfoArray[i].frameInfo); } } new MultipleThreadStackTraceVmOperation(threads, stackTraceVisitors).submit(); for (int i = 0; i < threadCount; i++) { ThreadListStackTraceVisitor sv = stackTraceVisitors[i]; Thread thread = sv.vmThread.javaThread(); int state = getThreadState(sv.vmThread); if (stu.isNative) { NativeThreadListStackTraceVisitor nsv = (NativeThreadListStackTraceVisitor) sv; setJVMTIStackInfo(stu.stackInfoArrayPtr, i, JniHandles.createLocalHandle(thread), state, nsv.frameBuffer, nsv.frameBufferIndex); } else { StackInfo si = stu.stackInfoArray[i]; si.thread = thread; si.state = state; si.frameCount = sv.frameBufferIndex; } } return JVMTI_ERROR_NONE; } /** * Invokes a {@link FindAppFramesStackTraceVisitor} on multiple threads in a {@link VmOperation}. */ static class MultipleThreadStackTraceVmOperation extends VmOperation { FindAppFramesStackTraceVisitor[] stackTraceVisitors; Thread[] threads; MultipleThreadStackTraceVmOperation(Thread[] threads, FindAppFramesStackTraceVisitor[] stackTraceVisitors) { super("JVMTIMultipleStackTrace", null, Mode.Safepoint); this.threads = threads; this.stackTraceVisitors = stackTraceVisitors; } @Override protected boolean operateOnThread(VmThread vmThread) { for (int i = 0; i < threads.length; i++) { if (VmThread.fromJava(threads[i]) == vmThread) { return true; } } return false; } private FindAppFramesStackTraceVisitor getStackTraceVisitor(VmThread vmThread) { for (int i = 0; i < threads.length; i++) { if (VmThread.fromJava(threads[i]) == vmThread) { return stackTraceVisitors[i]; } } assert false; return null; } @Override public void doThread(VmThread vmThread, Pointer ip, Pointer sp, Pointer fp) { getStackTraceVisitor(vmThread).walk(new VmStackFrameWalker(vmThread.tla()), ip, sp, fp); } } private static final int FRAME_INFO_STRUCT_SIZE = Word.size() * 2; private static int stackInfoSize; private static int stackInfoSize() { if (stackInfoSize == 0) { stackInfoSize = getJVMTIStackInfoSize(); } return stackInfoSize; } @C_FUNCTION private static native int getJVMTIStackInfoSize(); @C_FUNCTION private static native void setJVMTIFrameInfo(Pointer frameInfo, int index, Word methodID, long location); @C_FUNCTION private static native void setJVMTIStackInfo(Pointer stackInfo, int index, Word threadHandle, int state, Pointer frameBuffer, int frameount); // Frame operations /** * Checks for current thread request ({@code thread == null}, and live state. * @return {@code null} if should return error, the {@link VmThread} otherwise. */ static VmThread checkVmThread(Thread thread) { if (thread == null) { return VmThread.current(); } VmThread vmThread = VmThread.fromJava(thread); if (vmThread == null || vmThread.state() == Thread.State.TERMINATED) { return null; } return vmThread; } /** * Checks for {@code null} which means current thread (which is alive). * else checks if alive. * @param thread * @return {@code null} if the thread is not alive, {@code thread} otherwise */ static Thread checkThread(Thread thread) { if (thread == null) { return VmThread.current().javaThread(); } if (!thread.isAlive()) { return null; } return thread; } static int getFrameCount(Thread thread, Pointer countPtr) { VmThread vmThread = checkVmThread(thread); if (vmThread == null) { return JVMTI_ERROR_THREAD_NOT_ALIVE; } FindAppFramesStackTraceVisitor stackTraceVisitor = SingleThreadStackTraceVmOperation.invoke(vmThread); countPtr.setInt(stackTraceVisitor.stackElements.size()); return JVMTI_ERROR_NONE; } private static class FrameLocationUnion { boolean isNative; } private static class MethodActorLocation { MethodActor methodActor; int location; } static JJVMTI.FrameInfo getFrameLocation(Thread thread, int depth) throws JJVMTI.JJVMTIException { MethodActorLocation methodActorLocation = new MethodActorLocation(); int error = getFrameLocation(methodActorLocation, thread, depth); if (error == JVMTI_ERROR_NONE) { return new FrameInfo(methodActorLocation.methodActor, methodActorLocation.location); } else { throw new JJVMTI.JJVMTIException(error); } } static int getFrameLocation(Thread thread, int depth, Pointer methodPtr, Pointer locationPtr) { MethodActorLocation methodActorLocation = new MethodActorLocation(); int error = getFrameLocation(methodActorLocation, thread, depth); if (error == JVMTI_ERROR_NONE) { methodPtr.setWord(MethodID.fromMethodActor(methodActorLocation.methodActor)); locationPtr.setLong(methodActorLocation.location); } return error; } static int getFrameLocation(MethodActorLocation methodActorLocation, Thread thread, int depth) { VmThread vmThread = checkVmThread(thread); if (vmThread == null) { return JVMTI_ERROR_THREAD_NOT_ALIVE; } if (depth < 0) { return JVMTI_ERROR_ILLEGAL_ARGUMENT; } FindAppFramesStackTraceVisitor stackTraceVisitor = SingleThreadStackTraceVmOperation.invoke(vmThread); if (depth < stackTraceVisitor.stackElements.size()) { methodActorLocation.methodActor = stackTraceVisitor.getStackElement(depth).classMethodActor; methodActorLocation.location = stackTraceVisitor.getStackElement(depth).bci; return JVMTI_ERROR_NONE; } else { return JVMTI_ERROR_NO_MORE_FRAMES; } } /** * Used to carry the frame pop data from here through the event dispatch machinery. */ static class FramePopEventData { MethodID methodID; boolean wasPoppedByException; Object value; } static class FramePopEventDataThreadLocal extends ThreadLocal<FramePopEventData> { @Override public FramePopEventData initialValue() { return new FramePopEventData(); } } /** * Records {@link #notifyFramePop} requests per thread. Several requests may be * in flight at once. They are recorded using the frame index, which is stable * in the face of deoptimization. */ private static final Map<VmThread, int[]> framePopMap = new HashMap<VmThread, int[]>(); private static FramePopEventDataThreadLocal framePopEventDataTL = new FramePopEventDataThreadLocal(); /** * Add a frame pop request. * N.B. {@code depth} measures from the top of the stack; we record the frame index of the * frame at that depth. * @param env * @param thread * @param depth * @return */ static int notifyFramePop(JVMTI.Env env, Thread thread, int depth) { VmThread vmThread = checkVmThread(thread); if (vmThread == null) { return JVMTI_ERROR_THREAD_NOT_ALIVE; } if (depth < 0) { return JVMTI_ERROR_ILLEGAL_ARGUMENT; } FindAppFramesStackTraceVisitor stackTraceVisitor = SingleThreadStackTraceVmOperation.invoke(vmThread); if (depth < stackTraceVisitor.stackElements.size()) { StackElement se = stackTraceVisitor.getStackElement(depth); addFramePopId(vmThread, se.frameIndex); return JVMTI_ERROR_NONE; } else { return JVMTI_ERROR_NO_MORE_FRAMES; } } static boolean framePopForException() { int[] framePopIds = framePopMap.get(VmThread.current()); if (framePopIds == null) { return false; } for (int i = 0; i < framePopIds.length; i++) { if (framePopIds[i] != 0) { return true; } } return false; } private static void addFramePopId(VmThread vmThread, int frameIndex) { int[] framePopIds = framePopMap.get(vmThread); if (framePopIds == null) { framePopIds = new int[2]; framePopMap.put(vmThread, framePopIds); } for (int i = 0; i < framePopIds.length; i++) { if (framePopIds[i] == 0) { framePopIds[i] = frameIndex; return; } else if (framePopIds[i] == frameIndex) { return; } } int[] newFramePopIds = new int[framePopIds.length * 2]; System.arraycopy(framePopIds, 0, newFramePopIds, 0, framePopIds.length); newFramePopIds[framePopIds.length] = frameIndex; framePopMap.put(vmThread, newFramePopIds); } static boolean findFramePopId(VmThread vmThread, long frameId) { int[] framePopIds = framePopMap.get(vmThread); if (framePopIds != null) { for (int i = 0; i < framePopIds.length; i++) { if (framePopIds[i] == frameId) { // destructive, we remove the id as it is a one time event framePopIds[i] = 0; return true; } } } return false; } @NEVER_INLINE public static void framePopEvent(boolean wasPoppedByException, long value) { framePopEvent(wasPoppedByException, new Long(value)); } @NEVER_INLINE public static void framePopEvent(boolean wasPoppedByException, float value) { framePopEvent(wasPoppedByException, new Float(value)); } @NEVER_INLINE public static void framePopEvent(boolean wasPoppedByException, double value) { framePopEvent(wasPoppedByException, new Double(value)); } @NEVER_INLINE public static void framePopEvent(boolean wasPoppedByException) { framePopEvent(wasPoppedByException, null); } /** * Invoked from compiled code before a frame is being popped, e.g. a return. * @param wasPoppedByException */ @NEVER_INLINE public static void framePopEvent(boolean wasPoppedByException, Object value) { VmThread vmThread = VmThread.current(); boolean framePop = !framePopMap.isEmpty(); // fast conservative check boolean methodExitSet = JVMTIEvents.isEventSet(JVMTIEvents.E.METHOD_EXIT); if (framePop || methodExitSet) { FindAppFramesStackTraceVisitor stackTraceVisitor = SingleThreadStackTraceVmOperation.invoke(vmThread); if (framePop && stackTraceVisitor.stackElements.size() > 1) { framePop = findFramePopId(vmThread, stackTraceVisitor.getStackElement(0).frameIndex); // accurate if (framePop) { /* * We may have to deopt the method we are returning to for FRAME_POP. We have to be careful about * inlined source frames as then we really have to deopt the inlining method, which will then * implicitly deopt the inlined method. */ FrameAccessWithIP frameAccess = stackTraceVisitor.getStackElement(1).frameAccess; TargetMethod targetMethod = frameAccess.ip.toTargetMethod(); long codeEventSettings = JVMTIEvents.codeEventSettings(null, vmThread); JVMTICode.checkDeOptForTargetMethod(targetMethod, codeEventSettings); } } // METHOD_EXIT events can occur from within JDK code compiled at run time used by the VM // which results in an empty stack if (stackTraceVisitor.stackElements.size() > 0) { if (framePop || methodExitSet) { FramePopEventData framePopEventData = getFramePopEventData(MethodID.fromMethodActor(stackTraceVisitor.getStackElement(0).classMethodActor), wasPoppedByException, value); if (framePop) { JVMTI.event(JVMTIEvents.E.FRAME_POP, framePopEventData); } if (methodExitSet) { JVMTI.event(JVMTIEvents.E.METHOD_EXIT, framePopEventData); } } } } } static FramePopEventData getFramePopEventData(MethodID methodID, boolean wasPoppedByException, Object value) { FramePopEventData framePopEventData = framePopEventDataTL.get(); framePopEventData.methodID = methodID; framePopEventData.wasPoppedByException = wasPoppedByException; framePopEventData.value = value; return framePopEventData; } static int interruptThread(Thread thread) { if (!thread.isAlive()) { return JVMTI_ERROR_THREAD_NOT_ALIVE; } VmThread.fromJava(thread).interrupt0(); return JVMTI_ERROR_NONE; } /** * Visitor for getting/setting local variables in stack frames. */ private static class GetSetStackTraceVisitor extends FindAppFramesStackTraceVisitor { int depth; int slot; TypedData typedData; boolean isSet; int returnCode = JVMTI_ERROR_NONE; GetSetStackTraceVisitor(int depth, int slot, TypedData typedData, boolean isSet) { this.depth = depth; this.slot = slot; this.typedData = typedData; this.isSet = isSet; } @Override public void walk(StackFrameWalker walker, Pointer ip, Pointer sp, Pointer fp) { super.walk(walker, ip, sp, fp); if (depth < stackElements.size()) { StackElement stackElement = getStackElement(depth); ClassMethodActor classMethodActor = stackElement.classMethodActor; // the stack elements are logical but there may be inlining, so we must // get the TargetMethod from the physical frame info. FrameAccessWithIP frameAccess = stackElement.frameAccess; TargetMethod targetMethod = frameAccess.ip.toTargetMethod(); if (!targetMethod.isBaseline() && isSet) { returnCode = JVMTI_ERROR_OPAQUE_FRAME; // TODO need deopt return; } targetMethod.finalizeReferenceMaps(); int spi = targetMethod.findSafepointIndex(frameAccess.ip); assert spi >= 0; CiFrame ciFrame = targetMethod.debugInfoAt(spi, isSet ? null : frameAccess).frame(); if (slot >= ciFrame.numLocals) { returnCode = JVMTI_ERROR_INVALID_SLOT; return; } if (!typeCheck(classMethodActor)) { returnCode = JVMTI_ERROR_TYPE_MISMATCH; return; } CiConstant ciConstant = null; CiAddress ciAddress = null; if (isSet) { ciAddress = (CiAddress) ciFrame.getLocalValue(slot); } else { ciConstant = (CiConstant) ciFrame.getLocalValue(slot); } // The type of ciConstant almost certainly will not be accurate, // for example, T1X only distinguishes reference (object) types; everything else is a long // Checkstyle: stop switch (typedData.tag) { case 'L': if (ciConstant.kind != CiKind.Object) { returnCode = JVMTI_ERROR_TYPE_MISMATCH; return; } if (isSet) { } else { typedData.objectValue = ciConstant.asObject(); } break; case 'F': if (isSet) { } else { typedData.floatValue = Float.intBitsToFloat((int) ciConstant.asPrimitive()); } break; case 'D': if (isSet) { } else { typedData.doubleValue = Double.longBitsToDouble(ciConstant.asPrimitive()); } break; case 'I': if (isSet) { Pointer varPtr = frameSlotAddress(ciAddress, frameAccess); varPtr.setInt(typedData.intValue); } else { typedData.intValue = (int) ciConstant.asPrimitive(); } break; case 'J': if (isSet) { } else { typedData.longValue = ciConstant.asLong(); } } // Checkstyle: resume } else { returnCode = JVMTI_ERROR_NO_MORE_FRAMES; } } @NEVER_INLINE private static Pointer frameSlotAddress(CiAddress address, FrameAccess frameAccess) { Pointer fpVal = frameAccess.fp; return fpVal.plus(address.displacement); } private boolean typeCheck(ClassMethodActor classMethodActor) { LocalVariableTable.Entry[] entries = classMethodActor.codeAttribute().localVariableTable().entries(); if (entries.length > 0) { for (int i = 0; i < entries.length; i++) { LocalVariableTable.Entry entry = entries[i]; if (entry.slot() == slot) { String slotType = classMethodActor.holder().constantPool().utf8At(entry.descriptorIndex(), "local variable type").toString(); return typeMatch(slotType.charAt(0)); } } return false; } else { // no info so assume ok return true; } } private boolean typeMatch(int type) { if (typedData.tag == 'L') { return type == 'L' || type == '['; } else { if (typedData.tag == 'I') { return type == 'Z' || type == 'B' || type == 'C' || type == 'S' || type == 'I'; } else { return type == typedData.tag; } } } } /** * {@code typedData} carries the type and the input value when {@code isSet}, and carries the output * value when not {@code isSet}. */ private static int getOrSetLocalValue(Thread thread, int depth, int slot, TypedData typedData, boolean isSet) { VmThread vmThread = checkVmThread(thread); if (vmThread == null) { return JVMTI_ERROR_THREAD_NOT_ALIVE; } GetSetStackTraceVisitor stackTraceVisitor = new GetSetStackTraceVisitor(depth, slot, typedData, isSet); SingleThreadStackTraceVmOperation.invoke(vmThread, stackTraceVisitor); return stackTraceVisitor.returnCode; } /** * Native variant. Places value in {@code valuePtr}. */ static int getLocalValue(Thread thread, int depth, int slot, Pointer valuePtr, char type) { TypedData typedData = new TypedData(type); int error = getOrSetLocalValue(thread, depth, slot, typedData, false); if (error == JVMTI_ERROR_NONE) { switch (typedData.tag) { case 'L': valuePtr.setWord(JniHandles.createLocalHandle(typedData.objectValue)); break; case 'D': valuePtr.setDouble(typedData.doubleValue); break; case 'F': valuePtr.setFloat(typedData.floatValue); break; case 'J': valuePtr.setLong(typedData.longValue); break; case 'I': valuePtr.setInt(typedData.intValue); break; } } return error; } static int setLocalValue(Thread thread, int depth, int slot, TypedData typedData) { return getOrSetLocalValue(thread, depth, slot, typedData, true); } static int setLocalInt(Thread thread, int depth, int slot, int value) { return setLocalValue(thread, depth, slot, new TypedData(TypedData.DATA_INT, value)); } static int setLocalLong(Thread thread, int depth, int slot, long value) { return setLocalValue(thread, depth, slot, new TypedData(TypedData.DATA_LONG, value)); } static int setLocalFloat(Thread thread, int depth, int slot, float value) { return setLocalValue(thread, depth, slot, new TypedData(TypedData.DATA_FLOAT, value)); } static int setLocalDouble(Thread thread, int depth, int slot, double value) { return setLocalValue(thread, depth, slot, new TypedData(TypedData.DATA_DOUBLE, value)); } static int setLocalObject(Thread thread, int depth, int slot, Object value) { return setLocalValue(thread, depth, slot, new TypedData(TypedData.DATA_OBJECT, value)); } // JJVMTI get variants static TypedData getLocalValue(Thread thread, int depth, int slot, int typeTag) { TypedData typedData = new TypedData(typeTag); int error = getOrSetLocalValue(thread, depth, slot, typedData, false); if (error == JVMTI_ERROR_NONE) { return typedData; } else { throw new JJVMTI.JJVMTIException(error); } } static int getLocalInt(Thread thread, int depth, int slot) { return getLocalValue(thread, depth, slot, TypedData.DATA_INT).intValue; } static long getLocalLong(Thread thread, int depth, int slot) { return getLocalValue(thread, depth, slot, TypedData.DATA_LONG).longValue; } static float getLocalFloat(Thread thread, int depth, int slot) { return getLocalValue(thread, depth, slot, TypedData.DATA_FLOAT).floatValue; } static double getLocalDouble(Thread thread, int depth, int slot) { return getLocalValue(thread, depth, slot, TypedData.DATA_DOUBLE).doubleValue; } static Object getLocalObject(Thread thread, int depth, int slot) { return getLocalValue(thread, depth, slot, TypedData.DATA_OBJECT).objectValue; } // Thread suspend/resume/stop/interrupt static int suspendThread(JVMTI.Env jvmtiEnv, Thread thread) { int[] error = suspendThreadList(jvmtiEnv, new Thread[] {thread}); return error[0]; } static int[] suspendThreadList(JVMTI.Env jvmtiEnv, Thread[] threads) { Set<VmThread> threadSet = new HashSet<VmThread>(); int[] errors = new int[threads.length]; for (int i = 0; i < threads.length; i++) { VmThread vmThread = checkVmThread(threads[i]); if (vmThread == null) { errors[i] = JVMTI_ERROR_THREAD_NOT_ALIVE; } else if (VmOperation.isSuspendRequest(vmThread.tla())) { errors[i] = JVMTI_ERROR_THREAD_SUSPENDED; } else { threadSet.add(vmThread); } } suspendOrResumeThreadList(jvmtiEnv, threadSet, true); return errors; } private static int suspendOrResumeThreadList(JVMTI.Env jvmtiEnv, int requestCount, Pointer requestList, Pointer results, boolean isSuspend) { if (requestCount < 0) { return JVMTI_ERROR_ILLEGAL_ARGUMENT; } Set<VmThread> threadSet = new HashSet<VmThread>(); for (int i = 0; i < requestCount; i++) { try { Thread thread = (Thread) requestList.getWord(i).asJniHandle().unhand(); VmThread vmThread = checkVmThread(thread); if (!thread.isAlive()) { results.setInt(i, JVMTI_ERROR_THREAD_NOT_ALIVE); } else { boolean isSuspended = VmOperation.isSuspendRequest(vmThread.tla()); if (isSuspend && isSuspended) { results.setInt(i, JVMTI_ERROR_THREAD_SUSPENDED); } else if (!isSuspend && !isSuspended) { results.setInt(i, JVMTI_ERROR_THREAD_NOT_SUSPENDED); } else { threadSet.add(VmThread.fromJava(thread)); results.setInt(i, JVMTI_ERROR_NONE); } } } catch (ClassCastException ex) { results.setInt(i, JVMTI_ERROR_INVALID_THREAD); } } return suspendOrResumeThreadList(jvmtiEnv, threadSet, isSuspend); } private static int suspendOrResumeThreadList(JVMTI.Env jvmtiEnv, Set<VmThread> threadSet, boolean isSuspend) { if (isSuspend) { new VmOperation.SuspendThreadSet(threadSet).submit(); JVMTICode.suspendThreadListNotify(jvmtiEnv, threadSet); } else { JVMTICode.resumeThreadListNotify(jvmtiEnv, threadSet); new VmOperation.ResumeThreadSet(threadSet).submit(); } return JVMTI_ERROR_NONE; } static int suspendThreadList(JVMTI.Env jvmtiEnv, int requestCount, Pointer requestList, Pointer results) { return suspendOrResumeThreadList(jvmtiEnv, requestCount, requestList, results, true); } static int resumeThread(JVMTI.Env jvmtiEnv, Thread thread) { int[] error = resumeThreadList(jvmtiEnv, new Thread[] {thread}); return error[0]; } static int[] resumeThreadList(JVMTI.Env jvmtiEnv, Thread[] threads) { Set<VmThread> threadSet = new HashSet<VmThread>(); int[] errors = new int[threads.length]; for (int i = 0; i < threads.length; i++) { VmThread vmThread = checkVmThread(threads[i]); if (vmThread == null) { errors[i] = JVMTI_ERROR_THREAD_NOT_ALIVE; } else if (!VmOperation.isSuspendRequest(vmThread.tla())) { errors[i] = JVMTI_ERROR_THREAD_NOT_SUSPENDED; } else { threadSet.add(vmThread); } } suspendOrResumeThreadList(jvmtiEnv, threadSet, false); return errors; } static int resumeThreadList(JVMTI.Env jvmtiEnv, int requestCount, Pointer requestList, Pointer results) { return suspendOrResumeThreadList(jvmtiEnv, requestCount, requestList, results, false); } // ThreadGroup functions static int getTopThreadGroups(Pointer countPtr, Pointer groupsPtrPtr) { countPtr.setInt(1); Pointer groupsPtr = Memory.allocate(Size.fromInt(Word.size())); groupsPtr.setWord(JniHandles.createLocalHandle(VmThread.systemThreadGroup)); groupsPtrPtr.setWord(groupsPtr); return JVMTI_ERROR_NONE; } static int getThreadGroupInfo(ThreadGroup threadGroup, Pointer infoPtr) { setThreadGroupInfo(infoPtr, JniHandles.createLocalHandle(threadGroup.getParent()), CString.utf8FromJava(threadGroup.getName()), threadGroup.getMaxPriority(), threadGroup.isDaemon()); return JVMTI_ERROR_NONE; } @C_FUNCTION private static native void setThreadGroupInfo(Pointer threadGroupInfoPtr, Word parent, Pointer name, int maxPriority, boolean isDaemon); private static class ThreadGroupChildrenUnion extends ModeUnion { // native Pointer threadGroupsPtr; Pointer threadsPtr; int nGroups; int nThreads; // Java ThreadGroupChildrenInfo threadGroupChildrenInfo; ThreadGroupChildrenUnion(boolean isNative) { super(isNative); } } static JJVMTI.ThreadGroupChildrenInfo getThreadGroupChildren(ThreadGroup threadGroup) { ThreadGroupChildrenUnion tgu = new ThreadGroupChildrenUnion(false); getThreadGroupChildren(threadGroup, tgu); return tgu.threadGroupChildrenInfo; } static int getThreadGroupChildren(ThreadGroup threadGroup, Pointer threadCountPtr, Pointer threadsPtrPtr, Pointer groupCountPtr, Pointer groupsPtrPtr) { ThreadGroupChildrenUnion tgu = new ThreadGroupChildrenUnion(true); int error = getThreadGroupChildren(threadGroup, tgu); if (error == JVMTI_ERROR_NONE) { threadCountPtr.setInt(tgu.nThreads); groupCountPtr.setInt(tgu.nGroups); threadsPtrPtr.setWord(tgu.threadsPtr); groupsPtrPtr.setWord(tgu.threadGroupsPtr); } return error; } private static int getThreadGroupChildren(ThreadGroup threadGroup, ThreadGroupChildrenUnion tgu) { // We reach directly into the ThreadGroup class state to avoid security checks and clumsy iterators. ThreadGroupProxy proxy = ThreadGroupProxy.asThreadGroupProxy(threadGroup); Thread[] threads = proxy.threads; ThreadGroup[] threadGroups = proxy.groups; ArrayList<ThreadGroup> activeGroups = null; ArrayList<Thread> liveThreads = null; // Holding the lock means no changes to the ThreadGroup, however threads may die at any time. synchronized (threadGroup) { int nGroups = 0; int nThreads = 0; for (int i = 0; i < proxy.ngroups; i++) { if (!threadGroups[i].isDestroyed()) { nGroups++; } } for (int i = 0; i < proxy.nthreads; i++) { if (threads[i].isAlive()) { nThreads++; } } if (tgu.isNative) { tgu.threadsPtr = Memory.allocate(Size.fromInt(nThreads * Word.size())); if (tgu.threadsPtr.isZero()) { return JVMTI_ERROR_OUT_OF_MEMORY; } tgu.threadGroupsPtr = Memory.allocate(Size.fromInt(nGroups * Word.size())); if (tgu.threadGroupsPtr.isZero()) { Memory.deallocate(tgu.threadsPtr); return JVMTI_ERROR_OUT_OF_MEMORY; } } else { activeGroups = new ArrayList<ThreadGroup>(); liveThreads = new ArrayList<Thread>(); } // we recompute the live thread count int liveThreadCount = 0; for (int i = 0; i < nThreads; i++) { if (threads[i].isAlive()) { if (tgu.isNative) { tgu.threadsPtr.setWord(i, JniHandles.createLocalHandle(threads[i])); } else { liveThreads.add(threads[i]); } liveThreadCount++; } } int activeGroupCount = 0; for (int i = 0; i < nGroups; i++) { if (!threadGroups[i].isDestroyed()) { if (tgu.isNative) { tgu.threadGroupsPtr.setWord(i, JniHandles.createLocalHandle(threadGroups[i])); activeGroupCount++; } else { activeGroups.add(threadGroups[i]); } } } if (!tgu.isNative) { Thread[] threadArray = new Thread[liveThreadCount]; ThreadGroup[] threadGroupArray = new ThreadGroup[activeGroupCount]; liveThreads.toArray(threadArray); activeGroups.toArray(threadGroupArray); tgu.threadGroupChildrenInfo = new JJVMTI.ThreadGroupChildrenInfo(threadArray, threadGroupArray); } else { tgu.nThreads = liveThreadCount; tgu.nGroups = activeGroupCount; } return JVMTI_ERROR_NONE; } } static class ThreadGroupProxy { @INTRINSIC(UNSAFE_CAST) public static native ThreadGroupProxy asThreadGroupProxy(Object object); @ALIAS(declaringClass = ThreadGroup.class) private Thread[] threads; @ALIAS(declaringClass = ThreadGroup.class) private ThreadGroup[] groups; @ALIAS(declaringClass = ThreadGroup.class) private int nthreads; @ALIAS(declaringClass = ThreadGroup.class) private int ngroups; } }