1 Agent
Agent在java中本质是一个动态库,利用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后调用的函数
Agent_OnAttach 函数 当agent在启动时加载,所调用的Agent函数
Agent_OnUnload 函数 当agent卸载时调用
1.1 初始化Agent
目前常见的几个Agent
1. 以-agentlib: 或者-agentpath:为开头的格式,例如:常见的eclipse中的debug代码 -agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:xxxx,这种情况下的agent 是jdwp
2. 以-javaagent:为开头的默认为instrument的agent
JVM在启动的时候,读取参数 -agentlib -agentpath -javaagent 构建了AgentLibrary的链表
代码如下:
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);}
#ifdef JVMTI_KERNELif ((strcmp(name, "hprof") == 0) || (strcmp(name, "jdwp") == 0)) {warning("profiling and debugging agents are not supported with Kernel VM");} else#endif // JVMTI_KERNELadd_init_agent(name, options, is_absolute_path);}// -javaagent} else if (match_option(option, "-javaagent:", &tail)) {if(tail != NULL) {char *options = strcpy(NEW_C_HEAP_ARRAY(char, strlen(tail) + 1), tail);add_init_agent("instrument", options, false);}// -Xnoclassgc}</span>
1.2 加载Agent链接库
在启动JVM create_vm的时候会初始化agent的链表中的每个agent库,加载所指定的动态库, 并调用里面的Agent_OnLoad方法,对instrument的就是加载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) {// Invoke the Agent_OnLoad functionjint 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();
}</span>
在函数里,通过OnloadEntry方法来调用instruments动态库里的Onload方法
// 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) {// Invoke the Agent_OnLoad functionjint 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();
}
1.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;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*/appendClassPath(agent, jarfile);……}…..}
在代码中,可以看到在读取jar的配置文件MANIFEST 里Premain-Class,并且把jar文件追加到agent的class path中