Question

I'm quite new in bytecode injection. Until now, I was able to get everything what I wanted by exhaustive research and painful trial and error :-) But I seem to have reached my limits with the currently pursued objective. So, here it is: my very first stackoverflow question!

My aim is to trace the object references of method invocations via a java agent. I am using the ASM 4.0 library and have implemented an AdviceAdapter. My overriden visitMethodInsn()-method looks like this:

/**
 * Visits a method instruction. A method instruction is an instruction that invokes a method.
 * The stack before INVOKEINTERFACE, INVOKESPECIAL and INVOKEVIRTUAL instructions is:
 * "objectref, [arg1, arg2, ...]"
 *
 * @param opcode the opcode of the type instruction to be visited. This opcode is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE.
 * @param owner  the internal name of the method's owner class.
 * @param name   the method's name.
 * @param desc   the method's descriptor.
 */
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
    if (isExcluded()) {
        super.visitMethodInsn(opcode, owner, name, desc);
        return;
    }

    int arraySlot = -1;
    boolean isStatic = false;
    if (opcode == INVOKEVIRTUAL || opcode == INVOKEINTERFACE) {
        arraySlot = saveMethodParameters(owner, desc);
        super.visitMethodInsn(opcode, owner, name, desc);
    } else if (opcode == INVOKESTATIC) {
        isStatic = true;
        super.visitMethodInsn(opcode, owner, name, desc);
    } else if (opcode == INVOKESPECIAL && !owner.equals("java/lang/Object")) {
        //TODO: Causes VerifyError
        arraySlot = saveMethodParameters(owner, desc);
        super.visitMethodInsn(opcode, owner, name, desc);
    } else {
        super.visitMethodInsn(opcode, owner, name, desc);
    }

    if (arraySlot > 0) {
        loadLocal(arraySlot);
        push(0);
        arrayLoad(Type.getType(Object.class));
    } else {
        super.visitInsn(ACONST_NULL);
    }
    super.visitMethodInsn(INVOKESTATIC, "net/myjavaagent/MethodLogger",
            "writeToLoggerTest", "(Ljava/lang/Object;)V");
}

 /**
 * Pops the method invocation' arguments and objectref off the stack, saves them into a local array variable and
 * then puts them back on the stack again.
 *
 * @param owner owner class of the method
 * @param desc  method descriptor
 * @return the identifier of the local variable containing the parameters.
 */
private int saveMethodParameters(String owner, String desc) {
    JavaTracerAgent.agentErrorLogger.info("Save method parameters: " + owner + " " + desc);
    // Preparing the array construction
    Type objectType = Type.getType(Object.class);
    Type objectArrayType = Type.getType("[Ljava/lang/Object;");
    Type[] invokeParamTypes = getMethodParamTypes(owner, desc);
    int invokeParamCount = invokeParamTypes.length;

    // allocate a slot for the method parameters array
    int arrayLocal = newLocal(objectArrayType);
    // construct the object array
    push(invokeParamCount);
    newArray(objectType);
    // store array in the local variable
    storeLocal(arrayLocal);

    // pop the arguments off the stack into the array
    // note: the top one is the last parameter !
    for (int i = invokeParamCount - 1; i >= 0; i--) {
        Type type = invokeParamTypes[i];
        JavaTracerAgent.agentErrorLogger.info("Get from stack [" + i + "]:" + type.toString());

        if (type != null) {
            // convert value to object if needed
            box(type);
            // load array and  swap under value
            loadLocal(arrayLocal);
            swap(objectArrayType, objectType);
            // load index and swap under value
            push(i);
            swap(Type.INT_TYPE, objectType);
        } else {
            // this is a static method and index is 0 so we put null into the array
            // load array index and then null
            loadLocal(arrayLocal);
            push(i);
            push((Type) null);
        }
        // store the value in the array as an object
        arrayStore(objectType);
    }

    // now restore the stack and put back the arguments from the array in increasing order
    for (int i = 0; i < invokeParamCount; i++) {
        Type type = invokeParamTypes[i];
        JavaTracerAgent.agentErrorLogger.info("Put to stack [" + i + "]:" + type.toString());

        if (type != null) {
            // load the array
            loadLocal(arrayLocal);
            //retrieve the object at index i
            push(i);
            arrayLoad(objectType);
            //unbox if needed
            unbox(type);
        } else {
            // this is a static method so no target instance has to be put on stack
        }
    }

    return arrayLocal;
}

/**
 * Returns a type array containing the parameters of a method invocation:
 * <ul><li>owner type</li><li>arg1 type</li><li>arg2 type</li><li>...</li><li>argN type</li></ul>
 *
 * @param owner owner class
 * @param desc  method descriptor
 * @return method parameter types
 */
public Type[] getMethodParamTypes(String owner, String desc) {
    Type ownerType = Type.getObjectType(owner);
    Type[] argTypes = Type.getArgumentTypes(desc);
    int numArgs = argTypes.length;
    Type[] result = new Type[numArgs + 1];
    result[0] = ownerType;
    System.arraycopy(argTypes, 0, result, 1, numArgs);

    return result;
}

In short, I am trying to save everything which is on the stack before the INVOKESOMETHING operation is executed into a local variable. In order to enable the execution of the method operation I have to put the whole stuff back to the stack. Afterwards I assume that the reference of the called object is the first entry in my local array.

In the following is one of my test classes. This one is pretty simple: it is just starting another thread:

/**
* My test class.
*/
public class ThreadStarter {

    public static void main(String args[]) {

        Thread thread = new Thread("Hugo") {

            @Override
            public void run() {
                System.out.println("Hello World");
            }
        };

        thread.start();
    }
}

Concerning INVOKEVIRTUAL, INVOKEINTERFACE and INVOKESTATIC I did not face any issues. Everything seems fine and the logging output is exactly what I expect. However, there seems to be a problem with the INVOKESPECIAL instruction. I'm facing an ugly VerifyError here so I guess there must be something wrong in the way that I treat the stack.

Exception in thread "main" java.lang.VerifyError: (class: net/petafuel/qualicore/examples/ThreadStarter, method: main signature: ([Ljava/lang/String;)V) Expecting to find object/array on stack
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:171)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:113)

Starting the test class with the "-noverify" makes the VerifyError disappear. Everything seems to work just perfectly and I get the desired output. I could just leave it like that but actually the whole issue is causing me pain and lets me sleep very bad ;-)

If my understanding is correct, some statement like "new Thread()" turns to be

NEW java/lang/Thread
DUP
INVOKESPECIAL <init> 

in bytecode. Could it be a problem that the newly created object is still uninitialzed before the constructor is invoked?

I do not understand why the code is working but the JVM is complaining during verification.

Even looking at the decompiled code after instrumentation does not help me:

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   ThreadStarter.java
public class ThreadStarter
{

    public ThreadStarter()
    {
        MethodLogger.writeToLoggerTest(null);
    }

    public static void main(String args[])
    {
        JVM INSTR new #2   <Class ThreadStarter$1>;
        JVM INSTR dup ;
        "Hugo";
        Object aobj[] = new Object[2];
        aobj;
        JVM INSTR swap ;
        1;
        JVM INSTR swap ;
        JVM INSTR aastore ;
        aobj;
        JVM INSTR swap ;
        0;
        JVM INSTR swap ;
        JVM INSTR aastore ;
        ((_cls1)aobj[0])._cls1((String)aobj[1]);
        MethodLogger.writeToLoggerTest(aobj[0]);
        Thread thread;
        thread;
        thread;
        Object aobj1[] = new Object[1];
        aobj1;
        JVM INSTR swap ;
        0;
        JVM INSTR swap ;
        JVM INSTR aastore ;
        ((Thread)aobj1[0]).start();
        MethodLogger.writeToLoggerTest(aobj1[0]);
        return;
    }
}

Some additional information: I am developping with IntelliJ IDEA 10.5.4 and using jdk1.6.0_39.

Finally, I hope that somebody here can help me to get the necessary insight. Thanks in advance!

Was it helpful?

Solution 2

Thanks again to Mike and ruediste for their comments.

Mike was right: My problem was that I tried passing a reference as a method call argument right after it was created by NEW but before its constructor has been called. The JVM specification clearly states that such a behavior is forbidden: "The verifier rejects code that uses the new object before it has been initialized [...]" (see http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10.2.4)

However, the instruction sequence of creating and initializing a new object leaves my desired object on top of the operand stack where it can easily be obtained :)

In the end, I blew up my code bit for the INVOKESPECIAL handling:

if (opcode == INVOKESPECIAL) {
    // Invoke constructors and private methods

    // Ignore initialization of java/lang/Object
    if (name.equals("<init>") && owner.equals("java/lang/Object")) {
        super.visitMethodInsn(opcode, owner, name, desc);
        return;
    }

    if (methodName.equals("<clinit>")) {

        if (name.equals("<clinit>")) {
            // call to a static initializer from within a static initializer
            // there is no object reference!
            super.visitMethodInsn(opcode, owner, name, desc);
        } else if (name.equals("<init>")) {
            // call to a constructor from within a static initializer
            super.visitMethodInsn(opcode, owner, name, desc);
            // object reference is initialized and on stack now -> obtain it via DUP
        } else {
            // call to a private method from within a static initializer
            // no this-reference in static initializer!
            super.visitMethodInsn(opcode, owner, name, desc);
        }

    } else if (methodName.equals("<init>")) {

        if (name.equals("<clinit>")) {
            // call to a static initializer from within a constructor
            // there is no object reference!
            super.visitMethodInsn(opcode, owner, name, desc);
        } else if (name.equals("<init>")) {
            // call to a constructor from within a constructor
            super.visitMethodInsn(opcode, owner, name, desc);
            // if this constructor call is not an invocation of the super constructor: obtain object reference via DUP
        } else {
            // call to a private method from within a constructor
            // object reference is the this-reference (at local variable 0)
            super.visitMethodInsn(opcode, owner, name, desc);
        }

    } else {

        if (name.equals("<clinit>")) {
            // call to a static initializer from within some method
            // there is no object reference!
            super.visitMethodInsn(opcode, owner, name, desc);
        } else if (name.equals("<init>")) {
            // call to a constructor from within some method
            super.visitMethodInsn(opcode, owner, name, desc);
            // obtain object reference via DUP
        } else {
            // call to a private method from within some method
            // if the private method is called within some NON-STATIC method: object reference is the this-reference (at local variable 0)
            // if the private method is called within some NON-STATIC method: there is no object reference!
            super.visitMethodInsn(opcode, owner, name, desc);
        }
    }
}

Possibly this helps someone who's trying to do similar stuff :)

OTHER TIPS

There are two causes I am aware of that might produce this error when INVOKESPECIAL is concerend:

  1. You are attempting invoke a constructor on a reference that cannot be verified as uninitialized (the first argument to INVOKESPECIAL must be an uninitialized reference).

  2. You are attempting to pass an uninitialized reference somewhere where an initialized reference is expected (as an argument to a method call, an AASTORE operation, etc.).

A cursory glance of your code suggests you may be storing an uninitialized reference in your method parameters array.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top