当前位置: 代码迷 >> 综合 >> Java Instrument (五) Agent attach
  详细解决方案

Java Instrument (五) Agent attach

热度:90   发布时间:2023-12-07 23:12:11.0

1. 两个线程

首先先参考笔者前期的博客(http://blog.csdn.net/raintungli/article/details/7034005),先了解在jvm启动的过程中的两个线程Signal Dispatcher和Attach Listener

在博客中,已经探讨了在Attach Listener 的线程在linux环境中创建了socket的文件,接着我们的关注点讲成为客户端如何写这个文件。

2. Attach 方法

在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 socketint s = socket();// connect to target VMtry {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 replySocketInputStream sis = new SocketInputStream(s);// Read the command completion statusint 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 directoryos::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 pathchar 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 functionOnAttachEntry_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 libraryos::dll_unload(library);} else {// Invoke the Agent_OnAttach functionJavaThread* 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_OKst->print_cr("%d", result);result = JNI_OK;}}return result;}
#define AGENT_ONATTACH_SYMBOLS  {"Agent_OnAttach"}

3. Instrument 的Agent on attach

加载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{....}
}




  相关解决方案