Cannot Intercept method calls from agent

229 views
Skip to first unread message

Bronislav Gabrhelik

unread,
Oct 6, 2015, 9:25:07 AM10/6/15
to Byte Buddy
I would like to overload  several methods of 3rd party class laying on bootclasspath from my own agent. Unfortunately it doesn't work for me . I have following code:

public class Main {
    public static void premain(String agentArgument,
                               Instrumentation instrumentation) {

   
        TypePool typePool = TypePool.Default.ofClassPath();

        new AgentBuilder.Default()
            .enableBootstrapInjection(new File("c:\\temp"), instrumentation)
            .rebase(ElementMatchers.is(typePool.describe("com.comany.x.y.Classname").resolve()))
            .transform(new AgentBuilder.Transformer() {
                    @Override
                    public DynamicType.Builder transform(DynamicType.Builder builder,
                                                         TypeDescription typeDescription) {
                        return builder
                         .method(ElementMatchers.named("getPropertyX"))
                            .intercept(MethodDelegation.to(ClassInterceptor.class))
                         .method(ElementMatchers.named("getPropertyY"))
                            .intercept(MethodDelegation.to(ClassInterceptor.class))
                         .method(ElementMatchers.named("getPropertyZ"))
                            .intercept(MethodDelegation.to(ClassInterceptor.class));
                }
            })
            .installOn(instrumentation);
    }
    
    
    public static class ClassInterceptor {
    
      public static String getPropertyX(@Super Classname impl) {
        return impl.getPropertyX() + " getPropertyX";
      }
      public static String getPropertyY(@Super Classname impl) {
        return impl.getPropertyX() + " getPropertyY";
      }
      public static String getPropertyZ(@Super Classname impl) {
        return impl.getPropertyX() + " getPropertyZ";
      }

    }
 }

My manifest file looks like this
Manifest-Version: 1.0
Premain-Class: com.companyb.x.Main
Can-Retransform-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true


Transform is called and there is a generated jar file in the folder "c:\\temp" containing re-transformed code. However I cannot see any reference to ClassInterceptor in the constant pool.
The code look like following

 public java.lang.String getPropertyX();
    Signature: ()Ljava/lang/String;
    flags: ACC_PUBLIC

    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: getfield      #10                 // Field target:Lcom/com/companya/x/y/Classname;
         4: invokevirtual #355                // Method com/companya/x/y/Classname.getPropertyX$accessor$CYHRKHCv:()Ljava/lang/String;
         7: areturn       

I cannot see a definition of the class com/companya/x/y/Classname.getPropertyX$accessor or com/companya/x/y/Classname.getPropertyX$accessor$CYHRKHCv.
neither in this class file nor in separate class file.

Any hint is appreciated.
Thanks,
Bronislav


Rafael Winterhalter

unread,
Oct 6, 2015, 9:48:23 AM10/6/15
to Bronislav Gabrhelik, Byte Buddy
The problem you are having is the fact that installing your agent already loads the class that you are trying to redefine with your agent. Your interceptor defines ClassInterceptor in its signature. This triggers class loading.

Try:

  public static class ClassInterceptor {
    
      public static String intercept(@SuperCall Callable<String> zuper, @Origin String method)
          throws Exception {
        return zuper.call() + " " + method;
      }
    }

Also, a more efficient implemementation for your type matcher:

ElementMatchers.is(named("com.comany.x.y.Classname"))

Hope that helps. Get in touch if you need further advice.
Cheers, Rafael


--
You received this message because you are subscribed to the Google Groups "Byte Buddy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to byte-buddy+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Bronislav Gabrhelik

unread,
Oct 6, 2015, 3:27:46 PM10/6/15
to Byte Buddy, bgabr...@ahojky.com
Thanks Rafael! 

I give it a try, however I realized that during simplification of code for purpose of this forum, I garbled my original code. There is an ascendant abstract class of Classname in the ClassInterceptor so it shoudln't be a problem. I will try to use a callable so I am sure the class is not loaded.

I appreciate your help!
Thanks,
Bronek

Bronislav Gabrhelik

unread,
Oct 7, 2015, 9:49:35 AM10/7/15
to Byte Buddy, bgabr...@ahojky.com
Hi Rafael,

I just want let you know that it works for me after some effort, however I came across some problems, for which I used workarounds. Note that I am using JDK/JRE 1.7.x.

The root problem was missing byte-buddy jar in boot classpath.

Problem no 1: Expecting a stackmap frame at branch target 125

As a workaround I removed -Xverify:remote and added -noverify JVM argument. Is there a better solution?

java.lang.VerifyError: Expecting a stackmap frame at branch target 125
Exception Details:
  Location:
    com/sun/javaws/JnlpxArgs.verify()V @6: ifeq
  Reason:
    Expected stackmap frame at this location.
  Bytecode:
    0000000: b202 3bb8 0259 9900 7712 2bb2 023b b802
    0000010: 5bbb 018b 59b7 029f 123f b602 a5b8 026d
    0000020: b602 a4b6 02a0 b202 3bb8 025b bb01 8b59
    0000030: b702 9f12 46b6 02a5 b802 69b6 02a1 b602
    0000040: a0b2 023b b802 5bbb 018b 59b7 029f 1243
    0000050: b602 a5b8 026b b602 a3b6 02a0 b202 3bb8
    0000060: 025b bb01 8b59 b702 9f12 3db6 02a5 b802
    0000070: 6eb6 02a5 b602 a0b2 023b b802 5bb1     


Problem no 2: Could not initialize class com.sun.deploy.net.protocol.https.Handler$Initializer

java.lang.NoClassDefFoundError: Could not initialize class com.sun.deploy.net.protocol.https.Handler$Initializer

It looks like it is a generated class which has no related class file. I am not sure. As this is not class of my interest I solved it by a wrapper filter implementing ClassFileTransformer based on string comparison of classname. My filter delegates calls to AgentBuilder.Default.ExecutingTransformer implementation only for specific class according to the name match. It prevents type description resolving for in-memory classes like reflection classes etc...

Problem no 3: performance problem of  AgentBuilder.Default.ExecutingTransformer

Well your advice to use named("com.sun.javaws.JnlpxArgs") instead of typePool.describe("com.sun.javaws.JnlpxArgs").resolve() didn't work for me. Internally there was a comparison of  NamedElement against TypePool.LazyTypeDescription which have never matched.
I identified as a source of performance problem the code in AgentBuilder.Default.ExecutingTransformer.transform(), which is trying to resolve the type description. 
It invokes ASM class reader and writer. I could see some visitor pattern stuff on the stack when interrupted the long lasting execution.

So I came up with the same workaround as for the problem no 3. - with a wrapper implementing ClassFileTransformer and delegating call  to the instance returned by the makeraw()  method only in case of match.

I would be glad if I can get rid of these workarounds, so any pointer or hint is welcome. 

Thanks,
Bronislav

Rafael Winterhalter

unread,
Oct 8, 2015, 7:27:45 AM10/8/15
to Bronislav Gabrhelik, Byte Buddy
Once you redefine internal classes of the JVM, unexpected things can unfortunately happen.

Byte Buddy does not change the byte code level of instrumented classes, therefore, I do not really understand why you would get a verifier error then. Also, it is not Byte Buddy that needs to be present on the bootstrap class path, it is the interceptor.

You can add the interceptor to the bootstrap class path using the instrumentation API.

I do neither understand why the name-based matching does not work. The equality is determined based on the name. It should not make a difference.

As for the performance implications. It is true that Byte Buddy looks up a description representation for any class in the moment. I will try to add a facade that shields the resolution until anything but the name is queried. This will simplify the matching for matchers that do not require any information but the name internally.

--
You received this message because you are subscribed to the Google Groups "Byte Buddy" group.

Rafael Winterhalter

unread,
Oct 8, 2015, 8:16:42 AM10/8/15
to Byte Buddy, bgabr...@ahojky.com
If you build Byte Buddy from master, I just pushed an improvement that resolves type descriptions from an agent builder lazily. Hope this fixes your performance issues.
To unsubscribe from this group and stop receiving emails from it, send an email to byte-buddy+unsubscribe@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages