java agent基础原理

本文重点讲述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的主要的功能如下:

  • 可以在加载java文件之前做拦截把字节码做修改
  • 可以在运行期将已经加载的类的字节码做变更,但是这种情况下会有很多的限制,后面会详细说
  • 还有其他的一些小众的功能
    • 获取所有已经被加载过的类
    • 获取所有已经被初始化过了的类(执行过了clinit方法,是上面的一个子集)
    • 获取某个对象的大小
    • 将某个jar加入到bootstrapclasspath里作为高优先级被bootstrapClassloader加载
    • 将某个jar加入到classpath里供AppClassloard去加载
    • 设置某些native方法的前缀,主要在查找native方法的时候做规则匹配

JVMTI

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

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

说到javaagent必须要讲的是一个叫做instrument的JVMTIAgent(linux下对应的动态库是libinstrument.so),因为就是它来实现javaagent的功能的,另外instrument agent还有个别名叫JPLISAgent(Java Programming Language Instrumentation Services Agent),从这名字里也完全体现了其最本质的功能:就是专门为java语言编写的插桩服务提供支持的。

instrument agent

instrument agent实现了Agent_OnLoadAgent_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 */
};

这里解释下几个重要项:

  • mNormalEnvironment:主要提供正常的类transform及redefine功能的。
  • mRetransformEnvironment:主要提供类retransform功能的。
  • mInstrumentationImpl:这个对象非常重要,也是我们java agent和JVM进行交互的入口,或许写过javaagent的人在写premain以及agentmain方法的时候注意到了有个Instrumentation的参数,这个参数其实就是这里的对象。
  • mPremainCaller:指向sun.instrument.InstrumentationImpl.loadClassAndCallPremain方法,如果agent是在启动的时候加载的,那该方法会被调用。
  • mAgentmainCaller:指向sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain方法,该方法在通过attach的方式动态加载agent的时候调用。
  • mTransform:指向sun.instrument.InstrumentationImpl.transform方法。
  • mAgentClassName:在我们javaagent的MANIFEST.MF里指定的Agent-Class
  • mOptionsString:传给agent的一些参数。
  • mRedefineAvailable:是否开启了redefine功能,在javaagent的MANIFEST.MF里设置Can-Redefine-Classes:true
  • mNativeMethodPrefixAvailable:是否支持native方法前缀设置,通样在javaagent的MANIFEST.MF里设置Can-Set-Native-Method-Prefix:true
  • mIsRetransformer:如果在javaagent的MANIFEST.MF文件里定义了Can-Retransform-Classes:true,那将会设置mRetransformEnvironment的mIsRetransformer为true。

启动时加载instrument agent

正如『概述』里提到的方式,就是启动的时候加载instrument agent,具体过程都在InvocationAdapter.cAgent_OnLoad方法里,简单描述下过程:

  • 创建并初始化JPLISAgent
  • 监听VMInit事件,在vm初始化完成之后做下面的事情:
    • 创建InstrumentationImpl对象
    • 监听ClassFileLoadHook事件
    • 调用InstrumentationImpl的loadClassAndCallPremain方法,在这个方法里会去调用javaagent里MANIFEST.MF里指定的Premain-Class类的premain方法
  • 解析javaagent里MANIFEST.MF里的参数,并根据这些参数来设置JPLISAgent里的一些内容

运行时加载instrument agent

运行时加载的方式,大致按照下面的方式来操作:

VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(agentPath, agentArgs);

上面会通过jvm的attach机制来请求目标jvm加载对应的agent,过程大致如下:

  • 创建并初始化JPLISAgent
  • 解析javaagent里MANIFEST.MF里的参数
  • 创建InstrumentationImpl对象
  • 监听ClassFileLoadHook事件
  • 调用InstrumentationImpl的loadClassAndCallAgentmain方法,在这个方法里会去调用javaagent里MANIFEST.MF里指定的Agent-Class类的agentmain方法

instrument agent的ClassFileLoadHook回调实现

不管是启动时还是运行时加载的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的实现

这里说的class transform其实是狭义的,主要是针对第一次类文件加载的时候就要求被transform的场景,在加载类文件的时候发出ClassFileLoad的事件,然后交给instrumenat agent来调用javaagent里注册的ClassFileTransformer实现字节码的修改。

Class Redefine的实现

类重新定义,这是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_RedefineClassesVM_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 */

这个过程我尽量用语言来描述清楚,不详细贴代码了,因为代码量实在有点大:

  • 挨个遍历要批量重定义的jvmtiClassDefinition
  • 然后读取新的字节码,如果有关注ClassFileLoadHook事件的,还会走对应的transform来对新的字节码再做修改
  • 字节码解析好,创建一个klassOop对象
  • 对比新老类,并要求如下:
    • 父类是同一个
    • 实现的接口数也要相同,并且是相同的接口
    • 类访问符必须一致
    • 字段数和字段名要一致
    • 新增或删除的方法必须是private static/final的
    • 可以修改方法
  • 对新类做字节码校验
  • 合并新老类的常量池
  • 如果老类上有断点,那都清除掉
  • 对老类做jit去优化
  • 对新老方法匹配的方法的jmethodid做更新,将老的jmethodId更新到新的method上
  • 新类的常量池的holer指向老的类
  • 将新类和老类的一些属性做交换,比如常量池,methods,内部类
  • 初始化新的vtable和itable
  • 交换annotation的method,field,paramenter
  • 遍历所有当前类的子类,修改他们的vtable及itable

上面是基本的过程,总的来说就是只更新了类里内容,相当于只更新了指针指向的内容,并没有更新指针,避免了遍历大量已有类对象对它们进行更新带来的开销。

Class Retransform的实现

retransform class可以简单理解为回滚操作,具体回滚到哪个版本,这个需要看情况而定,下面不管那种情况都有一个前提,那就是javaagent已经要求要有retransform的能力了:

  • 如果类是在第一次加载的的时候就做了transform,那么做retransform的时候会将代码回滚到transform之后的代码
  • 如果类是在第一次加载的的时候没有任何变化,那么做retransform的时候会将代码回滚到最原始的类文件里的字节码
  • 如果类已经被加载了,期间类可能做过多次redefine(比如被另外一个agent做过),但是接下来加载一个新的agent要求有retransform的能力了,然后对类做redefine的动作,那么retransform的时候会将代码回滚到上一个agent最后一次做redefine后的字节码

我们从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...");
}

转自:https://blog.csdn.net/ancinsdn/article/details/58276945
 
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值