Java项目中ASM字节码处理
增加asm的依赖关系
implementation("org.ow2.asm:asm:9.4")
implementation("org.ow2.asm:asm-tree:9.4")
Comparable的原始接口如下面代码所示:
public interface Comparable {
int LESS = -1;
int EQUAL = 0;
int GREATER = 1;
int compareTo(Object o);
}
RemoveMethodAdapter的代码如下所示:
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import static org.objectweb.asm.Opcodes.ASM4;
public class RemoveMethodAdapter extends ClassVisitor {
private String mName;
private String mDesc;
public RemoveMethodAdapter(
ClassVisitor cv, String mName, String mDesc) {
super(ASM4, cv);
this.mName = mName;
this.mDesc = mDesc;
}
@Override
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
if (name.equals(mName) && desc.equals(mDesc)) {
// do not delegate to next visitor -> this removes the method
System.out.println("remove method name is " + mName + " desc is " + mDesc);
return null;
}
return cv.visitMethod(access, name, desc, signature, exceptions);
}
}
ChangeFieldVisitor的代码如下所示:
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import static org.objectweb.asm.Opcodes.ASM4;
public class ChangeFieldVisitor extends ClassVisitor {
private String name;
private Object value;
protected ChangeFieldVisitor(ClassVisitor cv, String name, Object value) {
super(ASM4, cv);
this.name = name;
this.value = value;
}
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
if (name.equals(this.name)) {
// do not delegate to next visitor -> this removes the method
System.out.println("change field visitor is " + this.name + " value is " + this.value);
return super.visitField(access, name, descriptor, signature, this.value);
}
return super.visitField(access, name, descriptor, signature, value);
}
}
ComparableTest的代码如下:
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import java.io.*;
public class ComparableTest {
public static void main(String[] args) throws IOException {
// Input class file
File inputFile = new File("build/classes/java/main/Comparable.class");
ClassReader cr = null;
try {
FileInputStream fis = new FileInputStream(inputFile);
cr = new ClassReader(fis);
} catch (IOException e) {
throw new RuntimeException(e);
}
ClassWriter cw = new ClassWriter(0);
RemoveMethodAdapter ca = new RemoveMethodAdapter(cw, "compareTo", "(Ljava/lang/Object;)I");
ChangeFieldVisitor changeFieldVisitor = new ChangeFieldVisitor(ca, "LESS", 2);
cr.accept(changeFieldVisitor, 0);
// Write the modified class to a new file
File outputFile = new File("build/classes/java/main/Comparable.class");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(outputFile);
fos.write(cw.toByteArray());
fos.close();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
System.out.println("Class modified and saved to: " + outputFile.getAbsolutePath());
// Comparable comparable = new Comparable() {
// @Override
// public int compareTo(Object o) {
// return 0;
// }
// };
System.out.print("Comparable.LESS is " + Comparable.LESS);
}
private static String getClassPath(Class<?> clazz) {
System.out.println("clazz path is " + clazz.getName().replace('.', '/') + ".class");
return clazz.getName().replace('.', '/') + ".class";
}
}
使用ASM可以对.class字节码增加属性,删除属性,修改属性,增加方法,删除方法,修改方法等逻辑操作。ClassReader基于事件驱动,ASM使用访问者设计模式,ClassVisitor可以链式链接,这样就能依次实现相关的业务逻辑。
代码的执行结果如下:
> Task :ComparableTest.main()
change field visitor is LESS value is 2
Class modified and saved to: /Users/qingfeng/IdeaProjects/KotlinFirst/build/classes/java/main/Comparable.class
Comparable.LESS is 2
BUILD SUCCESSFUL in 336ms
3 actionable tasks: 1 executed, 2 up-to-date
14:24:46: Execution finished ':ComparableTest.main()'.
Android字节码处理
Android的Transform API(在Android Gradle插件(AGP)中用于在构建过程中修改类文件和字节码)已被弃用(AGP7.0+弃用了Transform API,而AGP 8.0+ 则将其完全删除。)。取而代之的是 ASM(用于 Instrumentation 的 Android Gradle 插件 API)。从Android的开发教程中得到如下信息:使用Instrumentation相对于Transfrom性能提升18%,代码编写数量是原来的五分之一左右。Transform被废弃的一个重要原因是编译过程中多次回归编译导致性能低。
androidAsmClassVisitorFactory插桩
下面相关逻辑在buildSrc模块中。
创建MyClassVisitor(用于更改字节码)
创建MyClassVisitor.java以将日志注入sayHello()方法中。
import org.objectweb.asm.*;
import static org.objectweb.asm.Opcodes.*;
public class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM9, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if ("sayHello".equals(name)) {
return new MyMethodVisitor(mv);
}
return mv;
}
static class MyMethodVisitor extends MethodVisitor {
public MyMethodVisitor(MethodVisitor mv) {
super(Opcodes.ASM9, mv);
}
@Override
public void visitCode() {
super.visitCode();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("[ASM] Entering sayHello()");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
}
}
如果MyClassVisitor的方法visitMethod的返回值为null,代表class中的方法被删除了。
创建MyClassVisitorFactory
重写createClassVisitor和isInstrumentable方法。
import com.android.build.api.instrumentation.*;
import org.objectweb.asm.ClassVisitor;
public abstract class MyClassVisitorFactory implements AsmClassVisitorFactory<InstrumentationParameters.None> {
@Override
public ClassVisitor createClassVisitor(ClassContext classContext, ClassVisitor nextVisitor) {
return new MyClassVisitor(nextVisitor);
}
@Override
public boolean isInstrumentable(ClassData classData) {
return classData.getClassName().equals("com.zj.android_asm.MainActivity");
}
}
创建MethodRecordPlugin
Plugin注册ClassVisitorFactory。
import com.android.build.api.instrumentation.FramesComputationMode
import com.android.build.api.instrumentation.InstrumentationScope
import com.android.build.api.variant.AndroidComponentsExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
class MethodRecordPlugin : Plugin<Project> {
override fun apply(target: Project) {
val androidComponents = target.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.onVariants { variant ->
variant.instrumentation.transformClassesWith(
MyClassVisitorFactory::class.java,
InstrumentationScope.ALL
) {}
variant.instrumentation.setAsmFramesComputationMode(
FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS
)
}
}
}
创建newmethod.properties文件
main目录下新建resources/META-INF/gradle-plugins目录,同时配置MethodRecordPlugin,内容如下:
implementation-class=com.zj.asm.methodrecord.MethodRecordPlugin
注意:以下内容都是在app模块中。
app模块中使用定义的MethodRecordPlugin
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id("newmethod") apply true
}
id("newmethod") apply true配置中newmethod就是buildSrc配置Plugin的properties文件名。
MainActivity
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.zj.android_asm.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
sayHello()
}
fun sayHello() {
println("Hello, World!")
}
private fun test(time: Long) {
Thread.sleep(time)
}
private fun sum(i: Int, j: Int): Int {
return i + j
}
}
最后执行结果如下图所示:
如上图代表插桩已经成功了。
总结
ASM可以做的事情很多,比如通过添加字段、添加方法和改变现有方法的行为来操作现有的Java类,从而达到改变应用行为的目的,ASM还可以做分析。希望文章对您有所帮助。
参考资料
ASM官网:asm.ow2.io/
将build配置从Groovy迁移到 Kotlin:developer.android.com/build/migra…