JVM源码分析之javbbgent原理完

概述

本文重点讲述javaagent的具体实现,因为它面向的是我们java程序员,而且agent都是用java编写的,不需要太多的c/c++编程基础,不过这篇文章里也会讲到JVMTIAgent(c实现的),因为javaagent的运行还是依赖于一个特殊的JVMTIAgent。

对于javaagent或许大家都听过,甚至使用过,常见的用法大致如下:

java-javaagent:myagent.jar=mode=testTest

我们通过-javaagent来指定我们编写的agent的jar路径(./myagent.jar)及要传给agent的参数(mode=test),这样在启动的时候这个agent就可以做一些我们想要它做的事了。

javaagent的主要的功能如下:

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

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

JVMTI

JVMTI全称JVMToolInterface,是jvm暴露出来的一些供用户扩展的接口集合,JVMTI是基于事件驱动的,JVM每执行到一定的逻辑就会调用一些事件的回调接口(如果有的话),这些接口可以供开发者去扩展自己的逻辑。

比如说我们最常见的想在某个类的字节码文件读取之后类定义之前能修改相关的字节码,从而使创建的class对象是我们修改之后的字节码内容,那我们就可以实现一个回调函数赋给JvmtiEnv(JVMTI的运行时,通常一个JVMTIAgent对应一个jvmtiEnv,但是也可以对应多个)的回调方法集合里的ClassFileLoadHook,这样在接下来的类文件加载过程中都会调用到这个函数里来了,大致实现如下:

jvmtiEventCallbackscallbacks;jvmtiEnv*jvmtienv=jvmti(agent);jvmtiErrorjvmtierror;memset(callbacks,0,sizeof(callbacks));callbacks.ClassFileLoadHook=eventHandlerClassFileLoadHook;jvmtierror=(*jvmtienv)-SetEventCallbacks(jvmtienv,callbacks,sizeof(callbacks));JVMTIAgent

JVMTIAgent其实就是一个动态库,利用JVMTI暴露出来的一些接口来干一些我们想做但是正常情况下又做不到的事情,不过为了和普通的动态库进行区分,它一般会实现如下的一个或者多个函数:

JNIEXPORTjintJNICALLAgent_OnLoad(JavaVM*vm,char*options,void*reserved);JNIEXPORTjintJNICALLAgent_OnAttach(JavaVM*vm,char*options,void*reserved);JNIEXPORTvoidJNICALLAgent_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自带的jdwpagent来实现的,只是由于eclipse等工具在没让你察觉的情况下将相关参数(类似-agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:)给自动加到程序启动参数列表里了,其中agentlib参数就是用来跟要加载的agent的名字,比如这里的jdwp(不过这不是动态库的名字,而JVM是会做一些名称上的扩展,比如在linux下会去找libjdwp.so的动态库进行加载,也就是在名字的基础上加前缀lib,再加后缀.so),接下来会跟一堆相关的参数,会将这些参数传给Agent_OnLoad或者Agent_OnAttach函数里对应的options参数。

javaagent

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

instrumentagent

instrumentagent实现了Agent_OnLoad和Agent_OnAttach两方法,也就是说我们在用它的时候既支持启动的时候来加载agent,也支持在运行期来动态来加载这个agent,其中启动时加载agent还可以通过类似-javaagent:myagent.jar的方式来间接加载instrumentagent,运行期动态加载agent依赖的是jvm的attach机制JVMAttach机制实现,通过发送load命令来加载agent。

instrumentagent的核心数据结构如下:

struct_JPLISAgent{JavaVM*mJVM;/*handletotheJVM*/JPLISEnvironmentmNormalEnvironment;/*foreverythingbutretransformstuff*/JPLISEnvironmentmRetransformEnvironment;/*forretransformstuffonly*/jobjectmInstrumentationImpl;/*handletotheInstrumentationinstance*/jmethodIDmPremainCaller;/*methodontheInstrumentationImplthatdoesthepremainstuff(cachedtosavelotsoflookups)*/jmethodIDmAgentmainCaller;/*methodontheInstrumentationImplforagentsloadedviaattachmechanism*/jmethodIDmTransform;/*methodontheInstrumentationImplthatdoestheclassfiletransform*/jbooleanmRedefineAvailable;/*cachedanswertodoesthisagentsupportredefine*/jbooleanmRedefineAdded;/*indicatesifcan_redefine_classescapabilityhasbeenadded*/jbooleanmNativeMethodPrefixAvailable;/*cachedanswertodoesthisagentsupportprefixing*/jbooleanmNativeMethodPrefixAdded;/*indicatesifcan_set_native_method_prefixcapabilityhasbeenadded*/charconst*mAgentClassName;/*agentclassname*/charconst*mOptionsString;/*-javaagentoptionsstring*/};struct_JPLISEnvironment{jvmtiEnv*mJVMTIEnv;/*theJVMTIenvironment*/JPLISAgent*mAgent;/*correspondingagent*/jbooleanmIsRetransformer;/*indicatesifspecialenvironment*/};

这里解释下几个重要项:

mNormalEnvironment:主要提供正常的类transform及redefine功能的。mRetransformEnvironment:主要提供类retransform功能的。mInstrumentationImpl:这个对象非常重要,也是我们javaagent和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。启动时加载instrumentagent

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

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

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

VirtualMachinevm=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方法instrumentagent的ClassFileLoadHook回调实现

不管是启动时还是运行时加载的instrumentagent都







































山东治疗白癜风的医院
中科医院曝光



转载请注明:http://www.92nongye.com/hxjs/hxjs/204616365.html