当前位置: 首页 > 工具软件 > ASM > 使用案例 >

一、ASM简单入门

郭修平
2023-12-01

1.ASM简介

动态修改Java的class字节码的框架

官网:https://asm.ow2.io/

1.1 ASM的作用

  • 注解+注入:假设有一个判断登录功能,可以使用注解,在方法开始的地方,解析注解,然后插入代码
  • 闭源代码修改:假设有一个闭源的代码内部有bug,可以通过修改字节码的方式来进行修改
  • 统计功能:Android中可以在方法开始和方法结束,插入代码桩,来得出时间,找出anr

1.2 ASM简单使用

使用idea新建工程,首先导入asm:

    implementation("org.ow2.asm:asm:9.2")
    implementation("org.ow2.asm:asm-commons:9.2")

编写简单的测试类:

ASM01.java

package com.ifreedomer.asm;
public class ASM01 {
    public static void main(String[] args) {
        System.out.println("hello asm");
    }
}

安装插件: asm-bytecode-outline

然后双击shift->show bytecode outline,得到如下字节码:

// class version 52.0 (52)
// access flags 0x21
public class com/ifreedomer/asm/ASM01 {

  // compiled from: ASM01.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 2 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/ifreedomer/asm/ASM01; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 4 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "hello asm"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L1
    LINENUMBER 5 L1
    RETURN
   L2
    LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1
}

我们首先看一下最简单的:

package com.ifreedomer.asm

import org.objectweb.asm.*
import java.io.FileInputStream
import org.objectweb.asm.commons.AdviceAdapter
import org.objectweb.asm.commons.Method

fun main(args: Array<String>) {
    //读取class
    val inputStream = FileInputStream("/Users/xx/Documents/Study/ASMStudy/build/classes/java/main/ASM01.class")
    //class解析类
    val classReader = ClassReader(inputStream)
    //class写出类
    val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
    //class访问类,所有类的信息都可以在这里面获取到
    val classVisitor = AMSClassVisitor(Opcodes.ASM9, classWriter)
    classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES)
}

//记录methodName
var methodName = ""
class AMSClassVisitor(api: Int, classVisitor: ClassVisitor) : ClassVisitor(api, classVisitor) {
    override fun visitMethod(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
        val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions)
        return ASMMethodVisitor(
            api = api,
            name = name,
            methodVisitor = methodVisitor,
            access = access,
            descriptor = descriptor
        )
    }
}

/**
 * @param api asm的版本,@link{Opcodes.ASM9}
 * @param methodVisitor 方法访问器
 * @param access 访问作用于 public protect等
 * @param name 方法名
 * @param descriptor 类方法的签名描述
 */
class ASMMethodVisitor(
    api: Int, methodVisitor: MethodVisitor?, access: Int, name: String?,
    descriptor: String?
) : AdviceAdapter(
    api,
    methodVisitor, access, name, descriptor
) {
    //方法进入
    override fun onMethodEnter() {
        System.out.println("onMethodEnter $methodName")
    }

    //方法退出
    override fun onMethodExit(opcode: Int) {
        System.out.println("onMethodExit $methodName")
    }

    init {
        if (name != null) {
            methodName = name
        }
        println("name = $name  desc = $descriptor")
    }
}

这是ASM最简单的代码了,首先看main方法:

  • 读入class
  • 构造ClassReader
  • 构造ClassWriter
  • 构造ClassVisitor
  • 替换ClassVisitor内部的visitMothod,换成我们自己的ASMMethodVisitor

看看打印:

name = <init>  desc = ()V
onMethodEnter <init>
onMethodExit <init>
name = main  desc = ([Ljava/lang/String;)V
onMethodEnter main
onMethodExit main

对比字节码,我们发现,ASM01.class有两个方法,分别是init()和main(),所以我们的的程序正常work,这是ASM最简单的入门

 类似资料: