Interception de la méthode native java.lang.Object wait (long) avec Byte-Buddy
J'essaye d'enregistrer le nombre d'appels de méthode "Object.wait". L'interception «attente publique finale native vide (long timeout)» n'est pas valide avec ByteBuddy. J'ai fait quelques tests:
test1 、 erreur d'impression test2: java.lang.IllegalStateException: Impossible d'appeler la méthode super (ou par défaut) pour le public final void java.lang.Object.wait (long, int) lance java.lang.InterruptedException; java.lang.IllegalStateException: Impossible d'appeler la méthode super (ou par défaut) pour le void natif final public java.lang.Object.wait (long) lance java.lang.InterruptedException
test3 n'est pas valide, n'imprime rien.
test4 est le succès.
Voici mon code de test:
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);
}
}
Tester:
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();
}
:( Le code Test4 est pour la méthode "public final void wait ()", il fonctionne, mais il ne fonctionne pas pour la méthode "public final native void wait ()". Je veux savoir comment intercepter "public final native void wait ()" méthode.
Réponses
En regardant cela, il y a en fait un bogue dans la version 1.10.14 de Byte Buddy. La méthode rebasée doit avoir les modificateurs private finalet pas seulement private, sinon la JVM n'acceptera pas la méthode rebasée.
Le correctif est déjà sur master, si vous créez vous-même Byte Buddy, vous devriez pouvoir exécuter votre agent maintenant. La prochaine version 1.10.15 contiendra ce correctif.
Notez cependant que la JVM ne prend plus en charge l'ajout de méthodes privées (finales) à partir de la version 13. L'ancien comportement peut être réinitialisé à l'aide de -XX:+AllowRedefinitionToAddDeleteMethods. Cette option est cependant déconseillée et n'existera plus dans une future version de la JVM. Sans une telle possibilité de renommer la méthode native, ce comportement n'est plus possible sur la JVM, avec ou sans Byte Buddy.
(Pour la divulgation, je suis le mainteneur de Byte Buddy.)
Avertissement: Ce n'est pas une réponse finale, juste une version préliminaire nécessaire pour la discussion.
En fait, on parle d'une partie de BB que je ne connais pas car je ne suis pas non plus un expert. Mais j'étais curieux et j'ai essayé quelque chose comme
.transform((builder, typeDescription, classLoader, module) ->
builder
.method(named("wait").and(takesArguments(long.class)))
.intercept(MethodDelegation.to(WaitAdvice.class))
)
en combinaison avec
public class WaitAdvice {
public static long COUNT = 0;
public static void waitX(long millis) throws InterruptedException {
System.out.println("Before wait -> " + millis);
COUNT++;
}
}
Cela fonctionne si je mets WaitAdvicedans un JAR séparé et le mets sur le chemin de classe d'amorçage de la JVM. L'inconvénient ici est que je n'ai aucune idée de comment appeler la méthode cible réelle à partir de la méthode de wait(long)substitution, c'est-à-dire qu'elle ne sera pas réellement exécutée, seulement remplacée, ce qui n'est probablement pas ce que vous voulez. Je devrais fouiller dans tout un tas de tests de code source BB, d'unités et d'intégration (car le tutoriel n'est pas très utile et également obsolète), les problèmes GitHub et les questions StackOverflow afin de peut-être trouver une solution à cela, mais jusqu'à présent, je n'a rien trouvé d'utile qui n'ait pas immédiatement causé de problèmes de suivi. Je suis encore trop BB noob, même si je pourrais répondre à votre question précédente (plus simple).
Peut-être que Rafael Winterhalter (le mainteneur de BB) a une idée pour vous.