Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Injecting a Java method _before_ another method is called

I am using ASM and want to rewrite something like:

someMethod().targetMethod(args...)

to:

someMethod().injectedMethod(arg).targetMethod(args...)

The trouble is that I don't know what the method before is, I only know the target method (so finding someMethod() and injecting after that isn't an option).

I also have many versions of the target method, with different sets of parameters I want this to work with.

Using ASM I can easily find the target method invocation, but unfortunately the operand stack at that point is:

[ argN, ..., arg1, instance, ... ]

And while I can work out how far down the instance will be, there's no bytecode I can inject that will read it. I know you can do it for up to 4 parameters using tricks with dup commands, but I need a general solution.

I could add a bunch of local variables and copy everything off the stack, dup the instance pointed and put everything back on, but that's a runtime inefficiency I really don't want.

What I think would work is if I could track which instruction(s) were responsible for putting the instance pointer on the stack, and then I could inject my method invocation there rather than at the target method invocation. However I have had no luck in finding anything to help me do this.

I know that things like AspectJ allow for this, but have to do this for a lot of classes as they load and AspectJ is just too slow.

Can anyone point me at analysis tools built on top of ASM that might let me do this or can anyone think of a better approach for injecting one method call before another?

like image 876
David Avatar asked Dec 18 '12 13:12

David


2 Answers

If I understand your question properly, I have achieved the same as what you are wanting to do but in a different way.

Using ASM event driven byte code modification, I first of all renamed someMethod( arg, arg, arg ) to copyOf_someMethod( arg, arg, arg ). Then I created a new method called someMethod( arg, arg, arg ) which did some processing and then called copyOf_someMethod( arg, arg, arg ).

I did the method renaming in the visitMethod(..) method of the ClassVisitor I implemented:

MethodVisitor methodVisitor =
    super.visitMethod(
        methodAccess, "copyOf_" + methodName, methodDesc,
            methodSignature, methodExceptions );

return methodVisitor;

In the visitMethod(..) I also stored all the method signature details in class variables ready to be used in the visitEnd() method.

I actually stored the method details in a MethodDetail object and placed it in a queue:

private Queue<MethodDetail> methodDetails = new LinkedList<MethodDetail>();

I created the new implementation of someMethod( arg, arg, arg ) using the visitEnd() method of the ClassVisitor I implemented. I used ASMFier to generate the code to put in the visitEnd() method. The implementation made use of the details I stored previouslyin the visitMethod(..). The new implementation did some processing and then called copyOf_someMethod(). In the visitEnd() method I popped all the MethodDetail of the queue and for each MethodDetail called ASM code that I had previously generated by ASMFier.

Using this design I had created a proxy for a method which did some processing and then called the original method. Note that the original method had been renamed to copyOf_someMethod(..). Note also that I provided a new implementation for the original method which acted as the proxy.

To support multiple arguments I used ASMFier to generate different code for 1 arg, 2 arg, 3 arg, e.t.c. I supported up to 7 arguments and throw a Unsupported Exception if the method being proxied had more than 7 arguments. In the visitEnd(..) method I called different code (that had been generated by ASMFier) depending on how many method arguments the original method had.

I used a javaagent to intercept the class loading and modify the bytes.

As I am new to ASM, maybe I did not understand your question properly - however if your question was about creating a proxy that does some processing and then calls the original method then my solution works. It did not seem to be slow. Class load up time for the class having its methods being proxied was not that much more slower than without byte code modification. The runtime speed of the introduced code was not slow, ASM code generated by ASMFier appears to be very fast.

Cheers

like image 98
John Dickerson Avatar answered Oct 20 '22 01:10

John Dickerson


In general case you can offload values off the stack into temporary local variables. LocalVariableSorter adapter from ASM commons package makes it real easy. But in reality methods with more then 4 arguments is a rare case. Anyhow it is still much simpler and more solid then doing full blown dataflow analysis at runtime.

ASM's org.objectweb.asm.tree.analysis provides facility for data flow analysis, e.g. you can use SourceInterpreter to track what instructions produced values on each variable and stack slots. See ASM User Guide for more details.

like image 41
Eugene Kuleshov Avatar answered Oct 20 '22 00:10

Eugene Kuleshov