从ASM入门字节码增强
ASM作为Java字节码层次的处理框架,能够直接对字节码进行操作,使用ASM能够轻松完成代码注入等字节码增强的相关操作。
基础使用
这里使用一个基础调用展示ASM的使用
val cr = ClassReader(inputStream)val cw = ClassWriter(cr, 0)val cv = MyClassVisitor(Opcodes.ASM6, cw)cr.accept(cv, ClassReader.EXPAND_FRAMES)inputStream.close()val byteArray = cw.toByteArray()
在这里用到了ClassReader,ClassWriter以及一个自定义的ClassVIsitor,首先通过ClassReader读入输入流,之后定义ClassWriter,初始化ClassVisitor,再通过accept开启整个流程,最后通过ClassWriter获取修改过后的输出流。
自定义ClassVisitor如下,这里在Activity的onCreate方法中插入了方法System.out.println(System.currentTimeMills());
class MyClassVisitor(api: Int, cv: ClassVisitor) : ClassVisitor(api, cv) {
override fun visitMethod(access: Int, name: String?, desc: String?, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
val mv = super.visitMethod(access, name, desc, signature, exceptions) if (name == "onCreate") {
mv.visitMaxs(0,0) return BingyanMethodVisitor(api, mv) } return mv } private inner class BingyanMethodVisitor(api: Int, mv: MethodVisitor) : MethodVisitor(api, mv) {
override fun visitCode() {
super.visitCode() mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V", false); } }}
基础准备(字节码)
Class文件结构
ASM作为操作字节码的工具,那么首先会介绍相关字节码的简单结构。用一张表格来表示class文件的结构,class文件中分成了以下十个部分:
字节码结构 | ||
名称 | 大小 | 描述 |
魔数 | 4字节 | 0xcafebabe |
版本号 | 4字节 | 次版本号+主版本号 |
常量池 | 2字节+N | 计数器+数据区 |
访问标志 | 2字节 | 类or接口,以及修饰符 |
当前类索引 | 2字节 | 当前类全限定名(常量池索引) |
父类索引 | 2字节 | 父类的全限定名(常量池索引) |
接口索引 | 2字节+N | 计数器+常量池索引 |
字段表 | 2字节+N | 计数器+类和接口中声明的变量字段的详细信息 |
方法表 | 2字节+N | 计数器+方法信息 |
附加属性 | 类或接口所定义属性的基本信息 |
1、魔数
前四个字节是魔数,固定值为:0xCAFEBABE,用来标识一个文件是class文件。
2、版本号
四个字节标示版本号,其中两个字节为次版本号,后两个字节为主版本号。例如 “00 00 00 34” 转化为16进制后分别为 0 和 52。对应版本1.8.0。
3、常量池
计数器
在常量池中前两位是计数器,用来标示在当前class文件中有多少个常量。将16进制转化成10进制之后-1。
数据区
由指定数量的cp_info结构组成,每一个cp_info有两部分或三部分组成。储存字面量与符号引用,字面量即代码中定义的文本字符串等,而符号引用包括类和接口的全限定名,字段的名称和描述符号,方法的名称和描述符。
CONSTANT_Utf8_info | tag (1 by |