首先先参考笔者前期的博客(http://blog.csdn.net/raintungli/article/details/7034005),先了解在jvm启动的过程中的两个线程Signal Dispatcher和Attach Listener
在博客中,已经探讨了在Attach Listener 的线程在linux环境中创建了socket的文件,接着我们的关注点讲成为客户端如何写这个文件。
在attach 的java代码中,使用sun自用的tool.jar中的VirtualMachine的attach的方式
VirtualMachine vm = VirtualMachine.attach(processid); vm.loadAgent(agentpath, args)在HotSpotVirtualMachine.java 中
public void loadAgent(String agent, String options) throws AgentLoadException, AgentInitializationException, IOException { String args = agent; if (options != null) { args = args + "=" + options; } try { loadAgentLibrary("instrument", args); } ..... } private void loadAgentLibrary(String agentLibrary, boolean isAbsolute, String options) throws AgentLoadException, AgentInitializationException, IOException { InputStream in = execute("load", agentLibrary, isAbsolute ? "true" : "false", options); try { int result = readInt(in); if (result != 0) { throw new AgentInitializationException("Agent_OnAttach failed", result); } } finally { in.close(); } }
在LinuxVirtualMachine.java中的execute方法
InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException { assert args.length <= 3; // includes null // did we detach? String p; synchronized (this) { if (this.path == null) { throw new IOException("Detached from target VM"); } p = this.path; } // create UNIX socket int s = socket(); // connect to target VM try { connect(s, p); } catch (IOException x) { close(s); throw x; } IOException ioe = null; // connected - write request // <ver> <cmd> <args...> try { writeString(s, PROTOCOL_VERSION); writeString(s, cmd); for (int i=0; i<3; i++) { if (i < args.length && args[i] != null) { writeString(s, (String)args[i]); } else { writeString(s, ""); } } } catch (IOException x) { ioe = x; } // Create an input stream to read reply SocketInputStream sis = new SocketInputStream(s); // Read the command completion status int completionStatus; try { completionStatus = readInt(sis); } catch (IOException x) { sis.close(); if (ioe != null) { throw ioe; } else { throw x; } } .... }也就是向socket的中写入了
<ver> <cmd> <args...>
格式为:
1 load instrument agentPath=path.jar
既然Load Agent 往socket里发了load指令,匹配到JVM的操作
static AttachOperationFunctionInfo funcs[] = { { "agentProperties", get_agent_properties }, { "datadump", data_dump }, #ifndef SERVICES_KERNEL { "dumpheap", dump_heap }, #endif // SERVICES_KERNEL { "load", JvmtiExport::load_agent_library }, { "properties", get_system_properties }, { "threaddump", thread_dump }, { "inspectheap", heap_inspection }, { "setflag", set_flag }, { "printflag", print_flag }, { NULL, NULL } };
"load", JvmtiExport::load_agent_library
jint JvmtiExport::load_agent_library(AttachOperation* op, outputStream* st) { char ebuf[1024]; char buffer[JVM_MAXPATHLEN]; void* library; jint result = JNI_ERR; const char* agent = op->arg(0); const char* absParam = op->arg(1); const char* options = op->arg(2); bool is_absolute_path = (absParam != NULL) && (strcmp(absParam,"true")==0); if (is_absolute_path) { library = os::dll_load(agent, ebuf, sizeof ebuf); } else { // Try to load the agent from the standard dll directory os::dll_build_name(buffer, sizeof(buffer), Arguments::get_dll_dir(), agent); library = os::dll_load(buffer, ebuf, sizeof ebuf); if (library == NULL) { // not found - try local path char ns[1] = {0}; os::dll_build_name(buffer, sizeof(buffer), ns, agent); library = os::dll_load(buffer, ebuf, sizeof ebuf); } } if (library != NULL) { // Lookup the Agent_OnAttach function OnAttachEntry_t on_attach_entry = NULL; const char *on_attach_symbols[] = AGENT_ONATTACH_SYMBOLS; for (uint symbol_index = 0; symbol_index < ARRAY_SIZE(on_attach_symbols); symbol_index++) { on_attach_entry = CAST_TO_FN_PTR(OnAttachEntry_t, os::dll_lookup(library, on_attach_symbols[symbol_index])); if (on_attach_entry != NULL) break; } if (on_attach_entry == NULL) { // Agent_OnAttach missing - unload library os::dll_unload(library); } else { // Invoke the Agent_OnAttach function JavaThread* THREAD = JavaThread::current(); { extern struct JavaVM_ main_vm; JvmtiThreadEventMark jem(THREAD); JvmtiJavaThreadEventTransition jet(THREAD); result = (*on_attach_entry)(&main_vm, (char*)options, NULL); } if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } if (result == JNI_OK) { Arguments::add_loaded_agent(agent, (char*)options, is_absolute_path, library); } // Agent_OnAttach executed so completion status is JNI_OK st->print_cr("%d", result); result = JNI_OK; } } return result; }
#define AGENT_ONATTACH_SYMBOLS {"Agent_OnAttach"}
加载instrument的动态库,并且调用方法instrument动态库中的Agent_OnAttach方法
JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char *args, void * reserved) { ..... initerror = createNewJPLISAgent(vm, &agent); if ( initerror == JPLIS_INIT_ERROR_NONE ) { ...... if (parseArgumentTail(args, &jarfile, &options) != 0) { return JNI_ENOMEM; } 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 AGENT_ERROR_BADJAR; } agentClass = getAttribute(attributes, "Agent-Class"); if (agentClass == NULL) { fprintf(stderr, "Failed to find Agent-Class manifest attribute from %s\n", jarfile); free(jarfile); if (options != NULL) free(options); freeAttributes(attributes); return AGENT_ERROR_BADJAR; } if (appendClassPath(agent, jarfile)) { fprintf(stderr, "Unable to add %s to system class path " "- not supported by system class loader or configuration error!\n", jarfile); free(jarfile); if (options != NULL) free(options); freeAttributes(attributes); return AGENT_ERROR_NOTONCP; } oldLen = strlen(agentClass); newLen = modifiedUtf8LengthOfUtf8(agentClass, oldLen); if (newLen == oldLen) { agentClass = strdup(agentClass); } else { char* str = (char*)malloc( newLen+1 ); if (str != NULL) { convertUtf8ToModifiedUtf8(agentClass, oldLen, str, newLen); } agentClass = str; } if (agentClass == NULL) { free(jarfile); if (options != NULL) free(options); freeAttributes(attributes); return JNI_ENOMEM; } bootClassPath = getAttribute(attributes, "Boot-Class-Path"); if (bootClassPath != NULL) { appendBootClassPath(agent, jarfile, bootClassPath); } convertCapabilityAtrributes(attributes, agent); success = createInstrumentationImpl(jni_env, agent); jplis_assert(success); /* * Turn on the ClassFileLoadHook. */ if (success) { success = setLivePhaseEventHandlers(agent); jplis_assert(success); } if (success) { success = startJavaAgent(agent, jni_env, agentClass, options, agent->mAgentmainCaller); } if (!success) { fprintf(stderr, "Agent failed to start!\n"); result = AGENT_ERROR_STARTFAIL; } if (options != NULL) free(options); free(agentClass); freeAttributes(attributes); } return result; }
代码里createNewJPLISAgent是和on_load是一样的注册了一些钩子函数,也就是说在on_attach的情况下,依然可以对没有进行解析的class通过钩子函数进行修改class的字节码。那么问题来了,如果已经解析过的class的呢?
在代码中我们看到了
agentClass = getAttribute(attributes, "Agent-Class");
也就是在on_attach中,会读取加载的jar中MANIFEST Agent-Class的配置
success = createInstrumentationImpl(jni_env, agent);
生成sun.instrument.InstrumentationImpl对象
success = startJavaAgent(agent,
jni_env,
agentClass,
options,
agent->mAgentmainCaller);
通过InstrumentationImpl对象中的loadClassAndCallAgentmain方法去初始化在Agent-Class中的类,并调用class里的agentmain的方法。
也就是说定义的on_attach的class里需要有agentmain的方法
public class MyTransformer { public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException, NotFoundException, CannotCompileException, IOException{ .... } }