笔者近期参与了开源pinpoint1.7.1的研究,pinpoint:https://github.com/naver/pinpoint/
pinpoint是部分实现了APM(Application Performance Management/应用性能管理)五个维度的一个开源产品,作者是韩国人,具有一定的研究价值,笔者将逐步发一些个人研究pinpoint的文章,与大家交流。
第一篇,我们对pinpoint的实现技术-----字节码注入进行介绍,不过和pp不同的是是在运行时修改字节码而不是加载进虚拟机时修改。
1.字节码修改框架
有很多第三方的字节码修改框架,pinpoint使用asm/javaassist 框架,相对而言javaasist更为简单,但是效率较低。
ASM框架概述:
ASM有两种处理字节码的API
The ASM library provides two APIs for generating and transforming compiled
classes: the core API provides an event based representation of classes, while
the tree API provides an object based representation.
可以类比解析xml
These two APIs can be compared to the Simple API for XML (SAX) and
Document Object Model (DOM) APIs for XML documents: the event based
API is similar to SAX, while the object based API is similar to DOM. The
object based API is built on top of the event based one, like DOM can be
provided on top of SAX.
ASM字节码修改demo:
github: https://github.com/chengbinghan/apm/tree/master/asm-first
ASM 十分底层,比如我们要生成一个包含几个属性和一个方法的接口,其代码如下:

如果操作不当会产生一些奇怪的字节码,比如下面的这个类,其方法和属性都重复了(测试发现不影响运行),但可以看出,操作不当,字节码还是有一定风险。

javaasist框架demo:
github:https://github.com/chengbinghan/apm/tree/master/javaassist
上述demo展示了如何使用javaassist修改字节码.包括对内部类的处理。

2. agent说明
java 提供了agent机制,有两个修改时机,一个是class文件加载进虚拟机时,一个是在runtime,即我们后续例子。
可以通过参数:-javaagent 指定agent jar 包位置,其实在jdk 中有一个示例jar,以jdk1.8为例,在JRE_HOEM/lib/management-agent.jar
这是一个空的jar包,其中的manifest.mf如下


Premain-Class: 对应是在加载进jvm时。
我们以pingpoint1.7.1为参考:
pinpoint的agent 指定的一个启动参数是:
-javaagent:path/pinpoint-bootstrap-1.7.1.jar

们注意到premain-class指定的class有一个方法premain,这个方法即入口函数Agent-Class:对应运行时。

3.运行时修改字节码案例
github:地址:
修改工程:https://github.com/chengbinghan/apm/tree/master/agentMain
测试工程:https://github.com/chengbinghan/apm/tree/master/web413
UpdateClazz的main函数中根据某虚拟机的pid将agent 的jar包load到agent.

agent jar 包的manifest 文件中指定了agentMain方法所在的类

AgentMain 的agentMain方法中调用Transformer修改修改字节码,案例中使用javaasist修改字节码。
ClassPool cp = ClassPool.getDefault();
CtClass cc = null;
try {
cc = cp.get("com.hcb.agent.update.HcbSimplePrincipal");
cc.defrost();
CtConstructor ctc = cc.getConstructors()[0];
ctc.setBody("{name=\"modify nameaaaaaaaaaaaaaaaaa\";this.name = name;}");
System.out.println("modify nameaaaaaaaaaaaaaaaa=================>");
final Class<? extends CtConstructor> aClass = ctc.getClass();
bytes = cc.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
return bytes;
测试步骤是:
访问启动tomcat访问对于servlet

UpdateClazz修改字节码(可以用jps命令获取pid)
再次访问

注意:整个过程,我们并没有重启服务器




