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