jvmti agent的加载与回调函数的执行分析
JVM agent 的加载流程基本都一致,最近在学习java debug ,本文就结合jvmti impl的实现 jdwp agent 分析一下agent 的加载与回调函数的执行流程。
0、基础知识:agent的启动与卸载过程
启动
Agent 是在 Java 虚拟机启动之时加载的,这个加载处于虚拟机初始化的早期,在这个时间点上:
- 所有的 Java 类都未被初始化;
- 所有的对象实例都未被创建;
- 因而,没有任何 Java 代码被执行;
但在这个时候,我们已经可以: - 操作 JVMTI 的 Capability 参数;
- 使用系统参数;
动态库被加载之后,虚拟机会先寻找一个 Agent 入口函数:
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved)
在这个函数中,虚拟机传入了一个 JavaVM 指针,以及命令行的参数。通过 JavaVM,我们可以获得 JVMTI 的指针,并获得 JVMTI 函数的使用能力,所有的 JVMTI 函数都通过这个 jvmtiEnv 获取,不同的虚拟机实现提供的函数细节可能不一样,但是使用的方式是统一的。
jvmtiEnv jvmti;
(jvm)->GetEnv(jvm, &jvmti, JVMTI_VERSION_1_0);
这里传入的版本信息参数很重要,不同的 JVMTI 环境所提供的功能以及处理方式都可能有所不同,不过它在同一个虚拟机中会保持不变。命令行参数事实上就是上面启动命令行中的 options 部分,在 Agent 实现中需要进行解析并完成后续处理工作。参数传入的字符串仅仅在 Agent_OnLoad 函数里有效。
需要强调的是,这个时候由于虚拟机并未完成初始化工作,并不是所有的 JVMTI 函数都可以被使用。
Agent 还可以在运行时加载。具体说来,虚拟机会在运行时监听并接受 Agent 的加载,在这个时候,它会使用 Agent 的:
JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char *options, void *reserved);
同样的在这个初始化阶段,不是所有的 JVMTI 的 Capability 参数都处于可操作状态,而且 options 这个 char 数组在这个函数运行之后就会被丢弃,如果需要,需要做好保留工作。
Agent 的主要功能是通过一系列的在虚拟机上设置的回调(callback)函数完成的,一旦某些事件发生,Agent 所设置的回调函数就会被调用,来完成特定的需求。
卸载
最后,Agent 完成任务,或者虚拟机关闭的时候,虚拟机都会调用一个类似于类析构函数的方法来完成最后的清理任务,注意这个函数和虚拟机自己的 VM_DEATH 事件是不同的。
1、jvm启动时加载agent
LoadJavaVM -> JNI_CreateJavaVM -> Threads::create_vm
if (Arguments::init_agents_at_startup()){
create_vm_init_agents();
}
其中 init_agents_at_startup 的意思是传入参数中是否包含 agentlib/agentpath
2、create_vm_init_agents() 函数主要功能是找到dll, 然后找到dll中的Agent_Onload方法,然后调用这个方法进行agent初始化
create_vm_init_agents
核心函数是lookup_agent_on_load。 (定义在hotspot/src/share/vm/runtime/thread.cpp)
lookup_agent_on_load
里面的核心函数是 lookup_on_load 。主要是找到dll,然后找到里面的agent onload 方法。 定义如下:
lookup_on_load
找到Agent_onload方法,再回到create_vm_init_agents 。接下来的工作就是调用Agent_OnLoad方法,进行agent的初始化。
2、执行Agent_OnLoad,创建Instrument JPLISAgent, 并且初始化JPLISAgent过程中注册了event 的处理钩子。
Agent_OnLoad是一个外部的c函数,看一下。(定义位置: jdk7u/jdk/src/share/instrument/InvocationAdapter.c)
在方法Agent_OnLoad中创建一个新的 JPLISAgent(Java Programming Language Instrumentation Services Agent),初始化了类和包里的配置文件,并且同时从Vm环境中获取了 jvmtiEnv 的环境。
/*
* This will be called once for every -javaagent on the command line.
* Each call to Agent_OnLoad will create its own agent and agent data.
*
* The argument tail string provided to Agent_OnLoad will be of form
* <jarfile>[=<options>]. The tail string is split into the jarfile and
* options components. The jarfile manifest is parsed and the value of the
* Premain-Class attribute will become the agent's premain class. The jar
* file is then added to the system class path, and if the Boot-Class-Path
* attribute is present then all relative URLs in the value are processed
* to create boot class path segments to append to the boot class path.
*/
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 ) {
int oldLen, newLen;
char * jarfile;
char * options;
jarAttribute* attributes;
char * premainClass;
char * agentClass;
char * bootClassPath;
/*
* Parse <jarfile>[=options] into jarfile and options
*/
if (parseArgumentTail(tail, &jarfile, &options) != 0) {
fprintf(stderr, "-javaagent: memory allocation failure.\n");
return JNI_ERR;
}
/*
* Agent_OnLoad is specified to provide the agent options
* argument tail in modified UTF8. However for 1.5.0 this is
* actually in the platform encoding - see 5049313.
*
* Open zip/jar file and parse archive. If can't be opened or
* not a zip file return error. Also if Premain-Class attribute
* isn't present we return an error.
*/
// 读取jar文件
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;
}
// 从jar文件中获取Premain-Class
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的class path中。
appendClassPath(agent, jarfile);
/*
* The value of the Premain-Class attribute becomes the agent
* class name. The manifest is in UTF8 so need to convert to
* modified UTF8 (see JNI spec).
*/
oldLen = (int)strlen(premainClass);
newLen = modifiedUtf8LengthOfUtf8(premainClass, oldLen);
if (newLen == oldLen) {
premainClass = strdup(premainClass);
} else {
char* str = (char*)malloc( newLen+1 );
if (str != NULL) {
convertUtf8ToModifiedUtf8(premainClass, oldLen, str, newLen);
}
premainClass = str;
}
if (premainClass == NULL) {
fprintf(stderr, "-javaagent: memory allocation failed\n");
free(jarfile);
if (options != NULL) free(options);
freeAttributes(attributes);
return JNI_ERR;
}
/*
* If the Boot-Class-Path attribute is specified then we process
* each relative URL and add it to the bootclasspath.
*/
bootClassPath = getAttribute(attributes, "Boot-Class-Path");
if (bootClassPath != NULL) {
appendBootClassPath(agent, jarfile, bootClassPath);
}
/*
* Convert JAR attributes into agent capabilities
*/
convertCapabilityAtrributes(attributes, agent);
/*
* Track (record) the agent class name and options data
*/
initerror = recordCommandLineData(agent, premainClass, options);
/*
* Clean-up
*/
free(jarfile);
if (options != NULL) free(options);
freeAttributes(attributes);
free(premainClass);
}
switch (initerror) {
case JPLIS_INIT_ERROR_NONE:
result = JNI_OK;
break;
case JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT:
result = JNI_ERR;
fprintf(stderr, "java.lang.instrument/-javaagent: cannot create native agent.\n");
break;
case JPLIS_INIT_ERROR_FAILURE:
result = JNI_ERR;
fprintf(stderr, "java.lang.instrument/-javaagent: initialization of native agent failed.\n");
break;
case JPLIS_INIT_ERROR_ALLOCATION_FAILURE:
result = JNI_ERR;
fprintf(stderr, "java.lang.instrument/-javaagent: allocation failure.\n");
break;
case JPLIS_INIT_ERROR_AGENT_CLASS_NOT_SPECIFIED:
result = JNI_ERR;
fprintf(stderr, "-javaagent: agent class not specified.\n");
break;
default:
result = JNI_ERR;
fprintf(stderr, "java.lang.instrument/-javaagent: unknown error\n");
break;
}
return result;
}
看一下核心 createNewJPLISAgent :
/*
* Creates a new JPLISAgent.
* Returns error if the agent cannot be created and initialized.
* The JPLISAgent* pointed to by agent_ptr is set to the new broker,
* or NULL if an error has occurred.
*/
JPLISInitializationError
createNewJPLISAgent(JavaVM * vm, JPLISAgent **agent_ptr) {
JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE;
jvmtiEnv * jvmtienv = NULL;
jint jnierror = JNI_OK;
*agent_ptr = NULL;
// 获取vm环境
jnierror = (*vm)->GetEnv( vm,
(void **) &jvmtienv,
JVMTI_VERSION_1_1);
if ( jnierror != JNI_OK ) {
initerror = JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT;
} else {
// 分配JPLISAgent
JPLISAgent * agent = allocateJPLISAgent(jvmtienv);
if ( agent == NULL ) {
initerror = JPLIS_INIT_ERROR_ALLOCATION_FAILURE;
} else {
// 这儿初始化了JPLISAgent 。接下来的部分讲一下这个函数。
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);
/* can be called from any phase */
jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
}
}
return initerror;
}
再看一下初始化JPLISAgent的函数 initializeJPLISAgent
JPLISInitializationError
initializeJPLISAgent( JPLISAgent * agent,
JavaVM * vm,
jvmtiEnv * jvmtienv) {
......
/* now turn on the VMInit event */
if ( jvmtierror == JVMTI_ERROR_NONE ) {
jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
// 这儿执行了一个VMInit的初始化函数。
callbacks.VMInit = &eventHandlerVMInit;
jvmtierror = (*jvmtienv)->SetEventCallbacks( jvmtienv,
&callbacks,
sizeof(callbacks));
check_phase_ret_blob(jvmtierror, JPLIS_INIT_ERROR_FAILURE);
jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
}
......
return (jvmtierror == JVMTI_ERROR_NONE)? JPLIS_INIT_ERROR_NONE : JPLIS_INIT_ERROR_FAILURE;
}
VMInit的初始化函数 eventHandlerVMInit : (定义位置: jdk7u/jdk/src/share/instrument/InvocationAdapter.c)。callback support 分了两个阶段(主要在processJavaStart中做了):
(1)在OnLoad时, 安装VMInit handler
(2)当VMInit handler 跑起来时,移除VMInit handler 安装一个ClassFileLoadHook handler(里面有我们定义的具体执行的回调函数).
/**
* JVMTI callback support
*/
void JNICALL eventHandlerVMInit( jvmtiEnv * jvmtienv,
JNIEnv * jnienv,
jthread thread) {
JPLISEnvironment * environment = NULL;
jboolean success = JNI_FALSE;
// 获取JPLISAgent的环境
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 里面执行了注册event的处理钩子。
/*
* If this call fails, the JVM launch will ultimately be aborted,
* so we don't have to be super-careful to clean up in partial failure
* cases.
*/
jboolean
processJavaStart( JPLISAgent * agent,
JNIEnv * jnienv) {
jboolean result;
/*
* OK, Java is up now. We can start everything that needs Java.
*/
/*
* First make our emergency fallback InternalError throwable.
*/
result = initializeFallbackError(jnienv);
jplis_assert(result);
/*
* Now make the InstrumentationImpl instance.
*/
if ( result ) {
// 创建java.lang.instrument.instrumentation 实例。
result = createInstrumentationImpl(jnienv, agent);
jplis_assert(result);
}
/*
* Then turn off the VMInit handler and turn on the ClassFileLoadHook.
* This way it is on before anyone registers a transformer.
*/
if ( result ) {
// 这里面注册了callbak 的ClassFileLoadHook 。 callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook; (*jvmtienv)->SetEventCallbacks; 具体可以看下一节的代码。
result = setLivePhaseEventHandlers(agent);
jplis_assert(result);
}
/*
* Load the Java agent, and call the premain.
*/
if ( result ) {
result = startJavaAgent(agent, jnienv,
agent->mAgentClassName, agent->mOptionsString,
agent->mPremainCaller);
}
/*
* Finally surrender all of the tracking data that we don't need any more.
* If something is wrong, skip it, we will be aborting the JVM anyway.
*/
if ( result ) {
deallocateCommandLineData(agent);
}
return result;
}
setLivePhaseEventHandlers :
jboolean
setLivePhaseEventHandlers( JPLISAgent * agent) {
jvmtiEventCallbacks callbacks;
jvmtiEnv * jvmtienv = jvmti(agent);
jvmtiError jvmtierror;
/* first swap out the handlers (switch from the VMInit handler, which we do not need,
* to the ClassFileLoadHook handler, which is what the agents need from now on)
*/
memset(&callbacks, 0, sizeof(callbacks));
callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook;
jvmtierror = (*jvmtienv)->SetEventCallbacks( jvmtienv,
&callbacks,
sizeof(callbacks));
check_phase_ret_false(jvmtierror);
jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
if ( jvmtierror == JVMTI_ERROR_NONE ) {
/* turn off VMInit */
jvmtierror = (*jvmtienv)->SetEventNotificationMode(
jvmtienv,
JVMTI_DISABLE,
JVMTI_EVENT_VM_INIT,
NULL /* all threads */);
check_phase_ret_false(jvmtierror);
jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
}
if ( jvmtierror == JVMTI_ERROR_NONE ) {
/* turn on ClassFileLoadHook */
jvmtierror = (*jvmtienv)->SetEventNotificationMode(
jvmtienv,
JVMTI_ENABLE,
JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
NULL /* all threads */);
check_phase_ret_false(jvmtierror);
jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
}
return (jvmtierror == JVMTI_ERROR_NONE);
}
startJavaAgent
jboolean
startJavaAgent( JPLISAgent * agent,
JNIEnv * jnienv,
const char * classname,
const char * optionsString,
jmethodID agentMainMethod) {
jboolean success = JNI_FALSE;
jstring classNameObject = NULL;
jstring optionsStringObject = NULL;
success = commandStringIntoJavaStrings( jnienv,
classname,
optionsString,
&classNameObject,
&optionsStringObject);
if (success) {
// 这儿加载java agent ,并且执行了premain/agentmain 方法。 premain方法就是我们jar包中自定义的premain 方法。
success = invokeJavaAgentMainMethod( jnienv,
agent->mInstrumentationImpl,
agentMainMethod,
classNameObject,
optionsStringObject);
}
return success;
}