Java Instrument (四) JPLISAgent

JPLISAgent的结构体

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:agent 的环境
  • mRetransformEnvironment:retransform环境
  • mInstrumentationImpl:sun自己提供的instrument的对象 
  • mPremainCaller:`sun.instrument.InstrumentationImpl.loadClassAndCallPremain`方法,agent启动时加载的会被调用该方法
  • mAgentmainCaller:`sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain`方法agent attach的方式动态加载agent的时候调用
  • mTransform:`sun.instrument.InstrumentationImpl.transform`方法
  • mAgentClassName:javaagent的MANIFEST.MF里指定的`Agent-Class`
  • mOptionsString:agent初始参数
  • mRedefineAvailable:MANIFEST.MF里的参数`Can-Redefine-Classes:true`
  • mNativeMethodPrefixAvailable:MANIFEST.MF里的参数`Can-Set-Native-Method-Prefix:true`。
  • 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 ) {
        (*jnienv)->CallVoidMethod(  jnienv,
                                    instrumentationImpl,
                                    mainCallingMethod,
                                    className,
                                    optionsString);
        errorOutstanding = checkForThrowable(jnienv);
        if ( errorOutstanding ) {
            logThrowable(jnienv);
        }
        checkForAndClearThrowable(jnienv);
    }
    return !errorOutstanding;
}

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

回到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(m, true);

        // 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(m, false);
    }

在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;
	}
	
}

钩子函数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(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);
    }
}
重要的是transformClassFile函数,我们来看看它究竟做了啥事情

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

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

总结

  • 1.  Instrument Agent 动态库通过JVM初始化,被动态加载。
  • 2.  Instrument Agent 初始化的时候,注册了JVMTI的初始化函数eventHandlerVMInit
  • 3.  JVM启动调用了初始化函数eventHandlerVMInit,启动了Instrument Agent ,用sun.instrument.InstrumentationImpl 类里的方法loadClassAndCallPremain 去初始话我们自定义的transformer里的premain方法
  • 4.  初始化函数注册了class解析的JVMTI的ClassFileLoadHook
  • 5.  在解析class的时候,JVM调用JVMTI的ClassFileLoadHook函数,钩子函数调用了sun.instrument.InstrumentationImpl 类里的transform方法,通过TransformerManager的transform的方法最终调用我们自定义的MyTransformer的类的transform方法
  • 6.  因为字节码是在解析class之前改的,通过直接替代修改后字节码的数据流,最后进入class的解析,对整个class解析无影响
  • 7.  重新加载class依然重新走5-6的步骤



你可能感兴趣的:(JPLISAgent)