Mencegat metode asli java.lang.Object wait (lama) dengan Byte-Buddy
Saya mencoba merekam jumlah panggilan metode "Object.wait". Intercept "public final native void wait (long timeout)" tidak valid dengan ByteBuddy. Saya melakukan beberapa tes:
test1, kesalahan print test2: java.lang.IllegalStateException: Tidak dapat memanggil metode super (atau default) untuk public final void java.lang.Object.wait (long, int) melempar java.lang.InterruptedException; java.lang.IllegalStateException: Tidak dapat memanggil metode super (atau default) untuk native final void java.lang.Object.wait (long) melempar java.lang.InterruptedException
test3 tidak valid, tidak mencetak apa pun.
test4 sukses.
Ini kode tes saya:
Agen jawa:
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);
}
}
Uji:
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();
}
:( Kode Test4 adalah untuk metode "public final void wait ()", ini berfungsi, tetapi tidak berfungsi untuk metode "public final native void wait ()". Saya ingin tahu cara mencegat "tunggu public final native void ()" metode.
Jawaban
Melihat ini, sebenarnya ada bug di Byte Buddy versi 1.10.14. Metode rebased harus memiliki pengubah private finaldan bukan hanya private, jika tidak, JVM tidak akan menerima metode rebased.
Perbaikannya sudah ada di master, jika Anda membangun Byte Buddy sendiri, Anda seharusnya sudah bisa menjalankan agen Anda sekarang. Versi 1.10.15 berikutnya akan berisi perbaikan ini.
Namun, perhatikan bahwa JVM tidak lagi mendukung penambahan metode pribadi (final) dari versi 13 ke atas. Perilaku lama dapat diatur ulang menggunakan -XX:+AllowRedefinitionToAddDeleteMethods. Namun, opsi ini sudah usang dan tidak akan ada lagi di versi JVM mendatang. Tanpa kemungkinan untuk mengganti nama metode asli, perilaku ini tidak mungkin lagi di JVM, dengan atau tanpa Byte Buddy.
(Untuk pengungkapan, saya adalah pengelola Byte Buddy.)
Penafian: Ini bukan jawaban akhir, hanya versi pendahuluan yang diperlukan untuk diskusi.
Sebenarnya kita berbicara tentang bagian dari BB yang saya tidak tahu karena saya juga bukan ahli. Tapi saya penasaran dan mencoba sesuatu seperti
.transform((builder, typeDescription, classLoader, module) ->
builder
.method(named("wait").and(takesArguments(long.class)))
.intercept(MethodDelegation.to(WaitAdvice.class))
)
dalam kombinasi dengan
public class WaitAdvice {
public static long COUNT = 0;
public static void waitX(long millis) throws InterruptedException {
System.out.println("Before wait -> " + millis);
COUNT++;
}
}
Ini berfungsi jika saya memasukkan WaitAdviceJAR terpisah dan meletakkannya di jalur kelas bootstrap JVM. Sisi negatifnya di sini adalah bahwa saya tidak tahu bagaimana memanggil metode target sebenarnya dari metode yang menimpa, yaitu wait(long)tidak akan benar-benar dijalankan, hanya diganti, yang mungkin bukan yang Anda inginkan. Saya harus menggali banyak kode sumber BB, unit dan tes integrasi (karena tutorial tidak terlalu membantu dan juga ketinggalan jaman), masalah GitHub dan pertanyaan StackOverflow untuk mungkin menemukan solusi untuk ini, tetapi sejauh ini saya tidak menemukan sesuatu yang membantu yang tidak segera menyebabkan masalah tindak lanjut. Saya masih terlalu banyak noob BB, meskipun saya bisa menjawab pertanyaan Anda sebelumnya (lebih sederhana).
Mungkin Rafael Winterhalter (pengelola BB) punya ide untuk Anda.