Java Agent(五)OpenJdk/Instrument包源码分析

2024-02-23 11:18

本文主要是介绍Java Agent(五)OpenJdk/Instrument包源码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

简介

在介绍instrument是什么之前,先来看几个定义:
JVMTI(JVM tool interface):它是JVM提供的一系列native编程接口。
Agent:与JVM进行通信的外部进程,它们通过调用JVMTI进行交互,可进行的操作包括设置JVM回调函数、获取当前虚拟机状态信息等
Instrument:Jdk提供的"java.lang.instrument"包,与JVM进行交互,可以看作为一个Instrument Agent。

https://www.ibm.com/developerworks/cn/java/j-lo-jpda2/index.html?ca=drs-

整体流程

接上文,当JVM接收到execute(“load”,“instrument”,“false”,options)命令之后,开始进行动态链接库的加载。

  • "load"命令——对应load_agent方法:
  1. 接收参数:op->arg(0)、 op->arg(1)、op->arg(2);(“instrument”、是否绝对路径、agentpath=args)
  2. 判断 if(arg(0)==“instrument”)先加载"java.instrument"模块,再load_agent_library
  • load_agent_library:
  1. 首先加载动态库:“libinstrument.so”,加载instrument Agent(JVM定义Linux系统中,加载的动态链接库名字是"lib"+args(0)+".so")
  2. 调用库中的Agent_OnAttach方法。

从Agent_OnAttach开始,是libinstrument.so中的内容:
在这里插入图片描述

关于libInstrument.so库,我认为可以当做是一个底层Agent。

它直接调用了JVMTI提供的回调方法,然后加载我们自定义的agent,避免了我们自己的agent与JVMTI直接通信。

源码分析

// Implementation of "load" command.
static jint load_agent(AttachOperation* op, outputStream* out) {// get agent name and optionsconst char* agent = op->arg(0);const char* absParam = op->arg(1);const char* options = op->arg(2);// If loading a java agent then need to ensure that the java.instrument module is loadedif (strcmp(agent, "instrument") == 0) {Thread* THREAD = Thread::current();ResourceMark rm(THREAD);HandleMark hm(THREAD);JavaValue result(T_OBJECT);Handle h_module_name = java_lang_String::create_from_str("java.instrument", THREAD);JavaCalls::call_static(&result,SystemDictionary::module_Modules_klass(),vmSymbols::loadModule_name(),vmSymbols::loadModule_signature(),h_module_name,THREAD);if (HAS_PENDING_EXCEPTION) {java_lang_Throwable::print(PENDING_EXCEPTION, out);CLEAR_PENDING_EXCEPTION;return JNI_ERR;}}return JvmtiExport::load_agent_library(agent, absParam, options, out);
}

接收到的三个参数对应我们发送命令时的三个:“instrument”、是否绝对路径、agentpath=args;
先加载java.instrument模块,再加载Agent,调用JvmtiExport::load_agent_library(agent, absParam, options, out),当成功时返回码为0.

jint JvmtiExport::load_agent_library(const char *agent, const char *absParam,const char *options, outputStream* st) {char ebuf[1024];char buffer[JVM_MAXPATHLEN];void* library = NULL;jint result = JNI_ERR;const char *on_attach_symbols[] = AGENT_ONATTACH_SYMBOLS;size_t num_symbol_entries = ARRAY_SIZE(on_attach_symbols);// The abs paramter should be "true" or "false"bool is_absolute_path = (absParam != NULL) && (strcmp(absParam,"true")==0);// Initially marked as invalid. It will be set to valid if we can find the agentAgentLibrary *agent_lib = new AgentLibrary(agent, options, is_absolute_path, NULL);// Check for statically linked in agent. If not found then if the path is// absolute we attempt to load the library. Otherwise we try to load it// from the standard dll directory.if (!os::find_builtin_agent(agent_lib, on_attach_symbols, num_symbol_entries)) {if (is_absolute_path) {library = os::dll_load(agent, ebuf, sizeof ebuf);} else {// Try to load the agent from the standard dll directoryif (os::dll_locate_lib(buffer, sizeof(buffer), Arguments::get_dll_dir(),agent)) {library = os::dll_load(buffer, ebuf, sizeof ebuf);}if (library == NULL) {// not found - try OS default library pathif (os::dll_build_name(buffer, sizeof(buffer), agent)) {library = os::dll_load(buffer, ebuf, sizeof ebuf);}}}if (library != NULL) {agent_lib->set_os_lib(library);agent_lib->set_valid();}}// If the library was loaded then we attempt to invoke the Agent_OnAttach// functionif (agent_lib->valid()) {// Lookup the Agent_OnAttach functionOnAttachEntry_t on_attach_entry = NULL;on_attach_entry = CAST_TO_FN_PTR(OnAttachEntry_t,os::find_agent_function(agent_lib, false, on_attach_symbols, num_symbol_entries));if (on_attach_entry == NULL) {// Agent_OnAttach missing - unload libraryif (!agent_lib->is_static_lib()) {os::dll_unload(library);}delete agent_lib;} 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);}// Agent_OnAttach may have used JNIif (HAS_PENDING_EXCEPTION) {CLEAR_PENDING_EXCEPTION;}// If OnAttach returns JNI_OK then we add it to the list of// agent libraries so that we can call Agent_OnUnload later.if (result == JNI_OK) {Arguments::add_loaded_agent(agent_lib);} else {delete agent_lib;}// Agent_OnAttach executed so completion status is JNI_OKst->print_cr("%d", result);result = JNI_OK;}}return result;
}

在linux中,首先要加载动态库,库的名字由dll_build_name(buffer, sizeof(buffer), agent)获得:“lib”+传入的agentlib名字+".so",即"libinstrument.so"。
on_attach_entry = CAST_TO_FN_PTR(OnAttachEntry_t, os::find_agent_function(agent_lib, false, on_attach_symbols, num_symbol_entries));这行代码负责在库中找Agent_OnAttach方法。
(find_agent_function支持找的方法:Agent_On(Un)Load/Attach)
libinstrument.so中的instrument agent,实现了Agent_OnLoad和Agent_OnAttach两方法,javaAgent功能就是由此实现的:
Agent_OnLoad对应虚拟机启动时加载,Agent_OnAttach对应启动后加载。
加载类库成功后,调用Agent_OnAttach方法。传参:vm、options(agentpath=args)、null
Agent_OnAttach:

/**  This will be called once each time a tool attaches to the VM and loads*  the JPLIS library.*/
JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char *args, void * reserved) {JPLISInitializationError initerror  = JPLIS_INIT_ERROR_NONE;jint                     result     = JNI_OK;JPLISAgent *             agent      = NULL;JNIEnv *                 jni_env    = NULL;/** Need JNIEnv - guaranteed to be called from thread that is already* attached to VM*/result = (*vm)->GetEnv(vm, (void**)&jni_env, JNI_VERSION_1_2);jplis_assert(result==JNI_OK);initerror = createNewJPLISAgent(vm, &agent);if ( initerror == JPLIS_INIT_ERROR_NONE ) {int             oldLen, newLen;char *          jarfile;char *          options;jarAttribute*   attributes;char *          agentClass;char *          bootClassPath;jboolean        success;/** Parse <jarfile>[=options] into jarfile and options*/if (parseArgumentTail(args, &jarfile, &options) != 0) {return JNI_ENOMEM;}/** Open the JAR file and parse the manifest*/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;}/** Add the jarfile to the system class path*/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;}/** The value of the Agent-Class attribute becomes the agent* class name. The manifest is in UTF8 so need to convert to* modified UTF8 (see JNI spec).*/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;}/** If the Boot-Class-Path attribute is specified then we process* each URL - in the live phase only JAR files will be added.*/bootClassPath = getAttribute(attributes, "Boot-Class-Path");if (bootClassPath != NULL) {appendBootClassPath(agent, jarfile, bootClassPath);}/** Convert JAR attributes into agent capabilities*/convertCapabilityAtrributes(attributes, agent);/** Create the java.lang.instrument.Instrumentation instance*/success = createInstrumentationImpl(jni_env, agent);jplis_assert(success);/**  Turn on the ClassFileLoadHook.*/if (success) {success = setLivePhaseEventHandlers(agent);jplis_assert(success);}/** Start the agent*/if (success) {success = startJavaAgent(agent,jni_env,agentClass,options,agent->mAgentmainCaller);}if (!success) {fprintf(stderr, "Agent failed to start!\n");result = AGENT_ERROR_STARTFAIL;}/** Clean-up*/free(jarfile);if (options != NULL) free(options);free(agentClass);freeAttributes(attributes);}return result;
}

首先(parseArgumentTail(args, &jarfile, &options)解析传来的参数:将agentPath 与 args通过"="分隔;

  1. 打开Jar文件并解析manifest——attributes = readAttributes( jarfile );,
  2. 得到入口类——agentClass = getAttribute(attributes, “Agent-Class”)
  3. 将agentPath加入到System class path
  4. 创建Instrumention实例——success = createInstrumentationImpl(jni_env, agent);
  5. 打开ClassFileLoadHook——success = setLivePhaseEventHandlers(agent)
  6. 开始agent——startJavaAgent(agent,jni_env, agentClass,options, agent->mAgentmainCaller)

第五步:setLivePhaseEventHandlers

jboolean
setLivePhaseEventHandlers(  JPLISAgent * agent) {jvmtiEventCallbacks callbacks;jvmtiEnv *          jvmtienv = jvmti(agent);jvmtiError          jvmtierror;/* first swap out the handlers (switch from the VMInit handler, which we do not need,* to the ClassFileLoadHook handler, which is what the agents need from now on)*/memset(&callbacks, 0, sizeof(callbacks));callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook;jvmtierror = (*jvmtienv)->SetEventCallbacks( jvmtienv,&callbacks,sizeof(callbacks));check_phase_ret_false(jvmtierror);jplis_assert(jvmtierror == JVMTI_ERROR_NONE);if ( jvmtierror == JVMTI_ERROR_NONE ) {/* turn off VMInit */jvmtierror = (*jvmtienv)->SetEventNotificationMode(jvmtienv,JVMTI_DISABLE,JVMTI_EVENT_VM_INIT,NULL /* all threads */);check_phase_ret_false(jvmtierror);jplis_assert(jvmtierror == JVMTI_ERROR_NONE);}if ( jvmtierror == JVMTI_ERROR_NONE ) {/* turn on ClassFileLoadHook */jvmtierror = (*jvmtienv)->SetEventNotificationMode(jvmtienv,JVMTI_ENABLE,JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,NULL /* all threads */);check_phase_ret_false(jvmtierror);jplis_assert(jvmtierror == JVMTI_ERROR_NONE);}return (jvmtierror == JVMTI_ERROR_NONE);
}

JVM中定义的关于类加载的回调:钩子函数eventHandlerClassFileLoadHook

eventHandlerClassFileLoadHook(  jvmtiEnv *              jvmtienv,JNIEnv *                jnienv,jclass                  class_being_redefined,jobject                 loader,const char*             name,jobject                 protectionDomain,jint                    class_data_len,const unsigned char*    class_data,jint*                   new_class_data_len,unsigned char**         new_class_data) {JPLISEnvironment * environment  = NULL;environment = getJPLISEnvironment(jvmtienv);/* if something is internally inconsistent (no agent), just silently return without touching the buffer */if ( environment != NULL ) {jthrowable outstandingException = preserveThrowable(jnienv);transformClassFile( environment->mAgent,jnienv,loader,name,class_being_redefined,protectionDomain,class_data_len,class_data,new_class_data_len,new_class_data,environment->mIsRetransformer);restoreThrowable(jnienv, outstandingException);}
}

transformClassFile方法中:

transformedBufferObject = (*jnienv)->CallObjectMethod(jnienv,agent->mInstrumentationImpl,agent->mTransform,loaderObject,classNameStringObject,classBeingRedefined,protectionDomain,classFileBufferObject,is_retransformer);

第六步:

startJavaAgent( JPLISAgent *    agent,JNIEnv *        jnienv,const char *    classname,const char *    optionsString,jmethodID       agentMainMethod) {jboolean    success = JNI_FALSE;jstring classNameObject = NULL;jstring optionsStringObject = NULL;success = commandStringIntoJavaStrings(    jnienv,classname,optionsString,&classNameObject,&optionsStringObject);if (success) {success = invokeJavaAgentMainMethod(   jnienv,agent->mInstrumentationImpl,agentMainMethod,classNameObject,optionsStringObject);}return success;
}

其中的invokeJavaAgentMainMethod,通过调用mInstrumentationImpl.loadClassAndStartAgent实现初始化调用我们自定义的agentMain方法。

loadClassAndCallAgentmain(  String  classname,String  optionsString)throws Throwable {loadClassAndStartAgent( classname, "agentmain", optionsString );
}// Attempt to load and start an agent
private void
loadClassAndStartAgent( String  classname,String  methodname,String  optionsString)throws Throwable {ClassLoader mainAppLoader   = ClassLoader.getSystemClassLoader();Class<?>    javaAgentClass  = mainAppLoader.loadClass(classname);Method m = null;NoSuchMethodException firstExc = null;boolean twoArgAgent = false;// The agent class must have a premain or agentmain method that// has 1 or 2 arguments. We check in the following order://// 1) declared with a signature of (String, Instrumentation)// 2) declared with a signature of (String)// 3) inherited with a signature of (String, Instrumentation)// 4) inherited with a signature of (String)//// So the declared version of either 1-arg or 2-arg always takes// primary precedence over an inherited version. After that, the// 2-arg version takes precedence over the 1-arg version.//// If no method is found then we throw the NoSuchMethodException// from the first attempt so that the exception text indicates// the lookup failed for the 2-arg method (same as JDK5.0).try {m = javaAgentClass.getDeclaredMethod( methodname,new Class<?>[] {String.class,java.lang.instrument.Instrumentation.class});twoArgAgent = true;} catch (NoSuchMethodException x) {// remember the NoSuchMethodExceptionfirstExc = x;}if (m == null) {// now try the declared 1-arg methodtry {m = javaAgentClass.getDeclaredMethod(methodname,new Class<?>[] { String.class });} catch (NoSuchMethodException x) {// ignore this exception because we'll try// two arg inheritance next}}if (m == null) {// now try the inherited 2-arg methodtry {m = javaAgentClass.getMethod( methodname,new Class<?>[] {String.class,java.lang.instrument.Instrumentation.class});twoArgAgent = true;} catch (NoSuchMethodException x) {// ignore this exception because we'll try// one arg inheritance next}}if (m == null) {// finally try the inherited 1-arg methodtry {m = javaAgentClass.getMethod(methodname,new Class<?>[] { String.class });} catch (NoSuchMethodException x) {// none of the methods exists so we throw the// first NoSuchMethodException as per 5.0throw firstExc;}}// the premain method should not be required to be public,// make it accessible so we can call it// Note: The spec says the following://     The agent class must implement a public static premain method...setAccessible(m, true);// invoke the 1 or 2-arg methodif (twoArgAgent) {m.invoke(null, new Object[] { optionsString, this });} else {m.invoke(null, new Object[] { optionsString });}// don't let others access a non-public premain methodsetAccessible(m, false);
}

这篇关于Java Agent(五)OpenJdk/Instrument包源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/738447

相关文章

一文详解如何在idea中快速搭建一个Spring Boot项目

《一文详解如何在idea中快速搭建一个SpringBoot项目》IntelliJIDEA作为Java开发者的‌首选IDE‌,深度集成SpringBoot支持,可一键生成项目骨架、智能配置依赖,这篇文... 目录前言1、创建项目名称2、勾选需要的依赖3、在setting中检查maven4、编写数据源5、开启热

Java对异常的认识与异常的处理小结

《Java对异常的认识与异常的处理小结》Java程序在运行时可能出现的错误或非正常情况称为异常,下面给大家介绍Java对异常的认识与异常的处理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参... 目录一、认识异常与异常类型。二、异常的处理三、总结 一、认识异常与异常类型。(1)简单定义-什么是

SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志

《SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志》在SpringBoot项目中,使用logback-spring.xml配置屏蔽特定路径的日志有两种常用方式,文中的... 目录方案一:基础配置(直接关闭目标路径日志)方案二:结合 Spring Profile 按环境屏蔽关

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.

java中long的一些常见用法

《java中long的一些常见用法》在Java中,long是一种基本数据类型,用于表示长整型数值,接下来通过本文给大家介绍java中long的一些常见用法,感兴趣的朋友一起看看吧... 在Java中,long是一种基本数据类型,用于表示长整型数值。它的取值范围比int更大,从-922337203685477

java Long 与long之间的转换流程

《javaLong与long之间的转换流程》Long类提供了一些方法,用于在long和其他数据类型(如String)之间进行转换,本文将详细介绍如何在Java中实现Long和long之间的转换,感... 目录概述流程步骤1:将long转换为Long对象步骤2:将Longhttp://www.cppcns.c

SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程

《SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程》LiteFlow是一款专注于逻辑驱动流程编排的轻量级框架,它以组件化方式快速构建和执行业务流程,有效解耦复杂业务逻辑,下面给大... 目录一、基础概念1.1 组件(Component)1.2 规则(Rule)1.3 上下文(Conte

SpringBoot服务获取Pod当前IP的两种方案

《SpringBoot服务获取Pod当前IP的两种方案》在Kubernetes集群中,SpringBoot服务获取Pod当前IP的方案主要有两种,通过环境变量注入或通过Java代码动态获取网络接口IP... 目录方案一:通过 Kubernetes Downward API 注入环境变量原理步骤方案二:通过

Springboot整合Redis主从实践

《Springboot整合Redis主从实践》:本文主要介绍Springboot整合Redis主从的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言原配置现配置测试LettuceConnectionFactory.setShareNativeConnect