Note that there are some explanatory texts on larger screens.

plurals
  1. POJava Bytecode instrumentation with ASM: VerifyError on code injection at INVOKESPECIAL instructions
    primarykey
    data
    text
    <p>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!</p> <p>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:</p> <pre><code>/** * 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 &amp;&amp; !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 &gt; 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 &gt;= 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 &lt; 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: * &lt;ul&gt;&lt;li&gt;owner type&lt;/li&gt;&lt;li&gt;arg1 type&lt;/li&gt;&lt;li&gt;arg2 type&lt;/li&gt;&lt;li&gt;...&lt;/li&gt;&lt;li&gt;argN type&lt;/li&gt;&lt;/ul&gt; * * @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; } </code></pre> <p>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.</p> <p>In the following is one of my test classes. This one is pretty simple: it is just starting another thread: </p> <pre><code>/** * 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(); } } </code></pre> <p>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.</p> <pre><code>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) </code></pre> <p>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 ;-)</p> <p>If my understanding is correct, some statement like "new Thread()" turns to be</p> <pre><code>NEW java/lang/Thread DUP INVOKESPECIAL &lt;init&gt; </code></pre> <p>in bytecode. Could it be a problem that the newly created object is still uninitialzed before the constructor is invoked? </p> <p>I do not understand why the code is working but the JVM is complaining during verification. </p> <p>Even looking at the decompiled code after instrumentation does not help me:</p> <pre><code>// 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 &lt;Class ThreadStarter$1&gt;; 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; } } </code></pre> <p>Some additional information: I am developping with IntelliJ IDEA 10.5.4 and using jdk1.6.0_39.</p> <p>Finally, I hope that somebody here can help me to get the necessary insight. Thanks in advance!</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload