目录
通过这篇文章,可以初步了解agent。包括instrument包、如何自定义agent、使用举例。
后续的例子为,向某个class method添加log,然后恢复原代码。agent执行后的结果为:
// 如下为preAgent修改class字节码,main()内调用class某个方法的结果
=== before targetMethod method ===
from main param
// 如下为调用agent之后,调用class某个方法的结果
=== before targetMethod method ===
person age is 10
from main param101
from main param101
// 如下为调用agent恢复字节码的结果
=== before targetMethod method ===
from main param
接下来一起了解下,想做到这个效果,需要掌握哪些知识。
1. java.lang.instrument
1.1. what
提供了用于定义'给java应用安装测量仪器的工具'的API,工具例如监听或收集应用程序的信息,还可以修改代码逻辑,达到热部署的目的。允许定义的工具去修改class文件,即向方法的字节码中插入额外的字节码,以此实现仪器的行为。
1.2. 主要类
1.2.1 java.lang.instrument.ClassDefinition
定义了需要重定义的class和新的class bytes的绑定关系,其实例作为Instrumentation.redefineClasses方法的参数,
1.2.2. java.lang.instrument.ClassFileTransformer
agent需要实现该接口。在jvm定义class之前,转换class字节码。
1.2.3. java.lang.instrument.Instrumentation
提供了一些行为,用于向java代码添加仪器。这里所谓的仪器,就是方法中添加的额外字节码。用于为工具收集信息。不需要提供实现。
每次调用agent static方法,传递一个新的Instrumentation实例,可以随时调用它的行为。
addTransformer | 注册一个转换器,可通过参数设置该转换器是否支持二次转换,但前提是agent的配置也支持二次转换。 |
removeTransformer | 注销一个转换器 |
isRetransformClassesSupported | jvm配置是否支持class的二次转化。 要求agent JAR文件中Can-Retransform-Classes manifest attribute is set to true且jvm支持。 |
retransformClasses | 二次转化 |
isRedefineClassesSupported | jvm配置是否支持class的二次定义。 要求agent JAR文件中Can-Redefine-Classes manifest attribute is set to true且jvm支持。 |
redefineClasses | 二次定义,需要提供新的class字节码 |
isModifiableClass | class是否由 retransformation or redefinition修改过 |
getAllLoadedClasses | jvm加载的所有class |
getInitiatedClasses | 由参数classLoader参与初始化的class |
appendToBootstrapClassLoaderSearch | 明确说明jar文件是由bootstrap classLoader定义的。 |
appendToSystemClassLoaderSearch | 明确说明jar文件是由system classLoader定义的。 |
isNativeMethodPrefixSupported | jvm配置是否支持setting a native method prefix。 要求agent JAR文件中Can-Set-Native-Method-Prefix manifest attribute is set to true且jvm支持。 |
2. 自定义agent
2.1. 自定义agent的入口
before jvm call main
- 优先调用 public static void premain(String agentArgs, Instrumentation inst);找不到,则调用public static void premain(String agentArgs);
sometime after jvm call main
- 优先调用 public static void agentmain(String agentArgs, Instrumentation inst);找不到,则调用public static void agentmain(String agentArgs)
需要考虑的问题:
- 如何调用某个jvm中的afterAgent?
-
jdk tools.jar com.sun.tools.attach.VirtualMachine
-
- 调用agent的参数规范
- 指令种类
- 关注的class
- 关注的method
- transformer的参数
2.2. 自定义transformer
需要考虑的问题:
- 如何变更class字节码
- javassist(门槛低,不需要了解class file规范。)
- asm(门槛高,需要了解class file的规范,直接操控字节码。)
- 看skywalking agent源码时发现的新工具bytebuddy
- 使用javassist添加字节码需要注意什么?
- insertAt lineNum是对于java文件而言
- 不可以访问method定义的局部变量,否则编译失败;
- 可以访问方法的入参
- 可以访问实例的field
- 可以自定义局部变量
- 参数规范
- 考虑针对哪些class和method
- 如何恢复已转化的class
2.3. 定义manifest文件
Manifest-Version: 1.0
Main-Class: com.chl.demo.agent.AfterAgent
Premain-Class: com.chl.demo.agent.PreAgent
Agent-Class: com.chl.demo.agent.AfterAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: false
2.4. jar包
需要考虑的问题:
- 是否需要把依赖的包放入jar?
- 如何包含manifest文件
2.5. 指令
preAgent和afterAgent均可用
- java -javaagent:path/xxxagent.jar -jar xxxapplication.jar
仅afterAgent可用
- java -jar xxxapplication.jar -javaagent:path/xxxagent.jar
3. 使用举例
https://github.com/chlInGithub/java_agent_demohttps://github.com/chlInGithub/java_agent_demo
本地演示步骤
run配置的application
效果为
premain has two params s
premain has two params e
main
=== before targetMethod method ===
from main param
jps找到上述应用的进程id
// 例如
jps
155696 ApplicationMain
修改字节码
AfterAgentClient main中设置上述查到的进程ID
修改agent jar包地址
run main
// 控制台输出
agentmain has two params 1#com.chl.demo.agent.target.AgentTarget#targetMethod#0@num = 2;int j = 101;param = param + 101;person = new com.chl.demo.agent.target.Person();
person.setAge(10);
if (null != person) {
System.out.println("person age is " + person.getAge());
}
com/chl/demo/agent/target/AgentTarget
classBeingRedefined is not null ? true
classfileBuffer is not null ? true
insertAt 0
finally remove ok
=== before targetMethod method ===
person age is 10
from main param101
from main param101
恢复修改
loadAgent第二个参数中1#改为2#
run main
// 控制台输出
agentmain has two params 2#com.chl.demo.agent.target.AgentTarget#targetMethod#1@System.out.println("=== this is from agentMain param1 ===");
com/chl/demo/agent/target/AgentTarget
RecoverTransformer classBeingRedefined is not null ? true
RecoverTransformer classfileBuffer is not null ? true
finally remove ok
=== before targetMethod method ===
from main param
4. 系统图示
参考