Java Instrument (五) Agent attach

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 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"}

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




你可能感兴趣的:(java,agent,Attach,instrument)