0 前言
前一节讲述了基于JVMTI如何实现Agent,还有一种是基于Java Instrument API实现Agent,可以在Java代码层面编写Agent代码,而非基于C++/C的代码,具体使用可参考《Java Instrument 功能使用及原理》:
以 -javaagent:为开头的默认为instrument的agent;
那么以上这两种Agent实现方式,又是在JVMTI源码中如何运行工作呢?
1 初始化 Agent
在JVM启动时,会读取JVM命令行参数 -agentlib -agentpath -javaagent
,并构建了Agent Library链表。初始化 Agent 代码如下:
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(tail) : pos - tail;
char* name = strncpy(NEW_C_HEAP_ARRAY(char, len + 1), tail, len);
name[len] = '\0';
char *options = NULL;
if(pos != NULL) {
options = strcpy(NEW_C_HEAP_ARRAY(char, strlen(pos + 1) + 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(tail) + 1), tail);
// 构建Agent Library链表
add_init_agent("instrument", options, false);
}
// -Xnoclassgc
}
2 加载Agent链接库
在启动JVM create_vm
时,对agent链表中的每个agent库,加载所指定的动态库, 并调用里面的Agent_OnLoad方法,比如:对于Java Instrument Agent加载就是对libinstrument的动态库instrument.so加载:
// 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(agent);
if (on_load_entry != NULL) {
// 调用 Agent_OnLoad 函数
jint err = (*on_load_entry)(&main_vm, agent->options(), NULL);
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();
}
3 创建 Instrument JPLISAgent
在方法Agent_OnLoad中创建一个新的 JPLISAgent(Java Programming Language Instrumentation Services Agent),初始化了类和包里的配置文件,并且同时从Vm环境中获取了 jvmtiEnv 的环境。
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(jarfile);
if (attributes == NULL) {
fprintf(stderr, "Error opening zip file or JAR manifest missing : %s\n", jarfile);
free(jarfile);
if (options != NULL) free(options);
return JNI_ERR;
}
premainClass = getAttribute(attributes, "Premain-Class");
if (premainClass == NULL) {
fprintf(stderr, "Failed to find Premain-Class manifest attribute in %s\n", jarfile);
free(jarfile);
if (options != NULL) free(options);
freeAttributes(attributes);
return JNI_ERR;
}
/*
* Add to the jarfile 把jar文件追加到agent的classpath中。
*/
appendClassPath(agent, jarfile);
……
}
在代码中,可以看到在 读取jar的配置文件MANIFEST里Premain-Class,并且把jar文件追加到agent的class path中。
4 JVMTI 回调接口注册与执行
以下是JVMTI的一些回调接口,通过这些回调接口设置回调函数指针:
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;
4.1 执行jvmtiEventVMInit的回调函数
虚拟机在创建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(env)) {
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(thread);
JvmtiJavaThreadEventTransition jet(thread);
jvmtiEventVMInit callback = env->callbacks()->VMInit;
if (callback != NULL) {
// 调用了VMInit的回调函数
(*callback)(env->jvmti_external(), jem.jni_env(), jem.jni_thread());
}
}
}
}
4.2 执行jvmtiEventClassFileLoadHook的回调函数
钩子方法是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(cfs);
}
}
…
}
在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(_thread);
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(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方法。
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);
}
4.3 注册钩子函数jvmtiEventClassFileLoadHook
如上,那么钩子函数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 = (*vm)->GetEnv(vm,(void **) &jvmtienv,JVMTI_VERSION);
if (jnierror != JNI_OK) {
initerror = JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT;
} else {
JPLISAgent * agent = allocateJPLISAgent(jvmtienv);
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(jvmtienv);
jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
}
}
return initerror;
}
函数initializeJPLISAgent初始化了JPLISAgent:
JPLISInitializationError initializeJPLISAgent( JPLISAgent * agent,JavaVM * vm,jvmtiEnv * jvmtienv) {
……
checkCapabilities(agent);
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(jvmtienv);
/* process the premain calls on the all the JPL agents */
if ( environment != NULL ) {
jthrowable outstandingException = preserveThrowable(jnienv);
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(jnienv);
jplis_assert(result);
if ( result ) {
result = createInstrumentationImpl(jnienv, agent);
jplis_assert(result);
}
if ( result ) {
result = setLivePhaseEventHandlers(agent);
jplis_assert(result);
}
if ( result ) {
result = startJavaAgent(agent, jnienv, agent->mAgentClassName, agent->mOptionsString,agent->mPremainCaller);
}
if ( result ) {
deallocateCommandLineData(agent);
}
return result;
}
在setLivePhaseEventHandler函数中注册了,代码如下:
callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook;
5 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。
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(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;
}
}
6 工作原理及运行时序图
6.1 启动并创建JVM,注册vmInit回调函数
6.2 执行vmInit回调函数,注册jvmtiEventClassFileLoadHook回调函数,加载并初始化 Instrument Agent
6.3 加载解析Class文件,执行jvmtiEventClassFileLoadHook回调函数
6.4 以-javaagent为例,工作原理
- 在JVM启动时,通过JVM参数-javaagent,传入agent jar,Instrument Agent被加载;
- 在Instrument Agent 初始化时,注册了JVMTI初始化函数eventHandlerVMinit;
- 在JVM启动时,会调用初始化函数eventHandlerVMinit,启动了Instrument Agent,用sun.instrument.instrumentationImpl类里的方法loadClassAndCallPremain方法去初始化Premain-Class指定类的premain方法;
- 初始化函数eventHandlerVMinit,注册了class解析的ClassFileLoadHook函数;
- 在解析Class之前,JVM调用JVMTI的ClassFileLoadHook函数,钩子函数调用sun.instrument.instrumentationImpl类里的transform方法,通过TransformerManager的transformer方法最终调用我们自定义的Transformer类的transform方法;
- 因为字节码在解析Class之前改的,直接使用修改后的字节码的数据流替代,最后进入Class解析,对整个Class解析无影响;
- 重新加载Class依然重新走5-6步骤;