ASM字节码操作:测量方法执行速度

时间:2014-12-07 11:13:19

标签: java bytecode agent java-bytecode-asm

我是ASM和字节码操作的新手。我的任务很简单:我告诉我的代理人要访问哪个类和方法,并测量该方法的执行时间。测量是用Guava图书馆的秒表课程完成的。 ASM基本上围绕方法体,方法是在开始时启动秒表并在方法体的末尾停止它并打印出执行时间。这在某些方法上有效,但在大多数方法上都失败了。

这是ASM访问目标方法开头的部分。

package com.agent.agentclasses;


import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.LocalVariablesSorter;
import org.objectweb.asm.Type;

public class ModifierMethodWriter  extends LocalVariablesSorter /*MethodVisitor*/ {
    private int time;
    protected ModifierMethodWriter(int api, int access, String desc, MethodVisitor mv) {
        super(api, access, desc, mv);
    }


    @Override
    public void visitCode() {
        System.out.println("I am @ModifierMethodWriter!");

        /*
        *   Guava - beginning of the method !
        */
        time = newLocal(Type.getObjectType("stopwatch"));


        super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/google/common/base/Stopwatch", "createStarted", "()Lcom/google/common/base/Stopwatch;", false);

        super.visitVarInsn(Opcodes.ASTORE, time);
        super.visitCode();




    }

将代码添加到最后!

package com.agent.agentclasses;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.AdviceAdapter;

public class AddCodeBeforeReturn extends AdviceAdapter { 
    public AddCodeBeforeReturn(int api, MethodVisitor mv,
             int acc, String name, String desc){

        super(api, mv, acc, name, desc);

    }
    @Override
    protected void onMethodExit(int opcode) {
        /*
        *   Guava - Before return !
        */
        super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        super.visitIntInsn(Opcodes.ALOAD, 2);
        super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/google/common/base/Stopwatch", "stop", "()Lcom/google/common/base/Stopwatch;", false);
        super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/google/common/base/Stopwatch", "toString", "()Ljava/lang/String;", false);
        super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    }
}

这是我想测试我的代理的测试类:它是HSQL db类,我想测量selectAll()方法的执行时间。

package com.agent.database;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import com.agent.testclasses.*;
public class Database {

    private Connection connection = null;  
    private ResultSet resultSet = null;  
    private Statement statement = null;
    private String createtablestr = " CREATE TABLE IF NOT EXISTS Students(Id int,Name varchar(255));";
    private static int id = 1;

    public String connect() {
        System.out.println("I am in connect!");
        try{
        Class.forName("org.hsqldb.jdbcDriver");
        connection = DriverManager.getConnection("jdbc:hsqldb:file:C:/hsqldb/studentdb", "sa", "");
        if (connection == null)
        {

            return "Error: Connection failed.";
        }
            statement = connection.createStatement();
            statement.executeUpdate(createtablestr);
        }catch(Throwable ex){
            ex.printStackTrace();
        }

        return "Connection succesful!";
    }

    public String insert(Student student){
        connect();  
        try {

            String insertStr = "INSERT INTO Students VALUES ('"+ id +"','"+ student.getName() + "')";
            statement.executeUpdate(insertStr);
            id++;
        }catch(Throwable ex){ex.printStackTrace(); return "Error: Data was not written!";}

        return "Data written succesfully!";
    }   

    public String selectAll() {


        connect();  
        String outputHTML="";
        try{


            resultSet = statement.executeQuery("SELECT * FROM Students");


            while (resultSet.next()) 
            {  
                outputHTML+= "<tr>" + "<td>" + resultSet.getString("Id") + "</td>" + "<td>" + resultSet.getString("Name") + "</td>"  + "</tr>";
            }



            resultSet.close();
            connection.commit();
            connection.close();


        } 
        catch (Throwable e) 
        {
            e.printStackTrace();
        }

        return outputHTML;
   }
}

最后,jvm thorws的例外:

Exception in thread "main" java.lang.VerifyError: Stack map does not match the one at exception handler 85
    Exception Details:
      Location:
        com/agent/database/Database.connect()Ljava/lang/String; @85: astore_2
      Reason:
        Type top (current frame, locals[1]) is not assignable to 'stopwatch' (stack map, locals[1])
      Current Frame:
        bci: @12
        flags: { }
        locals: { 'com/agent/database/Database', top, 'com/google/common/base/Stopwatch' }
        stack: { 'java/lang/Throwable' }
      Stackmap Frame:
        bci: @85
        flags: { }
        locals: { 'com/agent/database/Database', 'stopwatch' }
        stack: { 'java/lang/Throwable' }
      Bytecode:
        0x0000000: b800 2a4d b200 3212 34b6 003a 123c b800
        0x0000010: 4257 2a12 4412 4612 48b8 004e b500 182a
        0x0000020: b400 18c7 0014 1250 b200 3219 02b6 0053
        0x0000030: b600 56b6 003a b02a 2ab4 0018 b900 5e01
        0x0000040: 00b5 001c 2ab4 001c 2ab4 0020 b900 6402
        0x0000050: 0057 a700 084d 2cb6 0067 1269 b200 3219
        0x0000060: 02b6 0053 b600 56b6 003a b0            
      Exception Handler Table:
        bci [12, 38] => handler: 85
        bci [55, 82] => handler: 85
      Stackmap Table:
        append_frame(@55,Object[#88])
        same_locals_1_stack_item_frame(@85,Object[#44])
        same_frame(@90)

        at com.agent.testers.tester1.main(tester1.java:15)

在我看来,在ASM在秒表的局部变量表中创建新条目后,jvm会尝试使用我尝试定位的方法的局部变量覆盖它(在本例中为selectAll())。因此:

  

类型顶部(当前帧,本地[1])不能分配给'秒表'   (堆栈图,本地[1])

我不确定这是否是实际问题,但我需要解决它!最后,我的代理应该可以使用任何方法插入。所有的帮助将不胜感激!谢谢。

1 个答案:

答案 0 :(得分:1)

您正在制作&#39; StopWatch&#39;在新分配的时间&#39;中的实例局部变量,然后从局部变量插槽2加载其实例,你应该在&super; vis.Code()&#39;之后添加你的代码。

另请参阅ASM FAQ Why do I get the xxx verifier error了解有关如何使用CheckClassAdapter调试字节码转换的更多详细信息。