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

相关文章

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima

SpringBoot全局域名替换的实现

《SpringBoot全局域名替换的实现》本文主要介绍了SpringBoot全局域名替换的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录 项目结构⚙️ 配置文件application.yml️ 配置类AppProperties.Ja

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

JavaScript中的高级调试方法全攻略指南

《JavaScript中的高级调试方法全攻略指南》什么是高级JavaScript调试技巧,它比console.log有何优势,如何使用断点调试定位问题,通过本文,我们将深入解答这些问题,带您从理论到实... 目录观点与案例结合观点1观点2观点3观点4观点5高级调试技巧详解实战案例断点调试:定位变量错误性能分

Java实现将HTML文件与字符串转换为图片

《Java实现将HTML文件与字符串转换为图片》在Java开发中,我们经常会遇到将HTML内容转换为图片的需求,本文小编就来和大家详细讲讲如何使用FreeSpire.DocforJava库来实现这一功... 目录前言核心实现:html 转图片完整代码场景 1:转换本地 HTML 文件为图片场景 2:转换 H

Java使用jar命令配置服务器端口的完整指南

《Java使用jar命令配置服务器端口的完整指南》本文将详细介绍如何使用java-jar命令启动应用,并重点讲解如何配置服务器端口,同时提供一个实用的Web工具来简化这一过程,希望对大家有所帮助... 目录1. Java Jar文件简介1.1 什么是Jar文件1.2 创建可执行Jar文件2. 使用java

SpringBoot实现不同接口指定上传文件大小的具体步骤

《SpringBoot实现不同接口指定上传文件大小的具体步骤》:本文主要介绍在SpringBoot中通过自定义注解、AOP拦截和配置文件实现不同接口上传文件大小限制的方法,强调需设置全局阈值远大于... 目录一  springboot实现不同接口指定文件大小1.1 思路说明1.2 工程启动说明二 具体实施2

Java实现在Word文档中添加文本水印和图片水印的操作指南

《Java实现在Word文档中添加文本水印和图片水印的操作指南》在当今数字时代,文档的自动化处理与安全防护变得尤为重要,无论是为了保护版权、推广品牌,还是为了在文档中加入特定的标识,为Word文档添加... 目录引言Spire.Doc for Java:高效Word文档处理的利器代码实战:使用Java为Wo

SpringBoot日志级别与日志分组详解

《SpringBoot日志级别与日志分组详解》文章介绍了日志级别(ALL至OFF)及其作用,说明SpringBoot默认日志级别为INFO,可通过application.properties调整全局或... 目录日志级别1、级别内容2、调整日志级别调整默认日志级别调整指定类的日志级别项目开发过程中,利用日志

Java中的抽象类与abstract 关键字使用详解

《Java中的抽象类与abstract关键字使用详解》:本文主要介绍Java中的抽象类与abstract关键字使用详解,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、抽象类的概念二、使用 abstract2.1 修饰类 => 抽象类2.2 修饰方法 => 抽象方法,没有