Bytecode instrumentation using ASM 5.0 . inject a tracer to trace local variables

605 views Asked by At

I am doing Java bytecode analyse. I want to keep tracking each changing state of local variables. The idea is quite like a debugger. For example, I have a Java source code like

public class Foo {

public void main() {
    printOne();
}

public void printOne() {    
     int i=11110;
     String hello="hello";
}

By using ASM 5.0 I could visit line number ,local variable name ,mehtod name ,var instruction and so on. The visitor look like below

@Override
    public void visitLineNumber(int line, Label start) {
        System.out.println("line: "+line+" ");
        mv.visitLineNumber(line, start);

    }

    @Override
    public void visitLocalVariable(String name, String desc,String signature, Label start, Label end, int index) {
        System.out.println("local variable:"+name);
        mv.visitLocalVariable(name, desc, signature, start, end, index);

    }

    @Override
    public void visitVarInsn(int opcode, int var) {
        System.out.println("var instruction"+Integer.toHexString(opcode));
        mv.visitVarInsn(opcode, var);
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
        System.out.println("method");
        mv.visitMethodInsn(opcode, owner, name, desc, itf);
    }

The question is where do I inject a tracer(just a static method call), and look for variable value on stack and pass to the tracer as parameter?

mv.visitMethodInsn(Opcodes.INVOKESTATIC, "path/to/Tracer", "trace", "(Ljava/lang/String;)V",false);
1

There are 1 answers

2
Peter Swords On

You can't do what you want with a visitor. The visitor is only visiting the declarations of the local variables, stored in the optional LocalVariableTable attribute of a class file Code attribute (see the specification here). This gives you the signatures of the local variables but tells you nothing about what happens to their values at runtime. For that you would need to analyse the load and store instructions (among others) in the bytecode, which -- as you say -- would be effectively writing a debugger.

In principle you could visit the code, look for all the opcodes that store into the local variable index you are interested in, and insert instrumentation code to pass the new value to some reporting method of your own, but this would be a lot of work to reimplement what debuggers already do.

To reiterate: The LocalVariableTable doesn't contain values. It is the metadata describing the local variables -- their names, types etc. -- stored in the class file for the benefit of debuggers. It is an optional attribute, created only if you include debug information at compile time. At runtime the executing code does not use the LocalVariableTable. The local variable value itself lives on the stack, allocated at runtime. I think you need to step back and think about what you are trying to achieve. You want to know what values a local variable adopts at runtime. That cannot be known until runtime.

You can't point a debugger at a class file and ask "what's the value of local variable x?". That information does not exist in the class file. It's not even a meaningful question. You have to run the program to use a debugger on it. The debugger suspends execution and looks at the values on the execution stack at a particular point in time. Your asm visitor classes are looking at the class file, not the running code or the execution stack.

What you can know, is the index to the position of the local variable's value on the stack at runtime. That is the index parameter passed to your local variable visitor. You would need to take that index parameter and analyse the bytecode of the method to find all the opcodes that store values into the variable at that index. These represent the points in the method where the value of the local variable may change. You could then insert additional bytecodes to invoke your own reporting method, passing the value of the local variable at the specified index. Then you have to run the code which you have instrumented. Needless to say, none of this is for the fainthearted!