Abfangen der nativen Methode java.lang.Object wait (long) mit Byte-Buddy

Sep 01 2020

Ich versuche, die Anzahl der Methodenaufrufe "Object.wait" aufzuzeichnen. Intercept "public final native void wait (lange Zeitüberschreitung)" ist bei ByteBuddy ungültig. Ich habe einige Tests gemacht:

test1 、 test2 Druckfehler: java.lang.IllegalStateException: Super (oder Standard) -Methode für public final void kann nicht aufgerufen werden. java.lang.Object.wait (long, int) löst java.lang.InterruptedException aus; java.lang.IllegalStateException: Super (oder Standard) -Methode für public final native void kann nicht aufgerufen werden. java.lang.Object.wait (long) löst java.lang.InterruptedException aus

test3 ist ungültig, nichts drucken.

Test4 ist Erfolg.

Hier ist mein Testcode:

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);
    }
     
}

Prüfung:

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 ist für die Methode "public final void wait ()", funktioniert aber nicht für die Methode "public final native void wait ()". Ich möchte wissen, wie man "public final native void wait" abfängt ()" Methode.

Antworten

1 RafaelWinterhalter Sep 06 2020 at 20:53

In diesem Zusammenhang gibt es tatsächlich einen Fehler in Byte Buddy Version 1.10.14. Die neu basierte Methode muss die Modifikatoren enthalten private finalund nicht nur private, sonst akzeptiert die JVM die neu basierte Methode nicht.

Das Update ist bereits auf dem Master. Wenn Sie Byte Buddy selbst erstellen, sollten Sie Ihren Agenten jetzt ausführen können. Die nächste Version 1.10.15 enthält dieses Update.

Beachten Sie jedoch, dass die JVM das Hinzufügen privater (endgültiger) Methoden ab Version 13 nicht mehr unterstützt. Das alte Verhalten kann mit zurückgesetzt werden -XX:+AllowRedefinitionToAddDeleteMethods. Diese Option ist jedoch veraltet und wird in einer zukünftigen Version der JVM nicht mehr vorhanden sein. Ohne eine solche Möglichkeit, die native Methode umzubenennen, ist dieses Verhalten in der JVM mit oder ohne Byte Buddy nicht mehr möglich.

(Zur Offenlegung bin ich der Betreuer von Byte Buddy.)

kriegaex Sep 02 2020 at 08:32

Haftungsausschluss: Dies ist keine endgültige Antwort, sondern nur eine vorläufige Version, die zur Diskussion benötigt wird.

Eigentlich sprechen wir über einen Teil von BB, den ich nicht kenne, weil ich auch kein Experte bin. Aber ich war neugierig und habe so etwas versucht

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

in Kombination mit

public class WaitAdvice {
  public static long COUNT = 0;

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

Dies funktioniert, wenn ich WaitAdviceeine separate JAR in den Bootstrap-Klassenpfad der JVM lege. Der Nachteil hier ist, dass ich keine Ahnung habe, wie man die reale Zielmethode von der überschreibenden aufruft, dh wait(long)nicht tatsächlich ausgeführt, sondern nur ersetzt wird, was wahrscheinlich nicht das ist, was Sie wollen. Ich müsste eine ganze Menge BB-Quellcode-, Unit- und Integrationstests (da das Tutorial nicht sehr hilfreich und auch veraltet ist), GitHub-Probleme und StackOverflow-Fragen durchforsten, um vielleicht eine Lösung dafür zu finden, aber bisher habe ich fand nichts hilfreiches, was nicht sofort zu Folgeproblemen führte. Ich bin immer noch ein zu großer BB-Neuling, obwohl ich Ihre vorherige (einfachere) Frage beantworten konnte.

Vielleicht hat Rafael Winterhalter (der BB-Betreuer) eine Idee für Sie.