Interceptando java.lang.Object wait (long) método nativo com Byte-Buddy
Estou tentando registrar a contagem de chamadas do método "Object.wait". A interceptação "public final nativa nativa espera (longo tempo limite)" é inválida com ByteBuddy. Eu fiz alguns testes:
test1 、 test2 print error: java.lang.IllegalStateException: Não é possível chamar o método super (ou padrão) para public final void java.lang.Object.wait (long, int) throws java.lang.InterruptedException; java.lang.IllegalStateException: não é possível chamar o método super (ou padrão) para público final nativo void java.lang.Object.wait (long) throws java.lang.InterruptedException
test3 é inválido, não imprime nada.
test4 é sucesso.
Aqui está meu código de teste:
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);
}
}
Teste:
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();
}
:( O código Test4 é para o método "public final void wait ()", ele funciona, mas não funciona para o método "public final nativo void wait ()". Eu quero saber como interceptar "public final nativo void wait () "método.
Respostas
Olhando para isso, há realmente um bug no Byte Buddy versão 1.10.14. O método com base no rebase deve ter os modificadores private finale não apenas private, caso contrário, a JVM não aceitará o método com base no rebase.
A correção já está no master, se você mesmo construir o Byte Buddy, deverá conseguir executar o seu agente agora. A próxima versão 1.10.15 conterá essa correção.
No entanto, observe que a JVM não suporta mais a adição de métodos privados (finais) da versão 13 em diante. O comportamento antigo pode ser redefinido usando -XX:+AllowRedefinitionToAddDeleteMethods. Esta opção, entretanto, está obsoleta e não existirá mais em uma versão futura da JVM. Sem essa possibilidade de renomear o método nativo, esse comportamento não é mais possível na JVM, com ou sem Byte Buddy.
(Para divulgação, sou o mantenedor do Byte Buddy.)
Isenção de responsabilidade: esta não é uma resposta final, apenas uma versão preliminar necessária para discussão.
Na verdade estamos falando de uma parte do BB que não conheço porque também não sou especialista. Mas eu estava curioso e tentei algo como
.transform((builder, typeDescription, classLoader, module) ->
builder
.method(named("wait").and(takesArguments(long.class)))
.intercept(MethodDelegation.to(WaitAdvice.class))
)
em combinação com
public class WaitAdvice {
public static long COUNT = 0;
public static void waitX(long millis) throws InterruptedException {
System.out.println("Before wait -> " + millis);
COUNT++;
}
}
Isso funciona se eu colocar WaitAdviceem um JAR separado e colocá-lo no classpath de bootstrap da JVM. A desvantagem aqui é que não tenho ideia de como chamar o método de destino real do que está sobrescrevendo, ou seja wait(long), não será realmente executado, apenas substituído, o que provavelmente não é o que você deseja. Eu teria que cavar em um monte de código-fonte BB, testes de unidade e integração (porque o tutorial não é muito útil e também desatualizado), problemas de GitHub e questões de StackOverflow para talvez encontrar uma solução para isso, mas até agora eu não encontraram nada de útil que não causasse problemas de acompanhamento imediatamente. Ainda sou muito novato no BB, embora pudesse responder à sua pergunta anterior (mais simples).
Talvez Rafael Winterhalter (mantenedor do BB) tenha uma ideia para você.