Java Instrument (五) Agent attach

本文深入探讨Java Attach机制,包括AttachListener线程的作用、通过VirtualMachine的attach方法远程加载Agent的过程,以及Agent_OnAttach方法的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 使用方法 Java Agent 提供了一种机制,在不改变原有代码的情况下,允许开发者在 JVM 加载类之前或之后修改其行为。这可以通过 `-javaagent` 命令行参数或者 Attach API 来实现。 #### 通过命令行参数使用 Java Agent 当希望在整个应用程序生命周期内生效时,可以采用这种方式。具体来说,就是在启动 Java 应用程序的时候,利用 `-javaagent:<path-to-agent-jar>` 参数指定一个包含 `Instrumentation` 接口实现的 JAR 文件路径[^3]。此方式支持传递额外配置给代理程序,如: ```bash java -javaagent:/path/to/your-javaagent.jar=configParam=value -jar your-application.jar ``` 对于多个代理的情况,这些代理按照它们被声明的顺序依次执行完毕后再进入主程序逻辑。 #### 动态附加到正在运行的应用程序 (Attach API) 另一种场景下,可能需要对已经处于运行状态下的 JVM 实施监控或是调试功能而无需重启服务。此时可借助于 JDK 自带工具包里的 com.sun.tools.attach.VirtualMachine 类所提供的接口完成动态连接并加载新的 instrumentation agent 到目标进程中去[^4]。 以下是简单的例子展示如何创建自定义的 Java Agent 并将其应用于现有项目中: ```java // MyAgent.java import java.lang.instrument.Instrumentation; public class MyAgent { public static void premain(String args, Instrumentation inst) { System.out.println("MyAgent is running with argument: " + args); // Register a transformer to modify bytecode at load time. inst.addTransformer(new MyClassFileTransformer()); } } ``` 为了使上述代码片段能够作为有效的 Java Agent 工作,还需要确保打包成 jar 文件的同时附加上 MANIFEST.MF 清单文件,其中应包含如下属性以便让 JVM 认识这是一个合法的 agent 程序[^2]: ``` Manifest-Version: 1.0 Premain-Class: MyAgent Can-Redefine-Classes: true Can-Retransform-Classes: true Can-Set-Native-Method-Prefix: true ``` 最后编译好后的 Jar 可以像下面这样用于实际环境中: ```bash java -javaagent:path/to/myagent.jar=argValue -jar application.jar ``` 这种技术特别适用于性能分析、故障排查以及AOP(面向切面编程)等领域内的开发工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值