1
votes

I am trying to record the count of "Object.wait" method calls. Intercept "public final native void wait(long timeout)" is invalid with ByteBuddy. I did some tests:

test1、test2 print error: java.lang.IllegalStateException: Cannot call super (or default) method for public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException; java.lang.IllegalStateException: Cannot call super (or default) method for public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException

test3 is invalid, print nothing.

test4 is success.

Here is my test code:

Javaagent:


final public class AgentBootstrap {

     public static class TestAdvice {
        @Advice.OnMethodEnter
        public static void before(@Advice.Origin String methodIns) {
            System.out.println("Byte-Buddy enter:" + methodIns);
        }

        @Advice.OnMethodExit
        public static void after(@Advice.Origin String methodIns) {
            System.out.println("Byte-Buddy after:" + methodIns);
        }
      }
    
     public static void premain(String agentArgs, Instrumentation inst) throws Exception{
         AgentBuilder agentBuilder = new AgentBuilder.Default()
                .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
                .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
                .with(AgentBuilder.TypeStrategy.Default.REBASE)
                .enableNativeMethodPrefix("$$mynative_")
                .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly())
                .with(AgentBuilder.InstallationListener.StreamWriting.toSystemError())
                .ignore(nameStartsWith("net.bytebuddy."));
        // test1(agentBuilder, inst);
        // test2(agentBuilder, inst);
        // test3(agentBuilder, inst);
        // test4(agentBuilder, inst);
        
    }
     
    /**
     * intercept method: public final native void wait(long timeout)
     * print error: java.lang.IllegalStateException: Cannot call super (or default) method for public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
     */
    private static void test1(AgentBuilder agentBuilder, Instrumentation inst) throws Exception {
        agentBuilder.disableClassFormatChanges()
        .type(is(Object.class))
        .transform(new Transformer() {
            @Override
            public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader,JavaModule module) {
              return builder.method(named("wait").and(takesArguments(1))).intercept(Advice.to(TestAdvice.class));
            }
        })
        .installOn(inst);
        inst.retransformClasses(Object.class);
    }
    /**
     * intercept method: public final void wait(long timeout, int nanos)
     * print error: java.lang.IllegalStateException: Cannot call super (or default) method for public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
     */
    private static void test2(AgentBuilder agentBuilder, Instrumentation inst) throws Exception {
        agentBuilder.disableClassFormatChanges()
        .type(is(Object.class))
        .transform(new Transformer() {
            @Override
            public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader,JavaModule module) {
              return builder.method(named("wait").and(takesArguments(2))).intercept(Advice.to(TestAdvice.class));
            }
        })
        .installOn(inst);
        inst.retransformClasses(Object.class);
    }
    
    /**
     * intercept method: public final native void wait(long timeout)
     * invalid, print nothing
     */
    private static void test3(AgentBuilder agentBuilder, Instrumentation inst) throws Exception {
        agentBuilder.disableClassFormatChanges()
        .type(is(Object.class))
        .transform(new Transformer() {
            @Override
            public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader,JavaModule module) {
                return builder.visit(Advice.to(TestAdvice.class).on(named("wait").and(takesArguments(1))));
            }
        })
        .installOn(inst);
        inst.retransformClasses(Object.class);
    }
    /**
     * intercept method: public final void wait(long timeout, int nanos)
     * success
     */
    private static void test4(AgentBuilder agentBuilder, Instrumentation inst) throws Exception {
        agentBuilder.type(is(Object.class))
        .transform(new Transformer() {
            @Override
            public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader,JavaModule module) {
                return builder.visit(Advice.to(TestAdvice.class).on(named("wait").and(takesArguments(2))));
            }
        })
        .installOn(inst);
    }
     
}

Test:

public static void main(String[] args) throws Exception {
    new Thread(() -> {
        Object obj = new Object();
        while (true){
            try {
                synchronized (obj) {
                    obj.wait(1000);
                    obj.wait(1000, 1);
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }).start();
}

:( Test4 code is for "public final void wait()" method, it works, but it doesn't work for "public final native void wait()" method. I want to know how to intercept "public final native void wait()" method.

2
If "test4 is success" then what is your actual question? - luk2302
Sorry I didn't describe it clearly. Test4 code is for "public final void wait()" method, it works, but it doesn't work for "public final native void wait()" method. I want to know how to intercept "public final native void wait()" method. - yushanmufeng
The same code works for other JDK core class native method, like "Thread.sleep" . Is "Object.class native method" special? - yushanmufeng

2 Answers

1
votes

Looking into this, there is actually a bug in Byte Buddy version 1.10.14. The rebased method must have the modifiers private final and not just private, otherwise, the JVM will not accept the rebased method.

The fix is already on master, if you build Byte Buddy yourself, you should be able to run your agent now. The next version 1.10.15 will contain this fix.

Do however note that the JVM does no longer support adding private (final) methods from version 13 on upwards. The old behavior can be reset using -XX:+AllowRedefinitionToAddDeleteMethods. This option is however deprecated and will no longer exist in a future version of the JVM. Without such a possibility to rename the native method, this behavior is no longer possible on the JVM, with or without Byte Buddy.

(For disclosure, I am the maintainer of Byte Buddy.)

0
votes

Disclaimer: This is not a final answer, just a preliminary version needed for discussion.

Actually we are talking about a part of BB which I do not know because I am not an expert either. But I was curious and tried something like

.transform((builder, typeDescription, classLoader, module) ->
  builder
    .method(named("wait").and(takesArguments(long.class)))
    .intercept(MethodDelegation.to(WaitAdvice.class))
)

in combination with

public class WaitAdvice {
  public static long COUNT = 0;

  public static void waitX(long millis) throws InterruptedException {
    System.out.println("Before wait -> " + millis);
    COUNT++;
  }
}

This works if I put WaitAdvice into a separate JAR and put it on the JVM's bootstrap classpath. The downside here is that I have no idea how to call the real target method from the overriding one, i.e. wait(long) will not actually be executed, only replaced, which is probably not what you want. I would have to dig through a whole lot of BB source code, unit and integration tests (because the tutorial is not very helpful and also outdated), GitHub issues and StackOverflow questions in order to maybe find a solution for this, but so far I did not find anything helpful which did not immediately cause follow-up problems. I am still too much of a BB noob, even though I could answer your previous (simpler) question.

Maybe Rafael Winterhalter (the BB maintainer) has an idea for you.