本文重点讲述javaagent的具体实现,因为它面向的是我们java程序员,而且agent都是用java编写的,不需要太多的c/c++编程基础,不过这篇文章里也会讲到JVMTIAgent(c实现的),因为javaagent的运行还是依赖于一个特殊的JVMTIAgent。
对于javaagent或许大家都听过,甚至使用过,常见的用法大致如下:
java -javaagent:myagent.jar=mode=test Test
我们通过-javaagent来指定我们编写的agent的jar路径(./myagent.jar)及要传给agent的参数(mode=test),这样在启动的时候这个agent就可以做一些我们想要它做的事了。
javaagent的主要的功能如下:
JVM Tool Interface,是jvm暴露出来的一些供用户扩展的接口集合,JVMTI是基于事件驱动的,JVM每执行到一定的逻辑就会调用一些事件的回调接口(如果有的话),这些接口可以供开发者去扩展自己的逻辑。
比如说我们最常见的想在某个类的字节码文件读取之后类定义之前能修改相关的字节码,从而使创建的class对象是我们修改之后的字节码内容,那我们就可以实现一个回调函数赋给JvmtiEnv(JVMTI的运行时,通常一个JVMTIAgent对应一个jvmtiEnv,但是也可以对应多个)的回调方法集合里的ClassFileLoadHook,这样在接下来的类文件加载过程中都会调用到这个函数里来了,大致实现如下:
jvmtiEventCallbacks callbacks;
jvmtiEnv * jvmtienv = jvmti(agent);
jvmtiError jvmtierror;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook;
jvmtierror = (*jvmtienv)->SetEventCallbacks( jvmtienv,
&callbacks,
sizeof(callbacks));
JVMTIAgent其实就是一个动态库,利用JVMTI暴露出来的一些接口来干一些我们想做但是正常情况下又做不到的事情,不过为了和普通的动态库进行区分,它一般会实现如下的一个或者多个函数:
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *vm, char *options, void *reserved);
JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char* options, void* reserved);
JNIEXPORT void JNICALL
Agent_OnUnload(JavaVM *vm);
Agent_OnLoad
函数,如果agent是在启动的时候加载的,也就是在vm参数里通过-agentlib来指定,那在启动过程中就会去执行这个agent里的Agent_OnLoad
函数。Agent_OnAttach
函数,如果agent不是在启动的时候加载的,是我们先attach到目标进程上,然后给对应的目标进程发送load命令来加载agent,在加载过程中就会调用Agent_OnAttach
函数。Agent_OnUnload
函数,在agent做卸载的时候调用,不过貌似基本上很少实现它。其实我们每天都在和JVMTIAgent打交道,只是你可能没有意识到而已,比如我们经常使用eclipse等工具对java代码做调试,其实就利用了jre自带的jdwp agent来实现的,只是由于eclipse等工具在没让你察觉的情况下将相关参数(类似-agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:61349
)给自动加到程序启动参数列表里了,其中agentlib参数就是用来跟要加载的agent的名字,比如这里的jdwp(不过这不是动态库的名字,而JVM是会做一些名称上的扩展,比如在linux下会去找libjdwp.so
的动态库进行加载,也就是在名字的基础上加前缀lib
,再加后缀.so
),接下来会跟一堆相关的参数,会将这些参数传给Agent_OnLoad
或者Agent_OnAttach
函数里对应的options
参数。
说到javaagent必须要讲的是一个叫做instrument的JVMTIAgent(linux下对应的动态库是libinstrument.so),因为就是它来实现javaagent的功能的,另外instrument agent还有个别名叫JPLISAgent(Java Programming Language Instrumentation Services Agent),从这名字里也完全体现了其最本质的功能:就是专门为java语言编写的插桩服务提供支持的。
instrument agent实现了Agent_OnLoad
和Agent_OnAttach
两方法,也就是说我们在用它的时候既支持启动的时候来加载agent,也支持在运行期来动态来加载这个agent,其中启动时加载agent还可以通过类似-javaagent:myagent.jar
的方式来间接加载instrument agent,运行期动态加载agent依赖的是jvm的attach机制JVM Attach机制实现,通过发送load命令来加载agent。
instrument agent的核心数据结构如下:
struct _JPLISAgent {
JavaVM * mJVM; /* handle to the JVM */
JPLISEnvironment mNormalEnvironment; /* for every thing but retransform stuff */
JPLISEnvironment mRetransformEnvironment;/* for retransform stuff only */
jobject mInstrumentationImpl; /* handle to the Instrumentation instance */
jmethodID mPremainCaller; /* method on the InstrumentationImpl that does the premain stuff (cached to save lots of lookups) */
jmethodID mAgentmainCaller; /* method on the InstrumentationImpl for agents loaded via attach mechanism */
jmethodID mTransform; /* method on the InstrumentationImpl that does the class file transform */
jboolean mRedefineAvailable; /* cached answer to "does this agent support redefine" */
jboolean mRedefineAdded; /* indicates if can_redefine_classes capability has been added */
jboolean mNativeMethodPrefixAvailable; /* cached answer to "does this agent support prefixing" */
jboolean mNativeMethodPrefixAdded; /* indicates if can_set_native_method_prefix capability has been added */
char const * mAgentClassName; /* agent class name */
char const * mOptionsString; /* -javaagent options string */
};
struct _JPLISEnvironment {
jvmtiEnv * mJVMTIEnv; /* the JVM TI environment */
JPLISAgent * mAgent; /* corresponding agent */
jboolean mIsRetransformer; /* indicates if special environment */
};
这里解释下几个重要项:
premain
以及agentmain
方法的时候注意到了有个Instrumentation的参数,这个参数其实就是这里的对象。sun.instrument.InstrumentationImpl.loadClassAndCallPremain
方法,如果agent是在启动的时候加载的,那该方法会被调用。sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain
方法,该方法在通过attach的方式动态加载agent的时候调用。sun.instrument.InstrumentationImpl.transform
方法。Agent-Class
。Can-Redefine-Classes:true
。Can-Set-Native-Method-Prefix:true
。Can-Retransform-Classes:true
,那将会设置mRetransformEnvironment的mIsRetransformer为true。正如『概述』里提到的方式,就是启动的时候加载instrument agent,具体过程都在InvocationAdapter.c
的Agent_OnLoad
方法里,简单描述下过程:
loadClassAndCallPremain
方法,在这个方法里会去调用javaagent里MANIFEST.MF里指定的Premain-Class
类的premain方法运行时加载的方式,大致按照下面的方式来操作:
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(agentPath, agentArgs);
上面会通过jvm的attach机制来请求目标jvm加载对应的agent,过程大致如下:
loadClassAndCallAgentmain
方法,在这个方法里会去调用javaagent里MANIFEST.MF里指定的Agent-Class
类的agentmain
方法不管是启动时还是运行时加载的instrument agent都关注着同一个jvmti事件---ClassFileLoadHook
,这个事件是在读取字节码文件之后回调时用的,这样可以对原来的字节码做修改,那这里面究竟是怎样实现的呢?
void JNICALL
eventHandlerClassFileLoadHook( jvmtiEnv * jvmtienv,
JNIEnv * jnienv,
jclass class_being_redefined,
jobject loader,
const char* name,
jobject protectionDomain,
jint class_data_len,
const unsigned char* class_data,
jint* new_class_data_len,
unsigned char** new_class_data) {
JPLISEnvironment * environment = NULL;
environment = getJPLISEnvironment(jvmtienv);
/* if something is internally inconsistent (no agent), just silently return without touching the buffer */
if ( environment != NULL ) {
jthrowable outstandingException = preserveThrowable(jnienv);
transformClassFile( environment->mAgent,
jnienv,
loader,
name,
class_being_redefined,
protectionDomain,
class_data_len,
class_data,
new_class_data_len,
new_class_data,
environment->mIsRetransformer);
restoreThrowable(jnienv, outstandingException);
}
}
先根据jvmtiEnv取得对应的JPLISEnvironment,因为上面我已经说到其实有两个JPLISEnvironment(并且有两个jvmtiEnv),其中一个专门做retransform的,而另外一个用来做其他的事情,根据不同的用途我们在注册具体的ClassFileTransformer的时候也是分开的,对于作为retransform用的ClassFileTransformer我们会注册到一个单独的TransformerManager里。
接着调用transformClassFile方法,由于函数实现比较长,我这里就不贴代码了,大致意思就是调用InstrumentationImpl对象的transform方法,根据最后那个参数来决定选哪个TransformerManager里的ClassFileTransformer对象们做transform操作。
private byte[]
transform( ClassLoader loader,
String classname,
Class classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer,
boolean isRetransformer) {
TransformerManager mgr = isRetransformer?
mRetransfomableTransformerManager :
mTransformerManager;
if (mgr == null) {
return null; // no manager, no transform
} else {
return mgr.transform( loader,
classname,
classBeingRedefined,
protectionDomain,
classfileBuffer);
}
}
public byte[]
transform( ClassLoader loader,
String classname,
Class classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
boolean someoneTouchedTheBytecode = false;
TransformerInfo[] transformerList = getSnapshotTransformerList();
byte[] bufferToUse = classfileBuffer;
// order matters, gotta run 'em in the order they were added
for ( int x = 0; x < transformerList.length; x++ ) {
TransformerInfo transformerInfo = transformerList[x];
ClassFileTransformer transformer = transformerInfo.transformer();
byte[] transformedBytes = null;
try {
transformedBytes = transformer.transform( loader,
classname,
classBeingRedefined,
protectionDomain,
bufferToUse);
}
catch (Throwable t) {
// don't let any one transformer mess it up for the others.
// This is where we need to put some logging. What should go here? FIXME
}
if ( transformedBytes != null ) {
someoneTouchedTheBytecode = true;
bufferToUse = transformedBytes;
}
}
// if someone modified it, return the modified buffer.
// otherwise return null to mean "no transforms occurred"
byte [] result;
if ( someoneTouchedTheBytecode ) {
result = bufferToUse;
}
else {
result = null;
}
return result;
}
以上是最终调到的java代码,可以看到已经调用到我们自己编写的javaagent代码里了,我们一般是实现一个ClassFileTransformer类,然后创建一个对象注册了对应的TransformerManager里。
这里说的class transform其实是狭义的,主要是针对第一次类文件加载的时候就要求被transform的场景,在加载类文件的时候发出ClassFileLoad的事件,然后交给instrumenat agent来调用javaagent里注册的ClassFileTransformer实现字节码的修改。
类重新定义,这是Instrumentation提供的基础功能之一,主要用在已经被加载过的类上,想对其进行修改,要做这件事,我们必须要知道两个东西,一个是要修改哪个类,另外一个是那个类你想修改成怎样的结构,有了这两信息之后于是你就可以通过InstrumentationImpl的下面的redefineClasses方法去操作了:
public void
redefineClasses(ClassDefinition[] definitions)
throws ClassNotFoundException {
if (!isRedefineClassesSupported()) {
throw new UnsupportedOperationException("redefineClasses is not supported in this environment");
}
if (definitions == null) {
throw new NullPointerException("null passed as 'definitions' in redefineClasses");
}
for (int i = 0; i < definitions.length; ++i) {
if (definitions[i] == null) {
throw new NullPointerException("element of 'definitions' is null in redefineClasses");
}
}
if (definitions.length == 0) {
return; // short-circuit if there are no changes requested
}
redefineClasses0(mNativeAgent, definitions);
}
在JVM里对应的实现是创建一个VM_RedefineClasses
的VM_Operation
,注意执行它的时候会stop the world的:
jvmtiError
JvmtiEnv::RedefineClasses(jint class_count, const jvmtiClassDefinition* class_definitions) {
//TODO: add locking
VM_RedefineClasses op(class_count, class_definitions, jvmti_class_load_kind_redefine);
VMThread::execute(&op);
return (op.check_error());
} /* end RedefineClasses */
这个过程我尽量用语言来描述清楚,不详细贴代码了,因为代码量实在有点大:
上面是基本的过程,总的来说就是只更新了类里内容,相当于只更新了指针指向的内容,并没有更新指针,避免了遍历大量已有类对象对它们进行更新带来的开销。
retransform class可以简单理解为回滚操作,具体回滚到哪个版本,这个需要看情况而定,下面不管那种情况都有一个前提,那就是javaagent已经要求要有retransform的能力了:
我们从InstrumentationImpl的retransformClasses
方法参数看猜到应该是做回滚操作,因为我们只指定了class
public void
retransformClasses(Class>[] classes) {
if (!isRetransformClassesSupported()) {
throw new UnsupportedOperationException(
"retransformClasses is not supported in this environment");
}
retransformClasses0(mNativeAgent, classes);
}
不过retransform的实现其实也是通过redefine的功能来实现,在类加载的时候有比较小的差别,主要体现在究竟会走哪些transform上,如果当前是做retransform的话,那将忽略那些注册到正常的TransformerManager里的ClassFileTransformer,而只会走专门为retransform而准备的TransformerManager的ClassFileTransformer,不然想象一下字节码又被无声无息改成某个中间态了。
private:
void post_all_envs() {
if (_load_kind != jvmti_class_load_kind_retransform) {
// for class load and redefine,
// call the non-retransformable agents
JvmtiEnvIterator it;
for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) {
if (!env->is_retransformable() && env->is_enabled(JVMTI_EVENT_CLASS_FILE_LOAD_HOOK)) {
// non-retransformable agents cannot retransform back,
// so no need to cache the original class file bytes
post_to_env(env, false);
}
}
}
JvmtiEnvIterator it;
for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) {
// retransformable agents get all events
if (env->is_retransformable() && env->is_enabled(JVMTI_EVENT_CLASS_FILE_LOAD_HOOK)) {
// retransformable agents need to cache the original class file
// bytes if changes are made via the ClassFileLoadHook
post_to_env(env, true);
}
}
}
附录
// Jagent.cpp : 定义 DLL 应用程序的导出函数。 // //Alt+F8 #include "stdafx.h" #define MAX_TOKEN_LENGTH 16 #define MAX_THREAD_NAME_LENGTH 512 #define MAX_METHOD_NAME_LENGTH 1024 static jvmtiEnv *jvmti = NULL; static jvmtiCapabilities capa; /* Global agent data structure */ typedef struct { /* JVMTI Environment */ jvmtiEnv *jvmti; jboolean vm_is_started; /* Data access Lock */ jrawMonitorID lock; } GlobalAgentData; static GlobalAgentData *gdata; static jlong combined_size; static int num_class_refs; static int num_field_refs; static int num_array_refs; static int num_classloader_refs; static int num_signer_refs; static int num_protection_domain_refs; static int num_interface_refs; static int num_static_field_refs; static int num_constant_pool_refs; /* Every JVMTI interface returns an error code, which should be checked * to avoid any cascading errors down the line. * The interface GetErrorName() returns the actual enumeration constant * name, making the error messages much easier to understand. */ static void check_jvmti_error(jvmtiEnv *jvmti, jvmtiError errnum, const char *str) { if ( errnum != JVMTI_ERROR_NONE ) { char *errnum_str; errnum_str = NULL; (void)(*jvmti).GetErrorName(errnum, &errnum_str); printf("ERROR: JVMTI: %d(%s): %s\n", errnum, (errnum_str==NULL?"Unknown":errnum_str), (str==NULL?"":str)); } } /* Enter a critical section by doing a JVMTI Raw Monitor Enter */ static void enter_critical_section(jvmtiEnv *jvmti) { jvmtiError error; error = (*jvmti).RawMonitorEnter(gdata->lock); check_jvmti_error(jvmti, error, "Cannot enter with raw monitor"); } /* Exit a critical section by doing a JVMTI Raw Monitor Exit */ static void exit_critical_section(jvmtiEnv *jvmti) { jvmtiError error; error = (*jvmti).RawMonitorExit(gdata->lock); check_jvmti_error(jvmti, error, "Cannot exit with raw monitor"); } void describe(jvmtiError err) { jvmtiError err0; char *descr; err0 = (*jvmti).GetErrorName( err, &descr); if (err0 == JVMTI_ERROR_NONE) { printf(descr); } else { printf("error [%d]", err); } } // Exception callback static void JNICALL callbackException(jvmtiEnv *jvmti_env, JNIEnv* env, jthread thr, jmethodID method, jlocation location, jobject exception, jmethodID catch_method, jlocation catch_location) { enter_critical_section(jvmti); { jvmtiError err, err1, err2, error; jvmtiThreadInfo info, info1; jvmtiThreadGroupInfo groupInfo; jint num_monitors; jobject *arr_monitors; jvmtiFrameInfo frames[5]; jint count; jint flag = 0; jint thr_st_ptr; jint thr_count; jthread *thr_ptr; jvmtiError err3; char *name; char *sig; char *gsig; err3 = (*jvmti).GetMethodName(method, &name, &sig, &gsig); if (err3 != JVMTI_ERROR_NONE) { printf("GetMethodName:%d\n", err); return; } printf("Got Exception from Method:%s%s\n", name, sig); err = (*jvmti).GetThreadInfo( thr, &info); if (err != JVMTI_ERROR_NONE) { printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err); describe(err); jvmtiPhase phase; jvmtiError phaseStat; phaseStat = (*jvmti).GetPhase(&phase); printf(" current phase is %d\n", phase); printf("\n"); } if (err == JVMTI_ERROR_NONE) { err1 = (*jvmti).GetThreadGroupInfo(info.thread_group, &groupInfo); if (err1 != JVMTI_ERROR_NONE) { printf("(GetThreadGroupInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err); describe(err); printf("\n"); } } if ((err == JVMTI_ERROR_NONE ) && (err1 == JVMTI_ERROR_NONE ) ) { printf("Current Thread is : %s and it belongs to Thread Group : %s\n", info.name, groupInfo.name); } err = (*jvmti).GetOwnedMonitorInfo(thr, &num_monitors, &arr_monitors); if (err != JVMTI_ERROR_NONE) { printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err); describe(err); printf("\n"); } printf("Number of Monitors Owned by this thread : %d\n", num_monitors); /* Get Thread Status */ err = (*jvmti).GetThreadState( thr, &thr_st_ptr); if (err != JVMTI_ERROR_NONE) { printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err); describe(err); printf("\n"); } if (err == JVMTI_ERROR_NONE) { printf("Thread Status\n"); printf("==============\n"); if ( thr_st_ptr & JVMTI_THREAD_STATE_ALIVE) { printf("Thread %s is Alive\n", info.name); flag = 1; } if ( thr_st_ptr & JVMTI_THREAD_STATE_TERMINATED) { printf("Thread %s has been Terminated\n", info.name); flag = 1; } if ( thr_st_ptr & JVMTI_THREAD_STATE_RUNNABLE ) { printf("Thread %s is Runnable\n", info.name); flag = 1; } if ( thr_st_ptr & JVMTI_THREAD_STATE_WAITING ) { printf("Thread %s waiting\n", info.name); flag = 1; } if ( thr_st_ptr & JVMTI_THREAD_STATE_WAITING_INDEFINITELY ) { printf("Thread %s waiting indefinitely\n", info.name); flag = 1; } if ( thr_st_ptr & JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT ) { printf("Thread %s waiting with Timeout\n", info.name); flag = 1; } if ( thr_st_ptr & JVMTI_THREAD_STATE_SLEEPING ) { printf("Thread %s Sleeping \n", info.name); flag = 1; } /** if ( thr_st_ptr & JVMTI_THREAD_STATE_WAITING_FOR_NOTIFICATION ) { printf("Thread %s Waiting for Notification \n", info.name); flag = 1; } **/ if ( thr_st_ptr & JVMTI_THREAD_STATE_IN_OBJECT_WAIT ) { printf("Thread %s is in Object Wait \n", info.name); flag = 1; } if ( thr_st_ptr & JVMTI_THREAD_STATE_PARKED ) { printf("Thread %s is Parked \n", info.name); flag = 1; } if ( thr_st_ptr & JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER ) { printf("Thread %s is blocked on monitor enter \n", info.name); flag = 1; } if ( thr_st_ptr & JVMTI_THREAD_STATE_SUSPENDED ) { printf("Thread %s is Suspended \n", info.name); flag = 1; } if ( thr_st_ptr & JVMTI_THREAD_STATE_INTERRUPTED ) { printf("Thread %s is Interrupted \n", info.name); flag = 1; } if ( thr_st_ptr & JVMTI_THREAD_STATE_IN_NATIVE ) { printf("Thread %s is in Native \n", info.name); flag = 1; } if ( flag != 1 ) { printf("Illegal value %d for Thread State\n", thr_st_ptr); } } /* Get All Threads */ err = (*jvmti).GetAllThreads( &thr_count, &thr_ptr); if (err != JVMTI_ERROR_NONE) { printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err); describe(err); printf("\n"); } if (err == JVMTI_ERROR_NONE && thr_count >= 1) { int i = 0; printf("Thread Count: %d\n", thr_count); for ( i=0; i < thr_count; i++) { /* Make sure the stack variables are garbage free */ (void)memset(&info1,0, sizeof(info1)); err1 = (*jvmti).GetThreadInfo(thr_ptr[i], &info1); if (err1 != JVMTI_ERROR_NONE) { printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err1); describe(err1); printf("\n"); } printf("Running Thread#%d: %s, Priority: %d, context class loader:%s\n", i+1,info1.name, info1.priority,(info1.context_class_loader == NULL ? ": NULL" : "Not Null")); /* Every string allocated by JVMTI needs to be freed */ err2 = (*jvmti).Deallocate((unsigned char *)info1.name); if (err2 != JVMTI_ERROR_NONE) { printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err2); describe(err2); printf("\n"); } } } /* Get Stack Trace */ err = (*jvmti).GetStackTrace( thr, 0, 5, frames, &count); if (err != JVMTI_ERROR_NONE) { printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err); describe(err); printf("\n"); } printf("Number of records filled: %d\n", count); if (err == JVMTI_ERROR_NONE && count >=1) { char *methodName; methodName = "yet_to_call()"; char *declaringClassName; jclass declaring_class; int i=0; printf("Exception Stack Trace\n"); printf("=====================\n"); printf("Stack Trace Depth: %d\n", count); for ( i=0; i < count; i++) { err = (*jvmti).GetMethodName( frames[i].method, &methodName, NULL, NULL); if (err == JVMTI_ERROR_NONE) { err = (*jvmti).GetMethodDeclaringClass( frames[i].method, &declaring_class); err = (*jvmti).GetClassSignature( declaring_class, &declaringClassName, NULL); if (err == JVMTI_ERROR_NONE) { printf("at method %s() in class %s\n", methodName, declaringClassName); } } } printf("\n"); err = (*jvmti).Deallocate((unsigned char *)methodName); err = (*jvmti).Deallocate((unsigned char *)declaringClassName); } } exit_critical_section(jvmti); } // VM Death callback static void JNICALL callbackVMDeath(jvmtiEnv *jvmti_env, JNIEnv* jni_env) { enter_critical_section(jvmti); { printf("Got VM Death event\n"); } exit_critical_section(jvmti); } /* Get a name for a jthread */ static void get_thread_name(jvmtiEnv *jvmti, jthread thread, char *tname, int maxlen) { jvmtiThreadInfo info; jvmtiError error; /* Make sure the stack variables are garbage free */ (void)memset(&info,0, sizeof(info)); /* Assume the name is unknown for now */ (void)strcpy(tname, "Unknown"); /* Get the thread information, which includes the name */ error = (*jvmti).GetThreadInfo( thread, &info); check_jvmti_error(jvmti, error, "Cannot get thread info"); /* The thread might not have a name, be careful here. */ if ( info.name != NULL ) { int len; /* Copy the thread name into tname if it will fit */ len = (int)strlen(info.name); if ( len < maxlen ) { (void)strcpy(tname, info.name); } /* Every string allocated by JVMTI needs to be freed */ error = (*jvmti).Deallocate( (unsigned char *)info.name); if (error != JVMTI_ERROR_NONE) { printf("(get_thread_name) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, error); describe(error); printf("\n"); } } } // VM init callback static void JNICALL callbackVMInit(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread) { enter_critical_section(jvmti); { char tname[MAX_THREAD_NAME_LENGTH]; static jvmtiEvent events[] = { JVMTI_EVENT_THREAD_START, JVMTI_EVENT_THREAD_END }; int i; jvmtiFrameInfo frames[5]; jvmtiError err, err1; jvmtiError error; jint count; /* The VM has started. */ printf("Got VM init event\n"); get_thread_name(jvmti_env , thread, tname, sizeof(tname)); printf("callbackVMInit: %s thread\n", tname); error = (*jvmti).SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, (jthread)NULL); check_jvmti_error(jvmti_env, error, "Cannot set event notification"); } exit_critical_section(jvmti); } /* JVMTI callback function. */ static jvmtiIterationControl JNICALL reference_object(jvmtiObjectReferenceKind reference_kind, jlong class_tag, jlong size, jlong* tag_ptr, jlong referrer_tag, jint referrer_index, void *user_data) { combined_size = combined_size + size; switch (reference_kind) { case JVMTI_REFERENCE_CLASS: num_class_refs = num_class_refs + 1; break; case JVMTI_REFERENCE_FIELD: num_field_refs = num_field_refs + 1; break; case JVMTI_REFERENCE_ARRAY_ELEMENT: num_array_refs = num_array_refs + 1; break; case JVMTI_REFERENCE_CLASS_LOADER: num_classloader_refs = num_classloader_refs + 1; break; case JVMTI_REFERENCE_SIGNERS: num_signer_refs = num_signer_refs + 1; break; case JVMTI_REFERENCE_PROTECTION_DOMAIN: num_protection_domain_refs = num_protection_domain_refs + 1; break; case JVMTI_REFERENCE_INTERFACE: num_interface_refs = num_interface_refs + 1; break; case JVMTI_REFERENCE_STATIC_FIELD: num_static_field_refs = num_static_field_refs + 1; break; case JVMTI_REFERENCE_CONSTANT_POOL: num_constant_pool_refs = num_constant_pool_refs + 1; break; default: break; } return JVMTI_ITERATION_CONTINUE; } static void JNICALL callbackVMObjectAlloc(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread, jobject object, jclass object_klass, jlong size) { char *methodName; char *className; char *declaringClassName; jclass declaring_class; jvmtiError err; if (size > 50) { err = (*jvmti).GetClassSignature(object_klass, &className, NULL); if (className != NULL) { printf("\ntype %s object allocated with size %d\n", className, (jint)size); } //print stack trace jvmtiFrameInfo frames[5]; jint count; int i; err = (*jvmti).GetStackTrace(NULL, 0, 5, (jvmtiFrameInfo*)&frames, &count); if (err == JVMTI_ERROR_NONE && count >= 1) { for (i = 0; i < count; i++) { err = (*jvmti).GetMethodName(frames[i].method, &methodName, NULL, NULL); if (err == JVMTI_ERROR_NONE) { err = (*jvmti).GetMethodDeclaringClass( frames[i].method, &declaring_class); err = (*jvmti).GetClassSignature( declaring_class, &declaringClassName, NULL); if (err == JVMTI_ERROR_NONE) { printf("at method %s in class %s\n", methodName, declaringClassName); } } } } //reset counters combined_size = 0; num_class_refs = 0; num_field_refs = 0; num_array_refs = 0; num_classloader_refs = 0; num_signer_refs = 0; num_protection_domain_refs = 0; num_interface_refs = 0; num_static_field_refs = 0; num_constant_pool_refs = 0; err = (*jvmti).IterateOverObjectsReachableFromObject(object, &reference_object, NULL); if ( err != JVMTI_ERROR_NONE ) { printf("Cannot iterate over reachable objects\n"); } printf("\nThis object has references to objects of combined size %d\n", (jint)combined_size); printf("This includes %d classes, %d fields, %d arrays, %d classloaders, %d signers arrays,\n", num_class_refs, num_field_refs, num_array_refs, num_classloader_refs, num_signer_refs); printf("%d protection domains, %d interfaces, %d static fields, and %d constant pools.\n\n", num_protection_domain_refs, num_interface_refs, num_static_field_refs, num_constant_pool_refs); err = (*jvmti).Deallocate((unsigned char *)className); err = (*jvmti).Deallocate((unsigned char *)methodName); err = (*jvmti).Deallocate( (unsigned char *)declaringClassName); } } jobject getLocalValue(JNIEnv *env,jvmtiEnv *jvmti_env, jthread thread, jint depth, jvmtiLocalVariableEntry *table, int index) { jobject result; jint iVal; jfloat fVal; jdouble dVal; jlong jVal; jvmtiError tiErr; jclass reflectClass; jmethodID valueOf; // Retreive switch (table[index].signature[0]) { case '[': // Array case 'L': // Object tiErr = (*jvmti_env).GetLocalObject( thread,depth, table[index].slot,&result); break; case 'J': // long tiErr = (*jvmti_env).GetLocalLong( thread,depth, table[index].slot,&jVal); break; case 'F': // float tiErr = (*jvmti_env).GetLocalFloat( thread,depth, table[index].slot,&fVal); break; case 'D': // double tiErr = (*jvmti_env).GetLocalDouble(thread,depth, table[index].slot,&dVal); break; // Integer types case 'I': // int case 'S': // short case 'C': // char case 'B': // byte case 'Z': // boolean tiErr = (*jvmti_env).GetLocalInt(thread,depth, table[index].slot,&iVal); break; // error type default: return NULL; } if (tiErr != JVMTI_ERROR_NONE) { return NULL; } // Box primitives switch (table[index].signature[0]) { case 'J': // long reflectClass = (*env).FindClass("java/lang/Long"); valueOf = (*env).GetStaticMethodID(reflectClass, "valueOf","(J)Ljava/lang/Long;"); result = (*env).CallStaticObjectMethod( reflectClass, valueOf, jVal); break; case 'F': // float reflectClass = (*env).FindClass( "java/lang/Float"); valueOf = (*env).GetStaticMethodID( reflectClass, "valueOf", "(F)Ljava/lang/Float;"); result = (*env).CallStaticObjectMethod( reflectClass, valueOf, fVal); break; case 'D': // double reflectClass = (*env).FindClass("java/lang/Double"); valueOf = (*env).GetStaticMethodID( reflectClass, "valueOf","(D)Ljava/lang/Double;"); result = (*env).CallStaticObjectMethod( reflectClass, valueOf, dVal); break; // INTEGER TYPES case 'I': // int reflectClass = (*env).FindClass( "java/lang/Integer"); valueOf = (*env).GetStaticMethodID( reflectClass, "valueOf", "(I)Ljava/lang/Integer;"); result = (*env).CallStaticObjectMethod( reflectClass, valueOf, iVal); break; case 'S': // short reflectClass = (*env).FindClass( "java/lang/Short"); valueOf = (*env).GetStaticMethodID( reflectClass, "valueOf","(S)Ljava/lang/Short;"); result = (*env).CallStaticObjectMethod( reflectClass, valueOf, iVal); break; case 'C': // char reflectClass = (*env).FindClass( "java/lang/Character"); valueOf = (*env).GetStaticMethodID( reflectClass, "valueOf","(C)Ljava/lang/Character;"); result = (*env).CallStaticObjectMethod( reflectClass, valueOf, iVal); break; case 'B': // byte reflectClass = (*env).FindClass( "java/lang/Byte"); valueOf = (*env).GetStaticMethodID( reflectClass, "valueOf","(B)Ljava/lang/Byte;"); result = (*env).CallStaticObjectMethod( reflectClass, valueOf, iVal); break; case 'Z': // boolean reflectClass = (*env).FindClass( "java/lang/Boolean"); valueOf = (*env).GetStaticMethodID( reflectClass, "valueOf","(Z)Ljava/lang/Boolean;"); result = (*env).CallStaticObjectMethod( reflectClass, valueOf, iVal); break; default: // jobject break; } return result; } static void JNICALL callbackMethodEntry(jvmtiEnv *jvmti_env,JNIEnv* jni_env,jthread thread,jmethodID method) { unsigned char *methodName; unsigned char *className; unsigned char *declaringClassName; jclass declaring_class; jvmtiError err; jvmtiThreadInfo info_ptr; jint entry_count_ptr; jvmtiLocalVariableEntry* table_ptr; jint max_ptr; jvmtiFrameInfo frames[2]; jint count; int i; //for get local variable value; jobject objV =NULL; jobject local; jclass localClass; jstring name; jstring sig; jstring gensig; err = (*jvmti).GetStackTrace(NULL, 0, 1, (jvmtiFrameInfo*)&frames, &count); if (err == JVMTI_ERROR_NONE && count >= 1) { for (i = 0; i < count; i++) { //err = (*jvmti).GetMethodName(frames.method, (char **)&methodName, NULL, NULL); if (err == JVMTI_ERROR_NONE) { //err = (*jvmti).GetMethodDeclaringClass(frames[i].method, &declaring_class); //err = (*jvmti).GetClassSignature(declaring_class, (char **)&declaringClassName, NULL); err = (*jvmti).GetMaxLocals(frames[i].method,&max_ptr); if(max_ptr >= 1) { //printf("max_ptr: %d\n",max_ptr); //err = (*jvmti).GetMethodLocation(method,&start_location_ptr,&end_location_ptr); //printf("strat_location_ptr:%s+++end_location_ptr:%s",start_location_ptr,end_location_ptr); err = jvmti->GetLocalVariableTable(frames[i].method,&entry_count_ptr,&table_ptr); //err = (*jvmti).GetLocalVariableTable(frames[i].method,&entry_count_ptr,&table_ptr); if(err != JVMTI_ERROR_NONE) { //printf("(GetThreadGroupInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err); //describe(err); //printf("\n"); if(err == JVMTI_ERROR_ABSENT_INFORMATION) { //printf("No local variable information\n"); }else { // remove //printf("(GetThreadGroupInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err); } } else if(err == JVMTI_ERROR_NONE) { printf("variable count :%d\n",entry_count_ptr); //printf("the first variable %s",variable_array_ptr[0]->name); if(entry_count_ptr>0){ //jvmti->GetLocalObject(thread,0,1,objV); localClass = (*jni_env).FindClass("com/Frame$LocalVariable"); if(err ==JVMTI_ERROR_INVALID_CLASS){ return; } name = (*jni_env).NewStringUTF(table_ptr[0].name); sig = (*jni_env).NewStringUTF(table_ptr[0].signature); if (table_ptr[0].generic_signature) { objV =getLocalValue(jni_env,jvmti_env, thread, 0, table_ptr, 0); gensig = (*jni_env).NewStringUTF(table_ptr[0].generic_signature); local = (*jni_env).NewObject(localClass, method, name, sig, gensig, objV); printf("local variable :%s\n",local); } else { gensig = NULL; } printf("Not found print variable.\n"); } } } } } } } JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved){ printf("~~~~start onLoad function.~~~~\n"); static GlobalAgentData data; jvmtiError error; jint res; jvmtiEventCallbacks callbacks; (void)memset((void*)&data, 0, sizeof(data)); gdata = &data; // We need to first get the jvmtiEnv* or JVMTI environment res = (*jvm).GetEnv((void **) &jvmti, JVMTI_VERSION_1_0); if (res != JNI_OK || jvmti == NULL) { // This means that the VM was unable to obtain this version of the // JVMTI interface, this is a fatal error. printf("ERROR: Unable to access JVMTI Version 1 (0x%x)," " is your J2SE a 1.5 or newer version?" " JNIEnv's GetEnv() returned %d\n", JVMTI_VERSION_1, res); } // Here we save the jvmtiEnv* for Agent_OnUnload(). gdata->jvmti = jvmti; (void)memset(&capa, 0, sizeof(jvmtiCapabilities)); capa.can_signal_thread = 1; capa.can_get_owned_monitor_info = 1; capa.can_generate_method_entry_events = 1; capa.can_generate_exception_events = 1; capa.can_generate_vm_object_alloc_events = 1; capa.can_tag_objects = 1; capa.can_access_local_variables = 1;// capa.can_generate_method_entry_events = 1;// capa.can_signal_thread = 1;//interrupt or stop thread capa.can_suspend = 1; error = (*jvmti).AddCapabilities( &capa); if(error == JVMTI_ERROR_NOT_AVAILABLE) { printf("error!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); } check_jvmti_error(jvmti, error, "Unable to get necessary JVMTI capabilities."); (void)memset(&callbacks, 0, sizeof(callbacks)); callbacks.VMInit = &callbackVMInit; // JVMTI_EVENT_VM_INIT callbacks.VMDeath = &callbackVMDeath; // JVMTI_EVENT_VM_DEATH callbacks.Exception = &callbackException;// JVMTI_EVENT_EXCEPTION callbacks.VMObjectAlloc = &callbackVMObjectAlloc;// JVMTI_EVENT_VM_OBJECT_ALLOC callbacks.MethodEntry = &callbackMethodEntry;//JVMTI_EVENT_METHOD_ENTRY error = (*jvmti).SetEventCallbacks( &callbacks, (jint)sizeof(callbacks)); check_jvmti_error(jvmti, error, "Cannot set jvmti callbacks"); error = (*jvmti).SetEventNotificationMode(JVMTI_ENABLE,JVMTI_EVENT_VM_INIT, (jthread)NULL); error = (*jvmti).SetEventNotificationMode(JVMTI_ENABLE,JVMTI_EVENT_VM_DEATH, (jthread)NULL); //error = (*jvmti).SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, (jthread)NULL); error = (*jvmti).SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, (jthread)NULL); check_jvmti_error(jvmti, error, "Cannot set event notification"); error = (*jvmti).CreateRawMonitor("agent data", &(gdata->lock)); check_jvmti_error(jvmti, error, "Cannot create raw monitor"); return JNI_OK; } JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm){ printf("this on Unload.. something todo.\n"); printf("release memery after running..."); }