使用ASM编写 打印方法运行的时间 代码分析

    技术2023-08-12  107

    请先简单阅读下原文代码

    https://blog.csdn.net/weixin_44618248/article/details/107086410 可以对照源代码在阅读本文同时可以看得更清晰一些

    注⚠️:以下属于个人学习,理解 如果偏颇过深 非常欢迎在评论看到您的看法和想法

    先看前一段代码

    val startTimeLabel = newLabel() //标签 val endTimeLabel = newLabel() //标签

    ⬆️ 用于向 本地变量表1⃣️ 中插入变量时候使用

    var startTimeIndex: Int = 0

    ⬆️ 用于记录 变量在 本地变量表1⃣️ 里面的位置 因为待会要从这个 本地变量表1⃣️ 里面去取数据

    override fun onMethodEnter() { super.onMethodEnter() startTimeIndex = newLocal(Type.DOUBLE_TYPE)

    ⬆️ 通过 newLocal方法新建一个类型为 Double 的变量并记录它在 本地变量表1⃣️ 中的位置

    startTimeLabel.let { visitLabel(it) }

    ⬆️ 记录label的顺序 表示在这一行开始

    mv.visitLocalVariable( "startTime", "J", null, startTimeLabel, endTimeLabel, startTimeIndex )

    ⬆️ 向 本地变量表1⃣️ 中声明变量 :

    变量名称叫做startTime类型为 J2⃣️没有泛形变量存活生命周期开始于 startTimeLabel变量存活生命周期结束于 endTimeLabel它的下标位置在 startTimeIndex mv.visitMethodInsn( Opcodes.INVOKESTATIC, "android/os/SystemClock", "currentThreadTimeMillis", "()J", false )

    ⬆️ 在该字节码下一行插入语句: SystemClock.currentThreadTimeMillis() 对应位置参数:

    Opcodes.INVOKESTATIC -> 表示执行一个类的静态方法类叫做android/os/SystemClock方法名称叫 currentThreadTimeMillis方法的描述符号3⃣️ 为()J println("插入本地变量表index=${startTimeIndex}") mv.visitVarInsn(Opcodes.LSTORE, startTimeIndex) }

    ⬆️ 把刚刚方法的返回值 存到下标为 startTimeIndex 的本地变量表中 Opcodes.LSTORE 是指令 表示存一个Long类型的变量 为什么这样就存入到 本地变量表?因为方法执行后 返回值还在栈中 可以继续用指令 操作栈中数据

    方法即将要执行完毕的时候给他插入一些字节码

    override fun onMethodExit(opcode: Int) { //求时间差 并存入 本地变量表 mv.visitMethodInsn( Opcodes.INVOKESTATIC, "android/os/SystemClock", "currentThreadTimeMillis", "()J", false )

    ⬆️ 执行方法 SystemClock.currentThreadTimeMillis()

    mv.visitVarInsn(Opcodes.LLOAD, startTimeIndex)

    ⬆️ 从下标为 startTimeIndex 的 本地变量表1⃣️ 中获取值 并放入栈

    mv.visitInsn(Opcodes.LSUB)

    ⬆️ 栈中元素 进行相减 操作

    mv.visitVarInsn(Opcodes.LSTORE, startTimeIndex)

    ⬆️ 栈中元素 存入到下标为 startTimeIndex 的 本地变量表1⃣️

    //StringBuilder 构建str 并存入本地变量表 val strVarIndex = newLocal(Type.getType(String::class.java)) val strBeginLabel = newLabel() val strEndLabel = newLabel() mv.visitLocalVariable( "__tempStr", "Ljava/lang/String;", null, strBeginLabel, strEndLabel, strVarIndex ) strBeginLabel.let { visitLabel(it) }

    ⬆️ 创建变量 __tempStr (前面有过类似 就不在赘述)

    //NEW java/lang/StringBuilder mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder")

    ⬆️ new 一个对象 对象为StringBuilder

    // DUP mv.visitInsn(Opcodes.DUP)

    ⬆️ 复制栈顶数值并将复制值压入栈顶

    // INVOKESPECIAL java/lang/StringBuilder.<init> ()V mv.visitMethodInsn( Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false )

    ⬆️ 调用方法 < init >

    // LDC "Wtf" mv.visitLdcInsn(customTAG)

    ⬆️ 向栈中放入字符串(Wtf) customTAG里面的值(Wtf)

    // INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false )

    ⬆️ 调用方法 append 相当于 StringBuilder.append(“Wtf”)

    // ILOAD 2 mv.visitLdcInsn(",方法${name}消耗时间:") // INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder; mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false )

    ⬆️ 继续调用方法 相当于 StringBuilder.append(",方法${name}消耗时间:")

    //求得的时间 入栈 mv.visitVarInsn(Opcodes.LLOAD, startTimeIndex) //append 到 StringBuilder上 mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false )

    ⬆️ 从本地变量表中读取 并执行相当于: StringBuilder.append(startTime)

    // ILOAD 2 mv.visitLdcInsn("毫秒") // INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder; mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false )

    ⬆️ 继续调用方法 相当于 StringBuilder.append(“毫秒”)

    //toString // INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false )

    ⬆️ 继续调用方法 相当于 StringBuilder.toString()

    mv.visitVarInsn(Opcodes.ASTORE, strVarIndex)

    ⬆️ 栈中值 存入到 下标为 strVarIndex的变量中

    mv.visitFieldInsn( Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;" ) mv.visitVarInsn(Opcodes.ALOAD, strVarIndex) mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false )

    ⬆️ 继续调用方法 相当于 System.out.println()

    endTimeLabel.let { visitLabel(it) } strEndLabel.let { visitLabel(it) } super.onMethodExit(opcode) }

    本地变量表

    此图是我用javap 命令(javap -c -v xxx.class)获取到的(编译的时候要加额外参数 javac -g xxx.java) 本地变量表是方法内变量的集合 例如实例方法中的this 你可以直接用 是因为 方法默认给你 设置了一个 静态方法就没有 所以你也能明白了吧 在一个类中 它的静态方法为什么不可以调用它的实例方法

    字节码中的变量类型

    java代码中字节码中booleanZintIlongJfloatFdoubleD对象类型L+全类名;[][+对应类型

    方法描述符号

    规则 ()+返回值 eg: ()V 表示 无入参 且无返回值 (I)V ➡️ fun(Int) (Ljava/lang/String;)Ljava/lang/String; ➡️ fun(String):String

    特殊方法

    < init > : 构造方法
    Processed: 0.012, SQL: 10