This is the mail archive of the
java-patches@gcc.gnu.org
mailing list for the Java project.
[JVMTI] RFA: JVMTI Stack Tracing
- From: Kyle Galloway <kgallowa at redhat dot com>
- To: java-patches at gcc dot gnu dot org
- Date: Fri, 20 Oct 2006 09:50:44 -0400
- Subject: [JVMTI] RFA: JVMTI Stack Tracing
This patch implements the GetStackTrace and GetFrameCount methods of JVMTI.
These methods can be used on any java thread, including those with
native calls, running on the VM. To accomplish this, this patch uses
the existing stack tracing code, with some modifications. When a trace
is requested, it sets up a handler for SIGTRAP for the thread that is to
be traced. It then dispatches a SIGTRAP to that thread using
ptherad_kill (). The signal handler gets a raw trace of the call stack
of that thread, stores it, then resets the default handler. The actual
JVMTI method waits on a semaphore, since it has to be signal safe, so
that it will not begin to process the trace until the handler has
finished generating it. This raw trace is then fed into a modified
version of _Jv_StackTrace::GetStackTraceElements () called
GetJVMTIStyleTrace, which eliminates uninteresting frames, and returns a
java-style call stack stored in an array of jvmtiFrameInfo structures,
than can be copied into the array sent into the call (or have its length
used for GetFrameCount). The changes to avoid _Jv_AllocBytes is to
avoid the GC, which causes problems when used from a signal handler.
I have included a test case for GetFrameCount, the reason I have not
included one for GetStackTrace is because the values for jmethodIds will
not be constant. Since GetFrameCount and GetStackTrace differ only in
what they do with the jmethodIds and jlocations returned from
GetJVMTIStyleTrace, without being able to set jmethodids, a separate
test case seems redundant since all it would show is that GetStackTrace
retieives the correct number of frames, which is shown by the correct
working of GetFrameCount.
Questions/Comments?
- Kyle
Index: /notnfs/kgallowa/gcc-commit/libjava/include/java-stack.h
===================================================================
--- /notnfs/kgallowa/gcc-commit/libjava/include/java-stack.h (revision 117885)
+++ /notnfs/kgallowa/gcc-commit/libjava/include/java-stack.h (working copy)
@@ -18,6 +18,7 @@
#include <gcj/javaprims.h>
#include <java-interp.h>
+#include <jvmti.h>
#include <java/lang/Class.h>
#include <java/lang/StackTraceElement.h>
@@ -101,7 +102,7 @@
private:
int length;
_Jv_StackFrame frames[];
-
+
static void UpdateNCodeMap ();
static jclass ClassForFrame (_Jv_StackFrame *frame);
static void FillInFrameInfo (_Jv_StackFrame *frame);
@@ -118,9 +119,12 @@
public:
static _Jv_StackTrace *GetStackTrace (void);
+ static _Jv_StackTrace *GetStackTraceNoGC (void);
static JArray< ::java::lang::StackTraceElement *>*
GetStackTraceElements (_Jv_StackTrace *trace,
java::lang::Throwable *throwable);
+ static jvmtiFrameInfo *ConvertToJVMTIStyleTrace (_Jv_StackTrace *trace,
+ jint *javaFrameCount);
static jclass GetCallingClass (jclass);
static void GetCallerInfo (jclass checkClass, jclass *, _Jv_Method **);
static JArray<jclass> *GetClassContext (jclass checkClass);
@@ -126,7 +130,6 @@
static JArray<jclass> *GetClassContext (jclass checkClass);
static ClassLoader *GetFirstNonSystemClassLoader (void);
static jobjectArray GetAccessControlStack ();
-
};
// Information about a given address.
Index: /notnfs/kgallowa/gcc-commit/libjava/jvmti.cc
===================================================================
--- /notnfs/kgallowa/gcc-commit/libjava/jvmti.cc (revision 117885)
+++ /notnfs/kgallowa/gcc-commit/libjava/jvmti.cc (working copy)
@@ -12,6 +12,7 @@
#include <platform.h>
#include <jvm.h>
+#include <java-stack.h>
#include <java-threads.h>
#include <java-gc.h>
#include <jvmti.h>
@@ -33,6 +34,12 @@
#include <java/util/HashMap.h>
#include <java/net/URL.h>
+#ifdef __JV_POSIX_THREADS__
+#include <pthread.h>
+#include <signal.h>
+#include <semaphore.h>
+#endif
+
static void check_enabled_events (void);
static void check_enabled_event (jvmtiEvent);
@@ -192,6 +199,158 @@
return JVMTI_ERROR_NONE;
}
+//pthread stack tracing
+#ifdef __JV_POSIX_THREADS__
+//make sure only one thread can have access to these globals
+pthread_mutex_t globalMutex = PTHREAD_MUTEX_INITIALIZER;
+
+//synchronization semaphore for actual trace
+sem_t syncSem;
+
+//stack tracing global data
+_Jv_StackTrace *trace;
+
+
+//signal handler that performs a raw stack trace
+void
+traceStackFromSignal (MAYBE_UNUSED int signum)
+{
+ trace = _Jv_StackTrace::GetStackTraceNoGC ();
+
+ sem_post (&syncSem);
+}
+
+//Synchronization function to abstract out semaphores
+void
+waitSignalTraceFinish ()
+{
+ sem_wait (&syncSem);
+ sem_destroy (&syncSem);
+}
+
+jvmtiError
+stackTraceSetup (java::lang::Thread *t)
+{
+ using namespace java::lang;
+
+ sem_init (&syncSem, 0, 0);
+ struct sigaction act;
+
+ act.sa_flags = 0;
+ act.sa_flags |= SA_RESETHAND;
+ sigemptyset (&act.sa_mask);
+ act.sa_handler = traceStackFromSignal;
+
+ sigaction (SIGTRAP, &act, NULL);
+
+ //Initiate the trace
+ _Jv_Thread_t *data = _Jv_ThreadGetData (t);
+ pthread_t pThread = _Jv_GetPlatformThreadID (data);
+ pthread_kill (pThread, SIGTRAP);
+
+ return JVMTI_ERROR_NONE;
+}
+#endif
+static jvmtiError JNICALL
+_Jv_JVMTI_GetStackTrace (MAYBE_UNUSED jvmtiEnv *env, jthread thread,
+ jint start_depth, jint max_frame_count,
+ jvmtiFrameInfo *frame_buffer, jint *count)
+{
+ REQUIRE_PHASE (env, JVMTI_PHASE_LIVE);
+
+ using namespace java::lang;
+
+ THREAD_DEFAULT_TO_CURRENT (thread);
+
+ Thread *t = reinterpret_cast<Thread *> (thread);
+ THREAD_CHECK_VALID (t);
+ THREAD_CHECK_IS_ALIVE (t);
+
+ NULL_CHECK (count);
+ NULL_CHECK (frame_buffer);
+
+ if (max_frame_count < 0)
+ return JVMTI_ERROR_ILLEGAL_ARGUMENT;
+
+ jvmtiError ret;
+#ifdef __JV_POSIX_THREADS__
+ pthread_mutex_lock (&globalMutex);
+
+ if ((ret = stackTraceSetup (t)) != JVMTI_ERROR_NONE)
+ return ret;
+
+ waitSignalTraceFinish ();
+#endif
+ if (trace == NULL)
+ return JVMTI_ERROR_OUT_OF_MEMORY;
+
+ jvmtiFrameInfo *fBuff = _Jv_StackTrace::ConvertToJVMTIStyleTrace (trace,
+ count);
+
+ _Jv_Free (trace);
+#ifdef __JV_POSIX_THREADS__
+ pthread_mutex_unlock (&globalMutex);
+#endif
+ if (fBuff == NULL)
+ return JVMTI_ERROR_OUT_OF_MEMORY;
+
+ if (start_depth < 0)
+ start_depth = (*count) + start_depth;
+
+ if (start_depth >= (*count))
+ return JVMTI_ERROR_ILLEGAL_ARGUMENT;
+
+ //Take start depth into account
+ (*count) -= start_depth;
+ frame_buffer += (start_depth * sizeof (jvmtiFrameInfo));
+
+ if ((*count) > max_frame_count)
+ (*count) = max_frame_count;
+
+ memcpy ((void *) frame_buffer,
+ (void *) fBuff, (*count) * sizeof (jvmtiFrameInfo));
+
+ return JVMTI_ERROR_NONE;
+}
+
+static jvmtiError JNICALL
+_Jv_JVMTI_GetFrameCount (MAYBE_UNUSED jvmtiEnv *env, jthread thread,
+ jint *count)
+{
+ REQUIRE_PHASE (env, JVMTI_PHASE_LIVE);
+
+ using namespace java::lang;
+
+ THREAD_DEFAULT_TO_CURRENT (thread);
+
+ Thread *t = reinterpret_cast<Thread *> (thread);
+ THREAD_CHECK_VALID (t);
+ THREAD_CHECK_IS_ALIVE (t);
+
+ NULL_CHECK (count);
+
+ jvmtiError ret;
+#ifdef __JV_POSIX_THREADS__
+ pthread_mutex_lock (&globalMutex);
+
+ if ((ret = stackTraceSetup (t)) != JVMTI_ERROR_NONE)
+ return ret;
+
+ waitSignalTraceFinish ();
+#endif
+ if (trace == NULL)
+ return JVMTI_ERROR_OUT_OF_MEMORY;
+
+ _Jv_StackTrace::ConvertToJVMTIStyleTrace (trace, count);
+
+ _Jv_Free (trace);
+#ifdef __JV_POSIX_THREADS__
+ pthread_mutex_unlock (&globalMutex);
+#endif
+
+ return JVMTI_ERROR_NONE;
+}
+
static jvmtiError JNICALL
_Jv_JVMTI_CreateRawMonitor (MAYBE_UNUSED jvmtiEnv *env, const char *name,
jrawMonitorID *result)
@@ -1238,7 +1397,7 @@
UNIMPLEMENTED, // GetTopThreadGroups
UNIMPLEMENTED, // GetThreadGroupInfo
UNIMPLEMENTED, // GetThreadGroupChildren
- UNIMPLEMENTED, // GetFrameCount
+ _Jv_JVMTI_GetFrameCount, // GetFrameCount
UNIMPLEMENTED, // GetThreadState
RESERVED, // reserved18
UNIMPLEMENTED, // GetFrameLocation
@@ -1326,7 +1485,7 @@
UNIMPLEMENTED, // GetThreadListStackTraces
UNIMPLEMENTED, // GetThreadLocalStorage
UNIMPLEMENTED, // SetThreadLocalStorage
- UNIMPLEMENTED, // GetStackTrace
+ _Jv_JVMTI_GetStackTrace, // GetStackTrace
RESERVED, // reserved105
UNIMPLEMENTED, // GetTag
UNIMPLEMENTED, // SetTag
Index: /notnfs/kgallowa/gcc-commit/libjava/stacktrace.cc
===================================================================
--- /notnfs/kgallowa/gcc-commit/libjava/stacktrace.cc (revision 117885)
+++ /notnfs/kgallowa/gcc-commit/libjava/stacktrace.cc (working copy)
@@ -12,6 +12,7 @@
#include <platform.h>
#include <jvm.h>
+#include <jvmti.h>
#include <gcj/cni.h>
#include <java-interp.h>
#include <java-stack.h>
@@ -100,8 +101,9 @@
if (pos == state->length)
{
int newLength = state->length * 2;
- void *newFrames = _Jv_AllocBytes (newLength * sizeof (_Jv_StackFrame));
- memcpy (newFrames, state->frames, state->length * sizeof (_Jv_StackFrame));
+ void *newFrames = _Jv_MallocUnchecked (newLength * sizeof (_Jv_StackFrame));
+ memcpy (newFrames, state->frames, state->length * sizeof (_Jv_StackFrame));
+ _Jv_Free (state->frames);
state->frames = (_Jv_StackFrame *) newFrames;
state->length = newLength;
}
@@ -174,7 +176,32 @@
(sizeof (_Jv_StackFrame) * state.pos);
_Jv_StackTrace *trace = (_Jv_StackTrace *) _Jv_AllocBytes (traceSize);
trace->length = state.pos;
+ memcpy (trace->frames, state.frames, sizeof (_Jv_StackFrame) * state.pos);
+ if (state.length > trace_size)
+ _Jv_Free (state.frames);
+ return trace;
+}
+
+// Same as GetStackTrace, but allocates using _Jv_MallocUnchecked instead of
+// _Jv_AllocBytes.
+_Jv_StackTrace *
+_Jv_StackTrace::GetStackTraceNoGC(void)
+{
+ int trace_size = 100;
+ _Jv_StackFrame frames[trace_size];
+ _Jv_UnwindState state (trace_size);
+ state.frames = (_Jv_StackFrame *) &frames;
+
+ _Unwind_Backtrace (UnwindTraceFn, &state);
+
+ // Copy the trace and return it.
+ int traceSize = sizeof (_Jv_StackTrace) +
+ (sizeof (_Jv_StackFrame) * state.pos);
+ _Jv_StackTrace *trace = (_Jv_StackTrace *) _Jv_MallocUnchecked (traceSize);
+ trace->length = state.pos;
memcpy (trace->frames, state.frames, sizeof (_Jv_StackFrame) * state.pos);
+ if (state.length > trace_size)
+ _Jv_Free (state.frames);
return trace;
}
@@ -328,7 +355,7 @@
if (!frame->klass || !frame->meth)
// Not a Java frame.
continue;
-
+
// Throw away the top of the stack till we see:
// - the constructor(s) of this Throwable, or
// - the Throwable.fillInStackTrace call.
@@ -386,6 +413,63 @@
return (JArray<StackTraceElement *>*) list->toArray (array);
}
+jvmtiFrameInfo *
+_Jv_StackTrace::ConvertToJVMTIStyleTrace (_Jv_StackTrace *trace,
+ jint *javaFrameCount)
+{
+ //JvSynchronized (ncodeMap);
+ UpdateNCodeMap ();
+
+ int start_idx = 0;
+ int end_idx = trace->length - 1;
+
+ // First pass: strip superfluous frames from beginning and end of the trace.
+ for (int i = 0; i < trace->length; i++)
+ {
+ _Jv_StackFrame *frame = &trace->frames[i];
+ FillInFrameInfo (frame);
+
+ if (!frame->klass || !frame->meth)
+ // Not a Java frame.
+ continue;
+
+ // End the trace at the application's main() method if we see call_main.
+ if (frame->klass == &gnu::java::lang::MainThread::class$
+ && strcmp (frame->meth->name->chars(), "call_main") == 0)
+ end_idx = i - 1;
+ }
+
+ jvmtiFrameInfo *javaFrames
+ = (jvmtiFrameInfo *)
+ _Jv_AllocBytes (end_idx - start_idx +1 * sizeof (jvmtiFrameInfo));
+
+ (*javaFrameCount) = 0;
+
+ // Second pass: Look up line-number info for remaining frames.
+ for (int i = start_idx; (i <= end_idx && javaFrames != NULL) ; i++)
+ {
+ _Jv_StackFrame *frame = &trace->frames[i];
+
+ if (frame->klass == NULL)
+ // Not a Java frame.
+ continue;
+
+ javaFrames[(*javaFrameCount)].method = frame->meth;
+
+#ifdef INTERPRETER
+ if (frame->type == frame_interpreter)
+ javaFrames[(*javaFrameCount)].location
+ = frame->interp.meth->insn_index (frame->interp.pc);
+ else
+#endif /* INTERPRETER */
+ javaFrames[(*javaFrameCount)].location = -1;
+
+ (*javaFrameCount)++;
+ }
+
+ return javaFrames;
+}
+
struct CallingClassTraceData
{
jclass checkClass;
Index: /notnfs/kgallowa/gcc-commit/libjava/testsuite/libjava.jvmti/getframecount.java
===================================================================
--- /notnfs/kgallowa/gcc-commit/libjava/testsuite/libjava.jvmti/getframecount.java (revision 0)
+++ /notnfs/kgallowa/gcc-commit/libjava/testsuite/libjava.jvmti/getframecount.java (revision 0)
@@ -0,0 +1,93 @@
+// Test JVMTI GetFrameCount function
+
+public class getframecount extends Thread
+{
+ public boolean loop;
+ public int testNum;
+
+ public getframecount (int test)
+ {
+ super ();
+ loop = false;
+ testNum = test;
+ }
+
+ public static native void do_getframecount_tests (Thread t, int threadNum);
+
+ public native void run2 ();
+ public native void run3 ();
+
+ public void run ()
+ {
+ if (testNum == 0)
+ {
+ loop = true;
+ while (loop)
+ yield ();
+ }
+ else if (testNum == 1)
+ {
+ run1 ();
+ }
+ else if (testNum == 2)
+ {
+ run2 ();
+ }
+ else if (testNum == 3)
+ {
+ run3 ();
+ }
+ else
+ {
+ run4 ();
+ }
+ }
+
+ public void run1 ()
+ {
+ loop = true;
+ while (loop)
+ yield ();
+ }
+
+ public void run4 ()
+ {
+ run3 ();
+ }
+
+ public void done ()
+ {
+ loop = false;
+ }
+
+ public static void addThread (getframecount[] threads, int numThreads)
+ {
+ threads[numThreads] = new getframecount (numThreads);
+ threads[numThreads].start ();
+ while (!threads[numThreads].loop);
+ }
+
+ public static void cleanup (getframecount[] threads, int numThreads)
+ {
+ for (int i = 0; i < numThreads ;)
+ {
+ threads[i].done ();
+ i++;
+ }
+ }
+
+ public static void main (String[] args)
+ {
+ System.out.println ("JVMTI GetFrameCount Tests");
+ getframecount[] threads = new getframecount[5];
+ for (int i = 0; i < 5 ; i++)
+ {
+ addThread (threads, i);
+ for (int j = 0; j <= i; j++)
+ {
+ do_getframecount_tests (threads[j], j);
+ }
+ }
+ cleanup (threads, 5);
+ }
+}
Index: /notnfs/kgallowa/gcc-commit/libjava/testsuite/libjava.jvmti/getframecount.out
===================================================================
--- /notnfs/kgallowa/gcc-commit/libjava/testsuite/libjava.jvmti/getframecount.out (revision 0)
+++ /notnfs/kgallowa/gcc-commit/libjava/testsuite/libjava.jvmti/getframecount.out (revision 0)
@@ -0,0 +1,16 @@
+JVMTI GetFrameCount Tests
+Thread 0 has 2 frames
+Thread 0 has 2 frames
+Thread 1 has 3 frames
+Thread 0 has 2 frames
+Thread 1 has 3 frames
+Thread 2 has 2 frames
+Thread 0 has 2 frames
+Thread 1 has 3 frames
+Thread 2 has 2 frames
+Thread 3 has 4 frames
+Thread 0 has 2 frames
+Thread 1 has 3 frames
+Thread 2 has 2 frames
+Thread 3 has 4 frames
+Thread 4 has 5 frames
Index: /notnfs/kgallowa/gcc-commit/libjava/testsuite/libjava.jvmti/natgetframecount.cc
===================================================================
--- /notnfs/kgallowa/gcc-commit/libjava/testsuite/libjava.jvmti/natgetframecount.cc (revision 0)
+++ /notnfs/kgallowa/gcc-commit/libjava/testsuite/libjava.jvmti/natgetframecount.cc (revision 0)
@@ -0,0 +1,51 @@
+#include <gcj/cni.h>
+
+#include <jvm.h>
+#include <jvmti.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <java/lang/Throwable.h>
+
+#include "jvmti-int.h"
+#include "getframecount.h"
+
+void
+getframecount::do_getframecount_tests (java::lang::Thread *thr, jint threadNum)
+{
+ jvmtiEnv *env;
+ JavaVM *vm = _Jv_GetJavaVM ();
+ vm->GetEnv (reinterpret_cast<void **> (&env), JVMTI_VERSION_1_0);
+
+ jthread thread;
+ jint frameCount;
+
+ thread = reinterpret_cast<jthread> (thr);
+
+ jvmtiError err;
+
+ err = env->GetFrameCount (thread, &frameCount);
+
+ if (err != JVMTI_ERROR_NONE)
+ {
+ char *errorName;
+ env->GetErrorName (err, &errorName);
+ env->Deallocate ((unsigned char *) errorName);
+ }
+ else
+ printf ("Thread %d has %d frames\n", (int) threadNum, (int) frameCount);
+}
+
+void
+getframecount::run2 ()
+{
+ this->loop = true;
+ while (this->loop)
+ usleep(10);
+}
+
+void
+getframecount::run3 ()
+{
+ this->run1 ();
+}