diff agent/src/share/native/jvmdi/sa.cpp @ 0:a61af66fc99e jdk7-b24

Initial load
author duke
date Sat, 01 Dec 2007 00:00:00 +0000
parents
children c18cbe5936b8
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/src/share/native/jvmdi/sa.cpp	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,601 @@
+/*
+ * Copyright 2002 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ *
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <vector>
+#include "sa.hpp"
+#include "jni.h"
+#include "jvmdi.h"
+
+#ifndef WIN32
+ #include <inttypes.h>
+#else
+ typedef int int32_t;
+#endif
+
+#ifdef WIN32
+ #include <windows.h>
+ #define YIELD() Sleep(0)
+ #define SLEEP() Sleep(10)
+ #define vsnprintf _vsnprintf
+#else
+ Error: please port YIELD() and SLEEP() macros to your platform
+#endif
+
+using namespace std;
+
+//////////////////////////////////////////////////////////////////////
+//                                                                  //
+// Exported "interface" for Java language-level interaction between //
+// the SA and the VM. Note that the SA knows about the layout of    //
+// certain VM data structures and that knowledge is taken advantage //
+// of in this code, although this interfaces with the VM via JVMDI. //
+//                                                                  //
+//////////////////////////////////////////////////////////////////////
+
+extern "C" {
+  /////////////////////////////////////
+  //                                 //
+  // Events sent by the VM to the SA //
+  //                                 //
+  /////////////////////////////////////
+
+  // Set by the SA when it attaches. Indicates that events should be
+  // posted via these exported variables, and that the VM should wait
+  // for those events to be acknowledged by the SA (via its setting
+  // saEventPending to 0).
+  JNIEXPORT volatile int32_t saAttached     = 0;
+
+  // Set to nonzero value by the VM when an event has been posted; set
+  // back to 0 by the SA when it has processed that event.
+  JNIEXPORT volatile int32_t saEventPending = 0;
+
+  // Kind of the event (from jvmdi.h)
+  JNIEXPORT volatile int32_t saEventKind    = 0;
+
+  //
+  // Exception events
+  //
+  JNIEXPORT jthread   saExceptionThread;
+  JNIEXPORT jclass    saExceptionClass;
+  JNIEXPORT jmethodID saExceptionMethod;
+  JNIEXPORT int32_t   saExceptionLocation;
+  JNIEXPORT jobject   saExceptionException;
+  JNIEXPORT jclass    saExceptionCatchClass;
+  JNIEXPORT jmethodID saExceptionCatchMethod;
+  JNIEXPORT int32_t   saExceptionCatchLocation;
+
+  //
+  // Breakpoint events
+  //
+  JNIEXPORT jthread   saBreakpointThread;
+  JNIEXPORT jclass    saBreakpointClass;
+  JNIEXPORT jmethodID saBreakpointMethod;
+  JNIEXPORT jlocation saBreakpointLocation;
+
+  ///////////////////////////////////////
+  //                                   //
+  // Commands sent by the SA to the VM //
+  //                                   //
+  ///////////////////////////////////////
+
+  extern JNIEXPORT const int32_t SA_CMD_SUSPEND_ALL       = 0;
+  extern JNIEXPORT const int32_t SA_CMD_RESUME_ALL        = 1;
+  extern JNIEXPORT const int32_t SA_CMD_TOGGLE_BREAKPOINT = 2;
+  extern JNIEXPORT const int32_t SA_CMD_BUF_SIZE          = 1024;
+
+  // SA sets this to a nonzero value when it is requesting a command
+  // to be processed; VM sets it back to 0 when the command has been
+  // executed
+  JNIEXPORT volatile int32_t saCmdPending   = 0;
+
+  // SA sets this to one of the manifest constants above to indicate
+  // the kind of command to be executed
+  JNIEXPORT volatile int32_t saCmdType      = 0;
+
+  // VM sets this to 0 if the last command succeeded or a nonzero
+  // value if it failed
+  JNIEXPORT volatile int32_t saCmdResult    = 0;
+
+  // If last command failed, this buffer will contain a descriptive
+  // error message
+  JNIEXPORT char             saCmdResultErrMsg[SA_CMD_BUF_SIZE];
+
+  //
+  // Toggling of breakpoint command arguments.
+  //
+  // Originally there were separate set/clear breakpoint commands
+  // taking a class name, method name and signature, and the iteration
+  // through the debug information was done in the SA. It turns out
+  // that doing this work in the target VM is significantly faster,
+  // and since interactivity when setting and clearing breakpoints is
+  // important, the solution which resulted in more C/C++ code was used.
+  //
+
+  // Source file name
+  JNIEXPORT char    saCmdBkptSrcFileName[SA_CMD_BUF_SIZE];
+
+  // Package name ('/' as separator instead of '.')
+  JNIEXPORT char    saCmdBkptPkgName[SA_CMD_BUF_SIZE];
+
+  // Line number
+  JNIEXPORT int32_t saCmdBkptLineNumber;
+
+  // Output back to SA: indicator whether the last failure of a
+  // breakpoint toggle command was really an error or just a lack of
+  // debug information covering the requested line. 0 if not error.
+  // Valid only if saCmdResult != 0.
+  JNIEXPORT int32_t saCmdBkptResWasError;
+
+  // Output back to SA: resulting line number at which the breakpoint
+  // was set or cleared (valid only if saCmdResult == 0)
+  JNIEXPORT int32_t saCmdBkptResLineNumber;
+
+  // Output back to SA: resulting byte code index at which the
+  // breakpoint was set or cleared (valid only if saCmdResult == 0)
+  JNIEXPORT int32_t saCmdBkptResBCI;
+
+  // Output back to SA: indicator whether the breakpoint operation
+  // resulted in a set or cleared breakpoint; nonzero if set, zero if
+  // cleared (valid only if saCmdResult == 0)
+  JNIEXPORT int32_t saCmdBkptResWasSet;
+
+  // Output back to SA: method name the breakpoint was set in (valid
+  // only if saCmdResult == 0)
+  JNIEXPORT char    saCmdBkptResMethodName[SA_CMD_BUF_SIZE];
+
+  // Output back to SA: method signature (JNI style) the breakpoint
+  // was set in (valid only if saCmdResult == 0)
+  JNIEXPORT char    saCmdBkptResMethodSig[SA_CMD_BUF_SIZE];
+}
+
+// Internal state
+static JavaVM* jvm = NULL;
+static JVMDI_Interface_1* jvmdi = NULL;
+static jthread debugThreadObj = NULL;
+static bool suspended = false;
+static vector<jthread> suspendedThreads;
+static JVMDI_RawMonitor eventLock = NULL;
+
+class MonitorLocker {
+private:
+  JVMDI_RawMonitor lock;
+public:
+  MonitorLocker(JVMDI_RawMonitor lock) {
+    this->lock = lock;
+    if (lock != NULL) {
+      jvmdi->RawMonitorEnter(lock);
+    }
+  }
+  ~MonitorLocker() {
+    if (lock != NULL) {
+      jvmdi->RawMonitorExit(lock);
+    }
+  }
+};
+
+class JvmdiDeallocator {
+private:
+  void* ptr;
+public:
+  JvmdiDeallocator(void* ptr) {
+    this->ptr = ptr;
+  }
+  ~JvmdiDeallocator() {
+    jvmdi->Deallocate((jbyte*) ptr);
+  }
+};
+
+class JvmdiRefListDeallocator {
+private:
+  JNIEnv* env;
+  jobject* refList;
+  jint refCount;
+public:
+  JvmdiRefListDeallocator(JNIEnv* env, jobject* refList, jint refCount) {
+    this->env = env;
+    this->refList = refList;
+    this->refCount = refCount;
+  }
+  ~JvmdiRefListDeallocator() {
+    for (int i = 0; i < refCount; i++) {
+      env->DeleteGlobalRef(refList[i]);
+    }
+    jvmdi->Deallocate((jbyte*) refList);
+  }
+};
+
+static void
+stop(char* msg) {
+  fprintf(stderr, "%s", msg);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+// This fills in the command result error message, sets the command
+// result to -1, and clears the pending command flag
+static void
+reportErrorToSA(const char* str, ...) {
+  va_list varargs;
+  va_start(varargs, str);
+  vsnprintf(saCmdResultErrMsg, sizeof(saCmdResultErrMsg), str, varargs);
+  va_end(varargs);
+  saCmdResult = -1;
+  saCmdPending = 0;
+}
+
+static bool
+packageNameMatches(char* clazzName, char* pkg) {
+  int pkgLen = strlen(pkg);
+  int clazzNameLen = strlen(clazzName);
+
+  if (pkgLen >= clazzNameLen + 1) {
+    return false;
+  }
+
+  if (strncmp(clazzName, pkg, pkgLen)) {
+    return false;
+  }
+
+  // Ensure that '/' is the next character if non-empty package name
+  int l = pkgLen;
+  if (l > 0) {
+    if (clazzName[l] != '/') {
+      return false;
+    }
+    l++;
+  }
+  // Ensure that there are no more trailing slashes
+  while (l < clazzNameLen) {
+    if (clazzName[l++] == '/') {
+      return false;
+    }
+  }
+  return true;
+}
+
+static void
+executeOneCommand(JNIEnv* env) {
+  switch (saCmdType) {
+  case SA_CMD_SUSPEND_ALL: {
+    if (suspended) {
+      reportErrorToSA("Target process already suspended");
+      return;
+    }
+
+    // We implement this by getting all of the threads and calling
+    // SuspendThread on each one, except for the thread object
+    // corresponding to this thread. Each thread for which the call
+    // succeeded (i.e., did not return JVMDI_ERROR_INVALID_THREAD)
+    // is added to a list which is remembered for later resumption.
+    // Note that this currently has race conditions since a thread
+    // might be started after we call GetAllThreads and since a
+    // thread for which we got an error earlier might be resumed by
+    // the VM while we are busy suspending other threads. We could
+    // solve this by looping until there are no more threads we can
+    // suspend, but a more robust and scalable solution is to add
+    // this functionality to the JVMDI interface (i.e.,
+    // "suspendAll"). Probably need to provide an exclude list for
+    // such a routine.
+    jint threadCount;
+    jthread* threads;
+    if (jvmdi->GetAllThreads(&threadCount, &threads) != JVMDI_ERROR_NONE) {
+      reportErrorToSA("Error while getting thread list");
+      return;
+    }
+
+
+    for (int i = 0; i < threadCount; i++) {
+      jthread thr = threads[i];
+      if (!env->IsSameObject(thr, debugThreadObj)) {
+        jvmdiError err = jvmdi->SuspendThread(thr);
+        if (err == JVMDI_ERROR_NONE) {
+          // Remember this thread and do not free it
+          suspendedThreads.push_back(thr);
+          continue;
+        } else {
+          fprintf(stderr, " SA: Error %d while suspending thread\n", err);
+          // FIXME: stop, resume all threads, report error
+        }
+      }
+      env->DeleteGlobalRef(thr);
+    }
+
+    // Free up threads
+    jvmdi->Deallocate((jbyte*) threads);
+
+    // Suspension is complete
+    suspended = true;
+    break;
+  }
+
+  case SA_CMD_RESUME_ALL: {
+    if (!suspended) {
+      reportErrorToSA("Target process already suspended");
+      return;
+    }
+
+    saCmdResult = 0;
+    bool errorOccurred = false;
+    jvmdiError firstError;
+    for (int i = 0; i < suspendedThreads.size(); i++) {
+      jthread thr = suspendedThreads[i];
+      jvmdiError err = jvmdi->ResumeThread(thr);
+      env->DeleteGlobalRef(thr);
+      if (err != JVMDI_ERROR_NONE) {
+        if (!errorOccurred) {
+          errorOccurred = true;
+          firstError = err;
+        }
+      }
+    }
+    suspendedThreads.clear();
+    suspended = false;
+    if (errorOccurred) {
+      reportErrorToSA("Error %d while resuming threads", firstError);
+      return;
+    }
+    break;
+  }
+
+  case SA_CMD_TOGGLE_BREAKPOINT: {
+    saCmdBkptResWasError = 1;
+
+    // Search line number info for all loaded classes
+    jint classCount;
+    jclass* classes;
+
+    jvmdiError glcRes = jvmdi->GetLoadedClasses(&classCount, &classes);
+    if (glcRes != JVMDI_ERROR_NONE) {
+      reportErrorToSA("Error %d while getting loaded classes", glcRes);
+      return;
+    }
+    JvmdiRefListDeallocator rld(env, (jobject*) classes, classCount);
+
+    bool done = false;
+    bool gotOne = false;
+    jclass targetClass;
+    jmethodID targetMethod;
+    jlocation targetLocation;
+    jint targetLineNumber;
+
+    for (int i = 0; i < classCount && !done; i++) {
+      fflush(stderr);
+      jclass clazz = classes[i];
+      char* srcName;
+      jvmdiError sfnRes = jvmdi->GetSourceFileName(clazz, &srcName);
+      if (sfnRes == JVMDI_ERROR_NONE) {
+        JvmdiDeallocator de1(srcName);
+        if (!strcmp(srcName, saCmdBkptSrcFileName)) {
+          // Got a match. Now see whether the package name of the class also matches
+          char* clazzName;
+          jvmdiError sigRes = jvmdi->GetClassSignature(clazz, &clazzName);
+          if (sigRes != JVMDI_ERROR_NONE) {
+            reportErrorToSA("Error %d while getting a class's signature", sigRes);
+            return;
+          }
+          JvmdiDeallocator de2(clazzName);
+          if (packageNameMatches(clazzName + 1, saCmdBkptPkgName)) {
+            // Iterate through all methods
+            jint methodCount;
+            jmethodID* methods;
+            if (jvmdi->GetClassMethods(clazz, &methodCount, &methods) != JVMDI_ERROR_NONE) {
+              reportErrorToSA("Error while getting methods of class %s", clazzName);
+              return;
+            }
+            JvmdiDeallocator de3(methods);
+            for (int j = 0; j < methodCount && !done; j++) {
+              jmethodID m = methods[j];
+              jint entryCount;
+              JVMDI_line_number_entry* table;
+              jvmdiError lnRes = jvmdi->GetLineNumberTable(clazz, m, &entryCount, &table);
+              if (lnRes == JVMDI_ERROR_NONE) {
+                JvmdiDeallocator de4(table);
+                // Look for line number greater than or equal to requested line
+                for (int k = 0; k < entryCount && !done; k++) {
+                  JVMDI_line_number_entry& entry = table[k];
+                  if (entry.line_number >= saCmdBkptLineNumber &&
+                      (!gotOne || entry.line_number < targetLineNumber)) {
+                    gotOne = true;
+                    targetClass = clazz;
+                    targetMethod = m;
+                    targetLocation = entry.start_location;
+                    targetLineNumber = entry.line_number;
+                    done = (targetLineNumber == saCmdBkptLineNumber);
+                  }
+                }
+              } else if (lnRes != JVMDI_ERROR_ABSENT_INFORMATION) {
+                reportErrorToSA("Unexpected error %d while fetching line number table", lnRes);
+                return;
+              }
+            }
+          }
+        }
+      } else if (sfnRes != JVMDI_ERROR_ABSENT_INFORMATION) {
+        reportErrorToSA("Unexpected error %d while fetching source file name", sfnRes);
+        return;
+      }
+    }
+
+    bool wasSet = true;
+    if (gotOne) {
+      // Really toggle this breakpoint
+      jvmdiError bpRes;
+      bpRes = jvmdi->SetBreakpoint(targetClass, targetMethod, targetLocation);
+      if (bpRes == JVMDI_ERROR_DUPLICATE) {
+        bpRes = jvmdi->ClearBreakpoint(targetClass, targetMethod, targetLocation);
+        wasSet = false;
+      }
+      if (bpRes != JVMDI_ERROR_NONE) {
+        reportErrorToSA("Unexpected error %d while setting or clearing breakpoint at bci %d, line %d",
+                        bpRes, targetLocation, targetLineNumber);
+        return;
+      }
+    } else {
+      saCmdBkptResWasError = 0;
+      reportErrorToSA("No debug information found covering this line");
+      return;
+    }
+
+    // Provide result
+    saCmdBkptResLineNumber = targetLineNumber;
+    saCmdBkptResBCI        = targetLocation;
+    saCmdBkptResWasSet     = (wasSet ? 1 : 0);
+    {
+      char* methodName;
+      char* methodSig;
+      if (jvmdi->GetMethodName(targetClass, targetMethod, &methodName, &methodSig)
+          == JVMDI_ERROR_NONE) {
+        JvmdiDeallocator mnd(methodName);
+        JvmdiDeallocator msd(methodSig);
+        strncpy(saCmdBkptResMethodName, methodName, SA_CMD_BUF_SIZE);
+        strncpy(saCmdBkptResMethodSig,  methodSig, SA_CMD_BUF_SIZE);
+      } else {
+        strncpy(saCmdBkptResMethodName, "<error>", SA_CMD_BUF_SIZE);
+        strncpy(saCmdBkptResMethodSig,  "<error>", SA_CMD_BUF_SIZE);
+      }
+    }
+    break;
+  }
+
+  default:
+    reportErrorToSA("Command %d not yet supported", saCmdType);
+    return;
+  }
+
+  // Successful command execution
+  saCmdResult = 0;
+  saCmdPending = 0;
+}
+
+static void
+saCommandThread(void *arg) {
+  JNIEnv* env = NULL;
+  if (jvm->GetEnv((void **) &env, JNI_VERSION_1_2) != JNI_OK) {
+    stop("Error while starting Serviceability Agent "
+         "command thread: could not get JNI environment");
+  }
+
+  while (1) {
+    // Wait for command
+    while (!saCmdPending) {
+      SLEEP();
+    }
+
+    executeOneCommand(env);
+  }
+}
+
+static void
+saEventHook(JNIEnv *env, JVMDI_Event *event)
+{
+  MonitorLocker ml(eventLock);
+
+  saEventKind = event->kind;
+
+  if (event->kind == JVMDI_EVENT_VM_INIT) {
+    // Create event lock
+    if (jvmdi->CreateRawMonitor("Serviceability Agent Event Lock", &eventLock)
+        != JVMDI_ERROR_NONE) {
+      stop("Unable to create Serviceability Agent's event lock");
+    }
+    // Start thread which receives commands from the SA.
+    jclass threadClass = env->FindClass("java/lang/Thread");
+    if (threadClass == NULL) stop("Unable to find class java/lang/Thread");
+    jstring threadName = env->NewStringUTF("Serviceability Agent Command Thread");
+    if (threadName == NULL) stop("Unable to allocate debug thread name");
+    jmethodID ctor = env->GetMethodID(threadClass, "<init>", "(Ljava/lang/String;)V");
+    if (ctor == NULL) stop("Unable to find appropriate constructor for java/lang/Thread");
+    // Allocate thread object
+    jthread thr = (jthread) env->NewObject(threadClass, ctor, threadName);
+    if (thr == NULL) stop("Unable to allocate debug thread's java/lang/Thread instance");
+    // Remember which thread this is
+    debugThreadObj = env->NewGlobalRef(thr);
+    if (debugThreadObj == NULL) stop("Unable to allocate global ref for debug thread object");
+    // Start thread
+    jvmdiError err;
+    if ((err = jvmdi->RunDebugThread(thr, &saCommandThread, NULL, JVMDI_THREAD_NORM_PRIORITY))
+        != JVMDI_ERROR_NONE) {
+      char buf[256];
+      sprintf(buf, "Error %d while starting debug thread", err);
+      stop(buf);
+    }
+    // OK, initialization is done
+    return;
+  }
+
+  if (!saAttached) {
+    return;
+  }
+
+  switch (event->kind) {
+  case JVMDI_EVENT_EXCEPTION: {
+    fprintf(stderr, "SA: Exception thrown -- ignoring\n");
+    saExceptionThread        = event->u.exception.thread;
+    saExceptionClass         = event->u.exception.clazz;
+    saExceptionMethod        = event->u.exception.method;
+    saExceptionLocation      = event->u.exception.location;
+    saExceptionException     = event->u.exception.exception;
+    saExceptionCatchClass    = event->u.exception.catch_clazz;
+    saExceptionCatchClass    = event->u.exception.catch_clazz;
+    saExceptionCatchMethod   = event->u.exception.catch_method;
+    saExceptionCatchLocation = event->u.exception.catch_location;
+    //    saEventPending = 1;
+    break;
+  }
+
+  case JVMDI_EVENT_BREAKPOINT: {
+    saBreakpointThread       = event->u.breakpoint.thread;
+    saBreakpointClass        = event->u.breakpoint.clazz;
+    saBreakpointMethod       = event->u.breakpoint.method;
+    saBreakpointLocation     = event->u.breakpoint.location;
+    saEventPending = 1;
+    break;
+  }
+
+  default:
+    break;
+  }
+
+  while (saAttached && saEventPending) {
+    SLEEP();
+  }
+}
+
+extern "C" {
+JNIEXPORT jint JNICALL
+JVM_OnLoad(JavaVM *vm, char *options, void *reserved)
+{
+  jvm = vm;
+  if (jvm->GetEnv((void**) &jvmdi, JVMDI_VERSION_1) != JNI_OK) {
+    return -1;
+  }
+  if (jvmdi->SetEventHook(&saEventHook) != JVMDI_ERROR_NONE) {
+    return -1;
+  }
+  return 0;
+}
+};