Interceptación del método nativo java.lang.Object wait (long) con Byte-Buddy
Estoy intentando registrar el recuento de llamadas al método "Object.wait". La intercepción de "espera nula nativa final pública (tiempo de espera prolongado)" no es válida con ByteBuddy. Hice algunas pruebas:
test1 、 test2 error de impresión: java.lang.IllegalStateException: No se puede llamar al método super (o predeterminado) para el vacío final público java.lang.Object.wait (long, int) lanza java.lang.InterruptedException; java.lang.IllegalStateException: no se puede llamar al método super (o predeterminado) para el vacío nativo final público java.lang.Object.wait (long) lanza java.lang.InterruptedException
test3 no es válido, no imprima nada.
test4 es el éxito.
Aquí está mi código de prueba:
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);
}
}
Prueba:
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();
}
:( El código Test4 es para el método "public final void wait ()", funciona, pero no funciona para el método "public final native void wait ()". Quiero saber cómo interceptar "public final native void wait () "método.
Respuestas
Al analizar esto, en realidad hay un error en la versión 1.10.14 de Byte Buddy. El método rebasado debe tener los modificadores private finaly no solo private, de lo contrario, la JVM no aceptará el método rebasado.
La solución ya está en el maestro, si crea Byte Buddy usted mismo, debería poder ejecutar su agente ahora. La próxima versión 1.10.15 incluirá esta corrección.
Sin embargo, tenga en cuenta que la JVM ya no admite la adición de métodos privados (finales) desde la versión 13 en adelante. El antiguo comportamiento se puede restablecer usando -XX:+AllowRedefinitionToAddDeleteMethods. Sin embargo, esta opción está en desuso y ya no existirá en una versión futura de la JVM. Sin esa posibilidad de cambiar el nombre del método nativo, este comportamiento ya no es posible en la JVM, con o sin Byte Buddy.
(Para divulgación, soy el mantenedor de Byte Buddy).
Descargo de responsabilidad: esta no es una respuesta final, solo una versión preliminar necesaria para la discusión.
En realidad estamos hablando de una parte de BB que no conozco porque tampoco soy un experto. Pero tenía curiosidad e intenté algo como
.transform((builder, typeDescription, classLoader, module) ->
builder
.method(named("wait").and(takesArguments(long.class)))
.intercept(MethodDelegation.to(WaitAdvice.class))
)
en combinación con
public class WaitAdvice {
public static long COUNT = 0;
public static void waitX(long millis) throws InterruptedException {
System.out.println("Before wait -> " + millis);
COUNT++;
}
}
Esto funciona si lo coloco WaitAdviceen un JAR separado y lo coloco en la ruta de clase de arranque de la JVM. La desventaja aquí es que no tengo idea de cómo llamar al método de destino real desde el principal, es decir wait(long), no se ejecutará realmente, solo se reemplazará, lo que probablemente no sea lo que desea. Tendría que examinar una gran cantidad de pruebas de integración, unidades y código fuente de BB (porque el tutorial no es muy útil y tampoco está actualizado), problemas de GitHub y preguntas de StackOverflow para quizás encontrar una solución para esto, pero hasta ahora he no encontró nada útil que no causara problemas de seguimiento inmediatamente. Todavía soy demasiado novato, a pesar de que podría responder a tu pregunta anterior (más simple).
Quizás Rafael Winterhalter (el mantenedor de BB) tenga una idea para ti.