没有合适的资源?快使用搜索试试~ 我知道了~
首页ASM4 Java类运行时生成与转换实用指南
ASM4 Java类运行时生成与转换实用指南
需积分: 9 3 下载量 68 浏览量
更新于2024-07-15
收藏 1.23MB PDF 举报
ASM4使用指南是一份专注于Java字节码操作的实用文档,由贾洪峰翻译,由OneAPM公司提供赞助。该指南主要关注的是Java字节码级别的程序分析、生成和转换技术。ASM库,作为核心内容,特别设计用于在运行时处理编译后的Java类,支持动态类生成和转换,以适应各种应用场景,如逆向工程、代码优化、混淆和调试插入。 在第一部分的引言中,作者强调了程序分析和生成技术的重要性,它们在软件开发中的广泛应用,如检测bug、代码优化、编译器设计、JIT编译、混淆保护和面向对象编程等。这些技术对于Java语言尤其关键,因为它支持源代码和编译后的.class文件操作,使得在运行时对类进行操作成为可能,无需依赖于繁琐的源代码编译过程。 ASM库的设计目标是追求高效性和轻量级。由于它主要用于在应用程序运行时处理类,因此速度至关重要,以避免对应用程序整体性能造成显著影响。同时,小型化的库尺寸对于内存受限环境和小型应用或库的开发者来说,也是非常吸引人的,因为它能减少额外的内存占用。 然而,ASM并非专为Java设计的唯一工具,其他语言也有相应的工具,但由于其灵活性和效率,ASM在Java领域尤为知名。尽管如此,ASM并不是万能的解决方案,它可能不是所有程序分析和转换需求的最佳选择,需要根据具体项目特点和性能要求来选择合适的工具。 ASM4使用指南提供了深入理解Java字节码操作、利用ASM库进行高效程序处理的关键知识,对于开发者在实现程序优化、安全增强和性能监控等方面具有很高的参考价值。
资源详情
资源推荐
《ASM4 使用指南》- 第 2 章 类
15 / 101
byte[] b2 = cw.toByteArray();
执行这一优化后,由于 ChangeVersionAdapter 没有转换任何方法,所以以上代码的
速度可以达到之前代码的两倍。对于转换部分或全部方法的常见转换,这一速度提升幅度可能要
小一些,但仍然是很可观的:实际上在 10%到 20%的量级。遗憾的是,这一优化需要将原类中
定义的所有常量都复制到转换后的类中。对于那些增加字段、方法或指令的转换来说,这一点不
成问题,但对于那些要移除或重命名许多类成员的转换来说,这一优化将导致类文件大于未优化
时的情况。因此,建议仅对“增加性”转换应用这一优化。
3. 使用转换后的类
如上节所述,转换后的类 b2 可以存储在磁盘上,或者用 ClassLoader 加载。但在
ClassLoader 中执行的类转换只能转换由这个类加载器加载的类。如果希望转换所有类,则必
须将转换放在 ClassFileTransformer 内部,见 java.lang.instrument 包中的定义(更
多细节,请参阅这个软件包的文档):
public static void premain(String agentArgs, Instrumentation inst) {
inst. ad dT ransfor me r( new Cla ss Fi leTrans fo rm er () {
publi c by te[] tr an sf orm(Cla ss Lo ader l, S tr in g nam e, C la ss c,
Prote ct io nDomain d , byte[] b)
throw s Il legalCl as sF ormatEx ce pt ion {
Class Re ad er cr = n ew ClassR ea de r(b);
Class Wr it er cw = n ew ClassW ri te r(cr, 0 );
Class Vi si tor cv = ne w Chang eV er sionAda pt er (c w);
cr.ac ce pt (cv, 0) ;
retur n cw .toByte Ar ra y();
}
});
}
2.2.5 移除类成员
上一节用于转换类版本的方法当然也可用于 ClassVisitor 类的其他方法。例如,通过改
变 visitField 和 visitMethod 方法的 access 或 name 参数,可以改变一个字段或一个方
法的修饰字段或名字。另外,除了在转发的方法调用中使用经过修改的参数之外,还可以选择根
本不转发该调用。其效果就是相应的类元素被移除。
例如,下面的类适配器移除了有关外部类及内部类的信息,还删除了一个源文件的名字,也
就是由其编译这个类的源文件(所得到的类仍然具有全部功能,因为删除的这些元素仅用于调试
目的)。这一移除操作是通过在适当的访问方法中不转发任何内容而实现的:
public class RemoveDebugAdapter extends ClassVisitor {
publi c Re moveDeb ug Ad apter(C la ss Visitor c v) {
super (A SM 4, cv);
}
@Over ri de
publi c vo id visi tS ou rce(Str in g source, S tr in g deb ug ) {
}
@Over ri de
publi c vo id visi tO ut erClass (S tr ing own er , St ring na me , String de sc ) {
}
@Over ri de
《ASM4 使用指南》- 第 2 章 类
16 / 101
publi c vo id visi tI nn erClass (S tr ing nam e, S tr ing o ut er Na me,
Strin g in nerName , in t acces s) {
}
}
这一策略对于字段和方法是无效的,因为 visitField 和 visitMethod 方法必须返回一
个结果。要移除字段或方法,不得转发方法调用,并向调用者返回 null。例如,下面的类适配
器移除了一个方法,该方法由其名字及描述符指明(仅使用名字不足以标识一个方法,因为一个
类中可能包含若干个具有不同参数的同名方法):
public class RemoveMethodAdapter extends ClassVisitor {
priva te S tring m Na me ;
priva te S tring m De sc ;
publi c Re moveMet ho dA dapter(
Class Vi si tor cv, S tr ing mNa me , String mD es c) {
super (A SM 4, cv);
this. mN am e = mNa me ;
this. mD es c = mDe sc ;
}
@Over ri de
publi c Me thodVis it or visitM et ho d(int a cc es s, Stri ng n am e,
Strin g de sc, Str in g signatu re , String[ ] ex ce ption s) {
if (name. equals(mNam e) && desc.equals(mDesc) ) {
// 不要委托至下一个访问器 -> 这样将移除该方法
return null;
}
retur n cv .visitM et ho d(acces s, n ame, de sc , si gnatu re , ex ception s) ;
}
}
2.2.6 增加类成员
上述讨论的是少转发一些收到的调用,我们还可以多“转发”一些调用,也就是发出的调用
数多于收到的调用,其效果就是增加了类成员。新的调用可以插在原方法调用之间的若干位置,
只要遵守各个 visitXxx 必须遵循的调用顺序即可(见 2.2.1 节)。
例如,如果要向一个类中添加一个字段,必须在原方法调用之间添加对 visitField 的一
个新调用,而且必须将这个新调用放在类适配器的一个访问方法中。比如,不能在 visit 方法
中这样做,因为这样可能会导致对 visitField 的调用之后跟有 visitSource 、
visitOuterClass、visitAnnotation 或 visitAttribute,这是无效的。出于同样的原
因,不能将这个新调用放在 visitSource、visitOuterClass、visitAnnotation 或
visitAttribute 方法中. 仅有的可能位置是 visitInnerClass 、 visitField 、
visitMethod 或 visitEnd 方法。
如果将这个新调用放在 visitEnd 方法中,那这个字段将总会被添加(除非增加显式条件),
因为这个方法总会被调用。如果将它放在 visitField 或 visitMethod 中,将会添加几个字
段:原类中的每个字段和方法各有一个相应的字段。这两种解决方案都可能发挥应有的作用;具
体取决于你的需求。例如,可以仅添加一个计数器字段,用于计算对一个对象的调用次数,也可
以为每个方法添加一个计数器,用于分别计算对每个方法的调用次数。
《ASM4 使用指南》- 第 2 章 类
17 / 101
注意:事实上,惟一真正正确的解决方案是在
visitEnd 方法中添加更多调用,以添加新成员。实际上,
一个类中不得包含重复成员,要确保一个新成员没有重复成员,惟一方法就是将它与所有已有成员进行对
比,只有在 visitEnd 方法中访问了所有这些成员后才能完成这一工作。这种做法是相当受限制的。在
实践中,使用程序员不大可能使用的生成名,比如
_counter$或_4B7F_ i 就足以避免重复成员了,
并不需要将它们添加到
visitEnd 中。注意,在第一章曾经讨论过,树 API 没有这一限制:可以在任意
时刻向使用这个 API 的转换中添加新成员。
为了举例阐述以上讨论,下面给出一个类适配器,它会向类中添加一个字段,除非这个字段
已经存在:
public class AddFieldAdapter extends ClassVisitor {
priva te i nt fAcc ;
priva te S tring f Na me ;
priva te S tring f De sc ;
priva te b oolean is Fi eldPres en t;
publi c Ad dFieldA da pt er(Clas sV is itor cv , in t fAcc, S tr in g fName ,
Strin g fD esc) {
super (A SM 4, cv);
this. fA cc = fAcc ;
this. fN am e = fNa me ;
this. fD es c = fDe sc ;
}
@Over ri de
publi c Fi eldVisi to r visitFi el d( int acc es s, S tring n am e, String d es c,
Strin g si gnature , Ob ject va lu e) {
if (name. equals(fNam e)) {
isFieldPresent = true;
}
retur n cv .visitF ie ld (access , na me, des c, s ig natur e, v al ue);
}
@Over ri de
publi c vo id visi tE nd () {
if (!isFi eldPresent) {
FieldVisitor fv = cv.visit Field(fAcc, fName, fDesc, null, nul l);
if (fv != null) {
fv.visit End();
}
}
cv.vi si tE nd();
}
}
这个字段被添加在 visitEnd 方法中。visitField 方法未被重写为修改已有字段或删除
一个字段,只是检测一下我们希望添加的字段是否已经存在。注意 visitEnd 方法中在调用
fv.visitEnd()之前的 fv != null 检测:这是因为一个类访问器可以在 visitField 中返
回 null,在上一节已经看到这一点。
2.2.7 转换链
到目前为止,我们已经看到一些由 ClassReader、类适配器和 ClassWriter 组成的简单
转换链。当然可以使用更为复杂的转换链,将几个类适配器链接在一起。将几个适配器链接在一
起,就可以组成几个独立的类转换,以完成复杂转换。还要注意,转换链不一定是线性的。我们
《ASM4 使用指南》- 第 2 章 类
18 / 101
可以编写一个 ClassVisitor,将接收到的所有方法调用同时转发给几个 ClassVisitor:
public class MultiClassAdapter extends ClassVisitor {
prote ct ed
ClassVisitor[] cvs;
publi c Mu ltiClas sA da pter(ClassVisitor[] cvs) {
super (A SM 4);
this. cv s = cvs;
}
@Over ri de public v oi d visit (i nt versio n, i nt acce ss , St ring na me ,
Strin g si gnature , St ring su pe rN ame, St ri ng [] inte rf ac es ) {
for (ClassVisitor cv : cvs) {
cv.vi si t( version , ac cess, n am e, signat ur e, s uperN am e, i nterfac es );
}
}
...
}
反过来,几个类适配器可以委托至同一 ClassVisitor(这需要采取一些预防措施,确保
比如 visit 和 visitEnd 针对这个 ClassVisitor 恰好仅被调用一次)。因此,诸如图 2.8 所
示的这样一个转换链是完全可行的。
图 2.8一个复杂转换
2.3 工具
除了 ClassVisitor 类和相关的 ClassReader、ClassWriter 组件之外,ASM 还在
org.objectweb.asm.util 包中提供了几个工具,这些工具在开发类生成器或适配器时可能
非常有用,但在运行时不需要它们。ASM 还提供了一个实用类,用于在运行时处理内部名、类
型描述符和方法描述符。所有这些工具都将在下面介绍。
2.3.1 Type
在前几节已经看到,ASM API 公开 Java 类型的形式就是它们在已编译类中的存储形式,也
就是说,作为内部特性或类型描述符。也可以按照它们在源代码中的形式来公开它们,使代码更
便于阅读。但这样就需要在 ClassReader 和 ClassWriter 中的两种表示形式之间进行系统
转换,从而使性能降低。这就是为什么 ASM 没有透明地将内部名和类型描述符转换为它们等价
《ASM4 使用指南》- 第 2 章 类
19 / 101
的源代码形式。但它提供了 Type 类,可以在必要时进行手动转换。
一个 Type 对象表示一种 Java 类型,既可以由类型描述符构造,也可以由 Class 对象构建。
Type 类还包含表示基元类型的静态变量。例如,Type.INT_TYPE 是表示 int 类型的 Type 对
象。
getInternalName 方法返回一个 Type 的内部名。例如,
Type.getType(String.class). getInternalName()给出 String 类的内部名,即
"java/lang/String"。这一方法只能对类或接口类型使用。
getDescriptor 方法返回一个 Type 的描述符。比如,在代码中可以不使用
"Ljava/lang/String;" ,而是使用 Type.getType(String.class).
getDescriptor()。或者,可以不使用 I,而是使用 Type.INT_TYPE.getDescriptor()。
Type 对象还可以表示方法类型。这种对象既可以从一个方法描述符构建,也可以由 Method
对象构建。getDescriptor 方法返回与这一类型对应的方法描述符。此外,
getArgumentTypes 和 getReturnType 方法可用于获取与一个方法的参数类型和返回类型
相对应的 Type 对象。例如,Type.getArgumentTypes("(I)V")返回一个仅有一个元素
Type.INT_TYPE 的数组。与此类似,调用 Type.getReturnType("(I)V") 将返回
Type.VOID_TYPE 对象。
2.3.2 TraceClassVisitor
要确认所生成或转换后的类符合你的预期,ClassWriter 返回的字母数组并没有什么真正
的用处,因为它对人类来说是不可读的。如果有文本表示形式,那使用起来就容易多了。这正是
TraceClassVisitor 类提供的东西。从名字可以看出,这个类扩展了 ClassVisitor 类,
并生成所访问类的文本表示。因此,我们不是用 ClassWriter 来生成类,而是使用
TraceClassVisitor,以获得关于实际所生成内容的一个可读轨迹。甚至可以同时使用这两
者,这样要更好一些。除了其默认行为之外,TraceClassVisitor 实际上还可以将对其方法
的所有调用委托给另一个访问器,比如 ClassWriter:
ClassWriter cw = new ClassWriter(0);
TraceClassVisitor cv = new TraceClassVisitor(cw, printWriter);
cv.visit(...);
...
cv.visitEnd();
byte b[] = cw.toByteArray();
这一代码创建了一个 TraceClassVisitor,将它自己接收到的所有调用都委托给 cw,然
后将这些调用的一份文本表示打印到 printWriter。例如,如果在 2.2.3 节的例子中使用
TraceClassVisitor,将会得出:
// 类版本号 49.0 (49)
// 访问标志 15 37
public abstract interface pkg/Comparable implements pkg/Mesurable {
// 访问标志 25
publi c fi nal sta ti c I LESS = -1
剩余101页未读,继续阅读
A__loser
- 粉丝: 7
- 资源: 9
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
- 新型矿用本安直流稳压电源设计:双重保护电路
- 煤矿掘进工作面安全因素研究:结构方程模型
- 利用同位素位移探测原子内部新型力
- 钻锚机钻臂动力学仿真分析与优化
- 钻孔成像技术在巷道松动圈检测与支护设计中的应用
- 极化与非极化ep碰撞中J/ψ的Sivers与cos2φ效应:理论分析与COMPASS验证
- 新疆矿区1200m深孔钻探关键技术与实践
- 建筑行业事故预防:综合动态事故致因理论的应用
- 北斗卫星监测系统在电网塔形实时监控中的应用
- 煤层气羽状水平井数值模拟:交替隐式算法的应用
- 开放字符串T对偶与双空间坐标变换
- 煤矿瓦斯抽采半径测定新方法——瓦斯储量法
- 大倾角大采高工作面设备稳定与安全控制关键技术
- 超标违规背景下的热波动影响分析
- 中国煤矿选煤设计进展与挑战:历史、现状与未来发展
- 反演技术与RBF神经网络在移动机器人控制中的应用
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功