This is the mail archive of the java-patches@gcc.gnu.org mailing list for the Java project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[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 ();
+}

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]