SourceCode Crack——ASM

前言

ASM 是什么

  • 官方介绍:ASM is an all purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or dynamically generate classes, directly in binary form
  • ASM是一个多用途的Java字节码操作和分析框架。它可以被用来修改现有类或动态生成的类,直接以二进制形式
  • ASM 应该是 Assembly缩写(官方没有缩写介绍,个人理解)

哪些著名的框架使用了ASM

  • Languages and AOP tools: AspectWerkz | AspectJ | BeanShell | CGLIB | dynaop | Clojure | Groovy
  • Java ME: EclipseME | MicroEmulator Sun Java ME emulation for Java SE |
  • Tools and frameworks: Fractal | Dr. Garbage | Proactive | Retrotranslator | RIFE | R-OSGi | Terracotta
  • Persistence: EasyBeans | Ebean | JDBCPersistence | JPOX | OpenEJB | Oracle BerkleyDB
  • Monitoring: BEA WebLogic | BTrace | Byteman | JiP | ByCounter | Limpid Log
  • Testing and code analysis: Agitar | Cobertura | Eclipse | JCarder | SemmleCode | Structure101 | SonarJ

ASM特点

ASM特点

  1. 短小精悍、快速、健壮
  2. 具有很好的文档,并且还有eclipse\Idea插件
  3. 开源

ASM Core Api 设计

主要的类介绍(ASM4.0)
  1. ClassVisitor:重要的抽象类,定义在读取Class字节码时会触发的事件,如类头部解析完成、注解解析、字段解析、方法解析等
  2. ClassReader:重要的类,字节码的读取与分析引擎。它采用类似SAX的事件读取机制,每当有事件发生时,调用注册的ClassVisitor、AnnotationVisitor、FieldVisitor、MethodVisitor做相应的处理
  3. FieldVisitor:抽象类,定义在解析类属性时触发的事件
  4. MethodVisitor接口:定义在解析方法时触发的事件,如方法上的注解、属性、代码等
  5. ClassWriter类:实现了ClassVisitor接口,用于拼接字节码
  6. FieldWriter类:实现了FieldVisitor接口,用于拼接类属性相关字节码
  7. MethodWriter类:实现了MethodVisitor接口,用于拼接方法相关字节码
  8. Opcodes接口:字节码指令的一些常量定义
  9. Type类:类型相关的常量定义以及一些基于其上的操作

ClassVisitor类介绍

ClassVisitor 是重要的抽象类,在ASM4.0版本中是抽象方法,调用必须是遵循下面的调用顺序的:

1
2
visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )
( visitInnerClass | visitField | visitMethod ) visitEnd

  • 方法void visit(int version,int access,String name,String signature,String superName,String[] interfaces);
    当调用reader.accept()扫描类时第一个调用的方法就是visit,主要用于类声明时使用。参数的含义如下:
  1. version JDK的版本,V1_6表示JDK1.6版本
  2. access表示类的修饰符,public, private,static 等等
  3. name表示类名称
  4. signature表示泛型信息
  5. superName表示继承父类的名称
  6. interfaces表示实现接口的名称数组
  • FieldVisitor visitField(int access,String name,String desc,String signature,Object value)
    visitField是调用accept()方法扫描到类中字段时进行调用的。主要参数的含义如下:
  1. name表示字段名称
  2. desc表示字段的类型,I(int)、Z(boolean)等等
  3. value表示是默认值
  • MethodVisitor visitMethod(int access,String name,String desc,String signature,String[] exceptions)
    visitMethod是调用accept()方法扫描到类中字段时进行调用的。主要参数含义如下:
  1. name表示方法名称
  2. desc表示方法返回值类型
  3. exceptions表示方法会抛出的异常数组
  • 类型的简写对应关系
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    "I"        = int
    "B" = byte
    "C" = char
    "D" = double
    "F" = float
    "J" = long
    "S" = short
    "Z" = boolean
    "V" = void
    "[...;" = 数组
    "[[...;" = 二维数组
    "[[[...;" = 三维数组
    "L....;" = 引用类型

ClassReader实现

ClassReader是ASM中最核心的实现类,它作用是去解析Class字节码。当ClassReader解析到Method、Field、Annotation等都会触发事件。

1
2
ClassReader reader = new ClassReader("com.alibaba.Apple");
reader.accept(enhancer, ClassReader.SKIP_DEBUG);

ClassReader的构造函数的参数是class的文件流或者classPath。通过accept()方法解析上述的文件流,每当有事件发生时,会调用注册的继承于ClassVisitor、AnnotationVisitor、FieldVisitor、MethodVisitor等实现类的相应方法。accept()方法中关键代码

1
2
3
4
5
6
7
8
9
10
// calls the visit method
classVisitor.visit(readInt(4),access,name,signature,superClassName,implementedItfs);
// calls the visitSource method
if (!skipDebug && (sourceFile != null || sourceDebug != null)) {
classVisitor.visitSource(sourceFile, sourceDebug);
}
// calls the visitOuterClass method
if (enclosingOwner != null) {
classVisitor.visitOuterClass(enclosingOwner,enclosingName,enclosingDesc);
}

上述的代码我们也可以看出ClassVisitor调用顺序visitSource、visitOuterClass、visitAnnotation ………..visitEnd。
关于Java字节码格式可以参考这篇博客

ClassWriter类

ClassWriter继承于ClassVisitor,提供很多修改字节码的方法;通过toByteArray()方法可以得到字节码的byte数组,然后用类加载器动态加载代理类class,如下:

1
2
byte[] code = cw.toByteArray();
Class newClass = defineClass(proxyClassName, code, 0, code.length);

工具类ClassPrinter(打印class扫描阶段的基本信息)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class ClassPrinter extends ClassVisitor {
public ClassPrinter(int i) {
super(i);
}

public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
System.out.println(name + " extends " + superName + " {");
}

public void visitSource(String source, String debug) {
System.out.println("source:"+source+" debug:"+debug);
}

public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
System.out.println(" " + desc + " " + name);
return null;
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
System.out.println(" " + desc + " " + name);
return null;
}

public void visitEnd() {
System.out.println("}");
}

public static void main(String[] args) {
try {
ClassReader cr = new ClassReader("com.alibaba.Apple");
ClassPrinter cp = new ClassPrinter(Opcodes.ASM4);
cr.accept(cp, ClassReader.SKIP_DEBUG);
} catch (IOException e) {
e.printStackTrace();
}
}
}

运行结果

1
2
3
4
com/alibaba/Apple extends java/lang/Object {
()V <init>
()V testApple
}

例子:类生成

我们生成这样Apple类,有个字段String name,有个方法testApple(String param),具体如下:

1
2
3
4
5
6
public class Apple {
private String name;
public void testApple(String param){
System.out.println("testApple: "+param);
}
}

生成这样一个类,需要调用ClassVisitor和ClassWriter等工具类,然后使用ClassLoader加载类,具体实现的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class ClassGenTest extends ClassLoader implements Opcodes {
public byte[] genBytes(){
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, "com/alibaba/Apple", null, "java/lang/Object", null);
cw.visitSource("Apple.java", null);
// 定义字段name
{
fv = cw.visitField(ACC_PRIVATE, "name", "Ljava/lang/String;", null, null);
fv.visitEnd();
}
// 默认构造函数
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(11, l1);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
// 定义函数public void testApp(String)
{
mv = cw.visitMethod(ACC_PUBLIC, "testApple", "(Ljava/lang/String;)V", null, null);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");
mv.visitLdcInsn("testApple: ");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
mv.visitInsn(RETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitMaxs(3, 2);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}

public Class genClass(){
byte[] bytes = this.genBytes();
return this.defineClass(bytes, 0, bytes.length);
}

public static void main(String[] agrs) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
ClassGenTest classGenTest = new ClassGenTest();
Class proxyClass = classGenTest.genClass();
// 测试生成的Apple类的实例,能否调用testApple
Object proxyObject = proxyClass.newInstance();
Method method = proxyClass.getDeclaredMethod("testApple", new Class[]{String.class});
method.invoke(proxyObject, "hello asm");
}
}

使用 ASM 实现 Java 语言的“多重继承”

http://www.ibm.com/developerworks/cn/java/j-lo-asm/


插件ByteCode Outline使用介绍

我们看到生成Apple类是用调用了很多ClassVisitor和ClassWriter的方法,看上去比较的复杂,也标的繁琐。实际开发中我们先使用工具大致生成构建代码,然后修改。
博主使用的工具是ByteCode Outline,它是一款IDE插件,在Eclipse和IDEA下都有的插件。在IDEA下安装和使用方法方法如下:打开插件管理File | Settings | Plugins
bcol
bcol
bcol

JDK Instrument功能介绍简介

我们使用ASM,Cglib或者JDK Dynamic Proxy的时候经常会生产代理类,如何才能查看这些代理类的代码呢?Java5开始引入了“java.lang.instrument”包,在更底层实现字节码拦截功能,我们也可以用instrument包来修改字节码。
Instrument 要求在运行前利用命令行参数或者系统参数来设置代理类,在实际的运行之中,虚拟机在初始化之时(在绝大多数的 Java 类库被载入之前),instrumentation 的设置已经启动,并在虚拟机中设置了回调函数,检测特定类的加载情况,并完成实际工作。

如何使用Instrument功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class DumpClassFileTransformer implements ClassFileTransformer {

public DumpClassFileTransformer() {
}

public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
// transform会拦截代码,可以输出到本地文件,也可以修改字节码
try {
File targetFile = new File("dump/" + className + ".class");
targetFile.getParentFile().mkdirs();
FileOutputStream fout = new FileOutputStream(targetFile);
fout.write(classfileBuffer);
fout.close();
} catch (Exception e) {
e.printStackTrace();
}

return classfileBuffer;
}

public static void premain(String agentArgs, Instrumentation instrumentation) {
instrumentation.addTransformer(new DumpClassFileTransformer());
}
}

把DumpClassFileTransformer类打成jar包(可以使用Eclipse或者IDEA的带出为jar的功能),目录结构如下

1
2
3
4
5
aop.jar
│─ DumpClassFileTransformer.class

└─META-INF
MANIFEST.MF

MANIFEST.MF文件内容只有一行:Manifest-Version: 1.0

运行时需要添加JVM参数

1
-javaagent:D:\code\proxy\aop.jar