澳门新浦京娱乐游戏JVM源码分析之javaagent原理完全解读

澳门新浦京娱乐游戏 3

概述

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

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

想象一下可以让程序按照我们预期的逻辑去执行,听起来是不是挺酷的。

前一节讲述了基于JVMTI如何实现Agent,还有一种是基于Java Instrument
API实现Agent,可以在Java代码层面编写Agent代码,而非基于C++/C的代码,具体使用可参考《Java
Instrument 功能使用及原理》:

JVMTI

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));

-javaagent:为开头的默认为instrument的agent;

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_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。

那么以上这两种Agent实现方式,又是在JVMTI源码中如何运行工作呢?

javaagent

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

在JVM启动时,会读取JVM命令行参数
-agentlib -agentpath -javaagent并构建了Agent
Library链表
初始化 Agent 代码如下

instrument agent

instrument
agent实现了Agent_OnLoad和Agent_OnAttach两方法,也就是说在使用时,agent既可以在启动时加载,也可以在运行时动态加载。其中启动时加载还可以通过类似-javaagent:myagent.jar的方式来间接加载instrument
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。
if (match_option(option, "-agentlib:", &tail) || (is_absolute_path = match_option(option, "-agentpath:", &tail))) { if(tail != NULL) { const char* pos = strchr(tail, '='); size_t len = (pos == NULL) ? strlen : pos - tail; char* name = strncpy(NEW_C_HEAP_ARRAY(char, len + 1), tail, len); name[len] = ''; char *options = NULL; if(pos != NULL) { options = strcpy(NEW_C_HEAP_ARRAY(char, strlen + 1), pos + 1); } if ((strcmp(name, "hprof") == 0) || (strcmp(name, "jdwp") == 0)) { warning("profiling and debugging agents are not supported with Kernel VM"); } else if // JVMTI_KERNEL 构建Agent Library链表 add_init_agent(name, options, is_absolute_path); } } else if (match_option(option, "-javaagent:", &tail)) { // -javaagent if(tail != NULL) { char *options = strcpy(NEW_C_HEAP_ARRAY(char, strlen + 1), tail); // 构建Agent Library链表 add_init_agent("instrument", options, false); } // -Xnoclassgc }

在启动时加载instrument agent

正如前面“概述”里提到的方式,就是启动时加载instrument
agent,具体过程都在`InvocationAdapter.c`的`Agent_OnLoad`方法里,这里简单描述下过程:

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

在启动JVM
create_vm时,对agent链表中的每个agent库,加载所指定的动态库,
并调用里面的Agent_OnLoad方法
,比如:对于Java Instrument
Agent加载就是对libinstrument的动态库instrument.so加载

在运行时加载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方法
// Create agents for -agentlib: -agentpath: and converted -Xrun void Threads::create_vm_init_agents() { extern struct JavaVM_ main_vm; AgentLibrary* agent; JvmtiExport::enter_onload_phase(); for (agent = Arguments::agents(); agent != NULL; agent = agent->next { OnLoadEntry_t on_load_entry = lookup_agent_on_load; if (on_load_entry != NULL) { // 调用 Agent_OnLoad 函数 jint err = (*on_load_entry)(&main_vm, agent->options; if (err != JNI_OK) { vm_exit_during_initialization("agent library failed to init", agent->name; } } else { vm_exit_during_initialization("Could not find Agent_OnLoad function in the agent library", agent->name; } } JvmtiExport::enter_primordial_phase(); }

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里。

在方法Agent_OnLoad中创建一个新的 JPLISAgent(Java Programming Language
Instrumentation Services
Agent),初始化了类和包里的配置文件,并且同时从Vm环境中获取了 jvmtiEnv
的环境

Class Transform的实现

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

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *tail, void * reserved) { JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE; jint result = JNI_OK; JPLISAgent * agent = NULL; // 创建一个新的JPLISAgent对象 initerror = createNewJPLISAgent(vm, &agent); if ( initerror == JPLIS_INIT_ERROR_NONE ) { if (parseArgumentTail(tail, &jarfile, &options) != 0) { fprintf(stderr, "-javaagent: memory allocation failure.n"); return JNI_ERR; } attributes = readAttributes; if (attributes == NULL) { fprintf(stderr, "Error opening zip file or JAR manifest missing : %sn", jarfile); free; if (options != NULL) free; return JNI_ERR; } premainClass = getAttribute(attributes, "Premain-Class"); if (premainClass == NULL) { fprintf(stderr, "Failed to find Premain-Class manifest attribute in %sn", jarfile); free; if (options != NULL) free; freeAttributes(attributes); return JNI_ERR; } /* * Add to the jarfile 把jar文件追加到agent的classpath中。 */ appendClassPath(agent, jarfile); …… } 

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_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 */

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

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

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

在代码中,可以看到在
读取jar的配置文件MANIFEST里Premain-Class,并且把jar文件追加到agent的class
path中

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);

      }

    }

  }

以下是JVMTI的一些回调接口,通过这些回调接口设置回调函数指针:

javaagent的其他小众功能

javaagent除了做字节码上面的修改之外,其实还有一些小功能,有时候还是挺有用的

  • 获取所有已经被加载的类:Class[] getAllLoadedClasses();
  • 获取所有已经初始化了的类: Class[] getInitiatedClasses(ClassLoader
    loader);
  • 获取某个对象的大小: long getObjectSize(Object objectToSize);
  • 将某个jar加入到bootstrap classpath里优先其他jar被加载: void
    appendToBootstrapClassLoaderSearch(JarFile jarfile);
  • 将某个jar加入到classpath里供appclassloard去加载:void
    appendToSystemClassLoaderSearch(JarFile jarfile);
  • 设置某些native方法的前缀,主要在找native方法的时候做规则匹配: void
    setNativeMethodPrefix(ClassFileTransformer transformer, String
    prefix)。
typedef struct { /* 50 : VM Initialization Event */ jvmtiEventVMInit VMInit; /* 51 : VM Death Event */ jvmtiEventVMDeath VMDeath; /* 52 : Thread Start */ jvmtiEventThreadStart ThreadStart; /* 53 : Thread End */ jvmtiEventThreadEnd ThreadEnd; /* 54 : Class File Load Hook */ jvmtiEventClassFileLoadHook ClassFileLoadHook; /* 55 : Class Load */ jvmtiEventClassLoad ClassLoad; /* 56 : Class Prepare */ jvmtiEventClassPrepare ClassPrepare; /* 57 : VM Start Event */ jvmtiEventVMStart VMStart; /* 58 : Exception */ jvmtiEventException Exception; /* 59 : Exception Catch */ jvmtiEventExceptionCatch ExceptionCatch; /* 60 : Single Step */ jvmtiEventSingleStep SingleStep; /* 61 : Frame Pop */ jvmtiEventFramePop FramePop; /* 62 : Breakpoint */ jvmtiEventBreakpoint Breakpoint; /* 63 : Field Access */ jvmtiEventFieldAccess FieldAccess; /* 64 : Field Modification */ jvmtiEventFieldModification FieldModification; /* 65 : Method Entry */ jvmtiEventMethodEntry MethodEntry; /* 66 : Method Exit */ jvmtiEventMethodExit MethodExit; /* 67 : Native Method Bind */ jvmtiEventNativeMethodBind NativeMethodBind; /* 68 : Compiled Method Load */ jvmtiEventCompiledMethodLoad CompiledMethodLoad; /* 69 : Compiled Method Unload */ jvmtiEventCompiledMethodUnload CompiledMethodUnload; /* 70 : Dynamic Code Generated */ jvmtiEventDynamicCodeGenerated DynamicCodeGenerated; /* 71 : Data Dump Request */ jvmtiEventDataDumpRequest DataDumpRequest; /* 72 */ jvmtiEventReserved reserved72; /* 73 : Monitor Wait */ jvmtiEventMonitorWait MonitorWait; /* 74 : Monitor Waited */ jvmtiEventMonitorWaited MonitorWaited; /* 75 : Monitor Contended Enter */ jvmtiEventMonitorContendedEnter MonitorContendedEnter; /* 76 : Monitor Contended Entered */ jvmtiEventMonitorContendedEntered MonitorContendedEntered; /* 77 */ jvmtiEventReserved reserved77; /* 78 */ jvmtiEventReserved reserved78; /* 79 */ jvmtiEventReserved reserved79; /* 80 : Resource Exhausted */ jvmtiEventResourceExhausted ResourceExhausted; /* 81 : Garbage Collection Start */ jvmtiEventGarbageCollectionStart GarbageCollectionStart; /* 82 : Garbage Collection Finish */ jvmtiEventGarbageCollectionFinish GarbageCollectionFinish; /* 83 : Object Free */ jvmtiEventObjectFree ObjectFree; /* 84 : VM Object Allocation */ jvmtiEventVMObjectAlloc VMObjectAlloc; } jvmtiEventCallbacks; 

虚拟机在创建create_vm的时候,初始化了JVMTI环境后会执行JvmtiExport::post_vm_initialized();
方法,代码如下:

void JvmtiExport::post_vm_initialized() { EVT_TRIG_TRACE(JVMTI_EVENT_VM_INIT, ("JVMTI Trg VM init event triggered" )); // can now enable events JvmtiEventController::vm_init(); JvmtiEnvIterator it; for (JvmtiEnv* env = it.first(); env != NULL; env = it.next { if (env->is_enabled(JVMTI_EVENT_VM_INIT)) { EVT_TRACE(JVMTI_EVENT_VM_INIT, ("JVMTI Evt VM init event sent" )); JavaThread *thread = JavaThread::current(); JvmtiThreadEventMark jem; JvmtiJavaThreadEventTransition jet; jvmtiEventVMInit callback = env->callbacks()->VMInit; if (callback != NULL) { // 调用了VMInit的回调函数 (*callback)(env->jvmti_external(), jem.jni_env(), jem.jni_thread; } } } } 

钩子方法是jvmtiEventClassFileLoadHook的回调方法,代码在classFileParser的文件中

instanceKlassHandle ClassFileParser::parseClassFile(symbolHandle name, Handle class_loader,Handle protection_domain, KlassHandle host_klass, GrowableArray<Handle>* cp_patches, symbolHandle& parsed_name,bool verify, TRAPS) { …… if (JvmtiExport::should_post_class_file_load_hook { unsigned char* ptr = cfs->buffer(); unsigned char* end_ptr = cfs->buffer() + cfs->length(); JvmtiExport::post_class_file_load_hook(name, class_loader, protection_domain, &ptr, &end_ptr, &cached_class_file_bytes, &cached_class_file_length); if (ptr != cfs->buffer { // JVMTI agent has modified class file data. // Set new class file stream using JVMTI agent modified // class file data. cfs = new ClassFileStream(ptr, end_ptr - ptr, cfs->source; set_stream; } } … }

jvmtiexport::post_class_file_load_hook函数最后
调用了post_to_env()函数

void post_to_env(JvmtiEnv* env, bool caching_needed) { unsigned char *new_data = NULL; jint new_len = 0; JvmtiClassFileLoadEventMark jem(_thread, _h_name, _class_loader, _h_protection_domain, _h_class_being_redefined); JvmtiJavaThreadEventTransition jet; JNIEnv* jni_env = (JvmtiEnv::get_phase() == JVMTI_PHASE_PRIMORDIAL)? NULL : jem.jni_env(); jvmtiEventClassFileLoadHook callback = env->callbacks()->ClassFileLoadHook; if (callback != NULL) { (*callback)(env->jvmti_external(), jni_env, jem.class_being_redefined(), jem.jloader(), jem.class_name(), jem.protection_domain(), _curr_len, _curr_data, &new_len, &new_data); } ...... } 

函数中调用了jvmtiEventClassFileLoadHook的回调函数,也就是刚才在结构体中定义的jvmtiEventCallbacks。钩子函数eventHandlerClassFileLoadHook

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; /* if something is internally inconsistent , just silently return without touching the buffer */ if ( environment != NULL ) { jthrowable outstandingException = preserveThrowable; 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); } } 

重要的是transformClassFile函数,看看它究竟做了啥事情:

transformedBufferObject = ->CallObjectMethod( jnienv, agent->mInstrumentationImpl, agent->mTransform, loaderObject, classNameStringObject, classBeingRedefined, protectionDomain, classFileBufferObject, is_retransformer); 

也就是调用了InstrumentationImpl里的transform方法,在InstrumentationImpl类里通过TransformerManager的transform的方法最终调用我们自定义的MyTransformer的类的transform方法

 private byte[] transform(ClassLoader var1, String var2, Class var3, ProtectionDomain var4, byte[] var5, boolean var6) { TransformerManager var7 = var6 ? this.mRetransfomableTransformerManager : this.mTransformerManager; return var7 == null ? null : var7.transform(var1, var2, var3, var4, var5); }

如上,那么钩子函数jvmtiEventClassFileLoadHook是何时注册的,回到刚才创建新的JPLISAgent代码中

JPLISInitializationError createNewJPLISAgent(JavaVM * vm, JPLISAgent **agent_ptr) { JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE; jvmtiEnv * jvmtienv = NULL; jint jnierror = JNI_OK; *agent_ptr = NULL; jnierror = ->GetEnv(vm, &jvmtienv,JVMTI_VERSION); if (jnierror != JNI_OK) { initerror = JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT; } else { JPLISAgent * agent = allocateJPLISAgent; if (agent == NULL) { initerror = JPLIS_INIT_ERROR_ALLOCATION_FAILURE; } else { initerror = initializeJPLISAgent(agent, vm, jvmtienv); if (initerror == JPLIS_INIT_ERROR_NONE) { *agent_ptr = agent; } else { deallocateJPLISAgent(jvmtienv, agent); } } /* don't leak envs */ if ( initerror != JPLIS_INIT_ERROR_NONE ) { jvmtiError jvmtierror = (*jvmtienv)->DisposeEnvironment; jplis_assert(jvmtierror == JVMTI_ERROR_NONE); } } return initerror; } 

函数initializeJPLISAgent初始化了JPLISAgent:

JPLISInitializationError initializeJPLISAgent( JPLISAgent * agent,JavaVM * vm,jvmtiEnv * jvmtienv) { …… checkCapabilities; jvmtierror == (*jvmtienv)->GetPhase(jvmtienv, &phase); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); if (phase == JVMTI_PHASE_LIVE) { return JPLIS_INIT_ERROR_NONE; } /* now turn on the VMInit event */ if ( jvmtierror == JVMTI_ERROR_NONE ) { jvmtiEventCallbacks callbacks; memset(&callbacks, 0, sizeof(callbacks)); callbacks.VMInit = &eventHandlerVMInit; jvmtierror = (*jvmtienv)->SetEventCallbacks( jvmtienv, &callbacks, sizeof(callbacks)); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); } …… } 

JPLISAgent里首先
注册了一个VMInit的初始化函数eventHandlerVMInit,跟踪eventHandlerVMInit函数

void JNICALL eventHandlerVMInit( jvmtiEnv * jvmtienv, JNIEnv * jnienv, jthread thread) { JPLISEnvironment * environment = NULL; jboolean success = JNI_FALSE; environment = getJPLISEnvironment; /* process the premain calls on the all the JPL agents */ if ( environment != NULL ) { jthrowable outstandingException = preserveThrowable; success = processJavaStart( environment->mAgent, jnienv); restoreThrowable(jnienv, outstandingException); } /* if we fail to start cleanly, bring down the JVM */ if ( !success ) { abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART); } } 

在processJavaStart里

jboolean processJavaStart(JPLISAgent * agent, JNIEnv * jnienv) { jboolean result; result = initializeFallbackError; jplis_assert; if  { result = createInstrumentationImpl(jnienv, agent); jplis_assert; } if  { result = setLivePhaseEventHandlers; jplis_assert; } if  { result = startJavaAgent(agent, jnienv, agent->mAgentClassName, agent->mOptionsString,agent->mPremainCaller); } if  { deallocateCommandLineData; } return result; } 

在setLivePhaseEventHandler函数中注册了,代码如下

callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook; 

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 */ }; 
  1. mNormalEnvironment:agent环境;
  2. mRetransformEnvironment:retransform环境;
  3. mInstrumentationImpl:sun自己提供的instrument对象;
  4. mPremainCallersun.instrument.InstrumentationImpl.loadClassAndCallPremain方法,agent启动时加载会被调用该方法;
  5. mAgentmainCallersun.instrument.InstrumentationImpl.loadClassAndCallAgentmain方法,agent
    attach动态加载agent的时会被调用该方法;
  6. mTransformsun.instrument.InstrumentationImpl.transform方法;
  7. mAgentClassName:javaagent的MANIFEST.MF里指定的Agent-Class
  8. mOptionsString:agent初始参数;
  9. mRedefineAvailable:MANIFEST.MF里的参数Can-Redefine-Classes:true
  10. mNativeMethodPrefixAvailable:MANIFEST.MF里的参数Can-Set-Native-Method-Prefix:true
  11. mIsRetransformer:MANIFEST.MF里的参数Can-Retransform-Classes:true

在startJavaAgent的方法中调用了启动JPLISAgent的方式,我们来看invokeJavaAgentMainMethod

jboolean invokeJavaAgentMainMethod(JNIEnv * jnienv, jobject instrumentationImpl, jmethodID mainCallingMethod, jstring className, jstring optionsString) { jboolean errorOutstanding = JNI_FALSE; jplis_assert(mainCallingMethod != NULL); if (mainCallingMethod != NULL ) { ->CallVoidMethod(jnienv, instrumentationImpl, mainCallingMethod, className, optionsString); errorOutstanding = checkForThrowable; if ( errorOutstanding ) { logThrowable; } checkForAndClearThrowable; } return !errorOutstanding; } 

在函数里,实际上是调用java类sun.instrument.InstrumentationImpl
类里的方法loadClassAndCallPremain。

 private void loadClassAndCallPremain(String var1, String var2) throws Throwable { this.loadClassAndStartAgent(var1, "premain", var2); } private void loadClassAndCallAgentmain(String var1, String var2) throws Throwable { this.loadClassAndStartAgent(var1, "agentmain", var2); }

继续查看Java的sun.instrument.InstrumentationImpl类的方法loadClassAndStartAgent:

private void loadClassAndStartAgent(String classname, String methodname, String optionsString) throws Throwable { ... try { m = javaAgentClass.getDeclaredMethod( methodname, new Class<?>[] { String.class, java.lang.instrument.Instrumentation.class } ); twoArgAgent = true; } catch (NoSuchMethodException x) { // remember the NoSuchMethodException firstExc = x; } if (m == null) { // now try the declared 1-arg method try { m = javaAgentClass.getDeclaredMethod(methodname, new Class<?>[] { String.class }); } catch (NoSuchMethodException x) { // ignore this exception because we'll try // two arg inheritance next } } if (m == null) { // now try the inherited 2-arg method try { m = javaAgentClass.getMethod( methodname, new Class<?>[] { String.class, java.lang.instrument.Instrumentation.class } ); twoArgAgent = true; } catch (NoSuchMethodException x) { // ignore this exception because we'll try // one arg inheritance next } } if (m == null) { // finally try the inherited 1-arg method try { m = javaAgentClass.getMethod(methodname, new Class<?>[] { String.class }); } catch (NoSuchMethodException x) { // none of the methods exists so we throw the // first NoSuchMethodException as per 5.0 throw firstExc; } } // the premain method should not be required to be public, // make it accessible so we can call it // Note: The spec says the following: // The agent class must implement a public static premain method... setAccessible; // invoke the 1 or 2-arg method if (twoArgAgent) { m.invoke(null, new Object[] { optionsString, this }); } else { m.invoke(null, new Object[] { optionsString }); } // don't let others access a non-public premain method setAccessible; } 

在InstrumentationImpl的类中初始化了我们自定义的Transformer的premain方法:

public class MyInjectTransformer implements ClassFileTransformer{ public static void premain(String options, Instrumentation ins) { ins.addTransformer(new SQLInjectTransformer; } @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { return null; } } 

澳门新浦京娱乐游戏 1启动并创建JVM澳门新浦京娱乐游戏 2执行vmInit回调函数,注册jvmtiEventClassFileLoadHook回调函数,加载并初始化
Instrument
Agent澳门新浦京娱乐游戏 3加载解析Class文件,执行jvmtiEventClassFileLoadHook回调函数

  1. 在JVM启动时,通过JVM参数-javaagent,传入agent jar,Instrument
    Agent被加载;
  2. 在Instrument Agent
    初始化时,注册了JVMTI初始化函数eventHandlerVMinit
  3. 在JVM启动时,会调用初始化函数eventHandlerVMinit,启动了Instrument
    Agent,用sun.instrument.instrumentationImpl类里的方法loadClassAndCallPremain方法去初始化Premain-Class指定类的premain方法
  4. 初始化函数eventHandlerVMinit,注册了class解析的ClassFileLoadHook函数
  5. 在解析Class之前,JVM调用JVMTI的ClassFileLoadHook函数,钩子函数调用sun.instrument.instrumentationImpl类里的transform方法,通过TransformerManager的transformer方法最终调用我们自定义的Transformer类的transform方法
  6. 因为字节码在解析Class之前改的,直接使用修改后的字节码的数据流替代,最后进入Class解析,对整个Class解析无影响;
  7. 重新加载Class依然重新走5-6步骤;
You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图