Byte-Buddy ile java.lang.Object wait (long) native yöntemini yakalama
"Object.wait" yöntemi çağrılarının sayısını kaydetmeye çalışıyorum. "Genel nihai yerel geçersiz beklemesi (uzun zaman aşımı)" kesilmesi, ByteBuddy ile geçersizdir. Bazı testler yaptım:
test1 、 test2 yazdırma hatası: java.lang.IllegalStateException: Genel son void için süper (veya varsayılan) yöntem çağrılamıyor java.lang.Object.wait (long, int) java.lang.InterruptedException; java.lang.IllegalStateException: Genel nihai yerel void için süper (veya varsayılan) yöntem çağrılamıyor java.lang.Object.wait (long) java.lang.InterruptedException atıyor
test3 geçersiz, hiçbir şey yazdırmayın.
test4 başarıdır.
İşte test kodum:
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);
}
}
Ölçek:
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 kodu "public final void wait ()" yöntemi içindir, çalışır, ancak "public final native void wait ()" yöntemi için çalışmaz. "Public final native void wait ()" yöntem.
Yanıtlar
Buna bakıldığında, aslında Byte Buddy'nin 1.10.14 sürümünde bir hata var. Yeniden yapılanma yönteminin değiştiricilere sahip olması gerekir, private finalyalnızca bu değil private, aksi takdirde JVM yeniden adlandırma yöntemini kabul etmez.
Düzeltme zaten ustada, Byte Buddy'yi kendiniz oluşturuyorsanız, ajanınızı şimdi çalıştırabilmelisiniz. Sonraki sürüm 1.10.15 bu düzeltmeyi içerecek.
Bununla birlikte, JVM'nin sürüm 13'ten itibaren özel (nihai) yöntemler eklemeyi artık desteklemediğini unutmayın. Eski davranış, kullanılarak sıfırlanabilir -XX:+AllowRedefinitionToAddDeleteMethods. Ancak bu seçenek kullanımdan kaldırılmıştır ve artık JVM'nin gelecekteki bir sürümünde mevcut olmayacaktır. Yerel yöntemi yeniden adlandırmak için böyle bir olanak olmadan, bu davranış Byte Buddy ile veya onsuz JVM'de artık mümkün değildir.
(Açıklamak için, Byte Buddy'nin bakıcısıyım.)
Feragatname: Bu son bir cevap değil, sadece tartışma için gerekli olan bir ön versiyon.
Aslında BB'nin bilmediğim bir kısmından bahsediyoruz çünkü ben de uzman değilim. Ama merak ettim ve şöyle bir şey denedim
.transform((builder, typeDescription, classLoader, module) ->
builder
.method(named("wait").and(takesArguments(long.class)))
.intercept(MethodDelegation.to(WaitAdvice.class))
)
ile bütünlüğünde
public class WaitAdvice {
public static long COUNT = 0;
public static void waitX(long millis) throws InterruptedException {
System.out.println("Before wait -> " + millis);
COUNT++;
}
}
Bu WaitAdvice, ayrı bir JAR'a koyarsam ve JVM'nin önyükleme sınıf yoluna koyarsam işe yarar . Buradaki dezavantajı, gerçek hedef yöntemi geçersiz kılan yöntemden nasıl çağıracağıma dair hiçbir fikrim yok, yani wait(long)aslında çalıştırılmayacak, sadece değiştirilecek, ki bu muhtemelen istediğiniz şey değil. Bunun için bir çözüm bulmak için çok sayıda BB kaynak kodu, birim ve entegrasyon testini (eğitim çok yararlı olmadığı ve aynı zamanda modası geçmiş olduğu için), GitHub sorunlarını ve StackOverflow sorularını araştırmam gerekirdi, ama şimdiye kadar hemen takip sorunlarına neden olmayan yararlı bir şey bulamadı. Önceki (daha basit) sorunuzu cevaplayabilsem bile, hala çok fazla BB noob'um.
Belki Rafael Winterhalter'ın (BB yöneticisi) sizin için bir fikri vardır.