Chặn phương thức gốc java.lang.Object wait (long) với Byte-Buddy
Tôi đang cố gắng ghi lại số lần gọi phương thức "Object.wait". Lệnh chặn "public Final void wait (thời gian chờ lâu)" không hợp lệ với ByteBuddy. Tôi đã làm một số bài kiểm tra:
lỗi in test1 、 test2: java.lang.IllegalStateException: Không thể gọi phương thức super (hoặc mặc định) cho public Final void java.lang.Object.wait (long, int) ném java.lang.InterruptException; java.lang.IllegalStateException: Không thể gọi phương thức super (hoặc mặc định) cho public cuối cùng gốc void java.lang.Object.wait (long) ném java.lang.InterruptException
test3 không hợp lệ, không in được gì.
test4 là thành công.
Đây là mã thử nghiệm của tôi:
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);
}
}
Kiểm tra:
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();
}
:( Mã test4 dành cho phương thức "public Final void wait ()", nó hoạt động, nhưng nó không hoạt động cho phương thức "public Final native void wait ()". Tôi muốn biết cách chặn "public Final native void wait ()" phương pháp.
Trả lời
Nhìn vào điều này, thực sự có một lỗi trong Byte Buddy phiên bản 1.10.14. Phương thức phục hồi phải có các bổ ngữ private finalvà không chỉ private, nếu không, JVM sẽ không chấp nhận phương thức tăng lại.
Bản sửa lỗi đã có sẵn, nếu bạn tự xây dựng Byte Buddy, bạn sẽ có thể chạy đại lý của mình ngay bây giờ. Phiên bản tiếp theo 1.10.15 sẽ có bản sửa lỗi này.
Tuy nhiên, xin lưu ý rằng JVM không còn hỗ trợ thêm các phương thức riêng tư (cuối cùng) từ phiên bản 13 trở lên. Hành vi cũ có thể được thiết lập lại bằng cách sử dụng -XX:+AllowRedefinitionToAddDeleteMethods. Tuy nhiên, tùy chọn này không được dùng nữa và sẽ không còn tồn tại trong phiên bản tương lai của JVM. Nếu không có khả năng đổi tên phương thức gốc, hành vi này không còn khả thi trên JVM, dù có hoặc không có Byte Buddy.
(Để tiết lộ, tôi là người duy trì Byte Buddy.)
Tuyên bố từ chối trách nhiệm: Đây không phải là câu trả lời cuối cùng, chỉ là một phiên bản sơ bộ cần thiết để thảo luận.
Thực ra chúng ta đang nói về một phần của BB mà tôi không biết vì tôi cũng không phải là chuyên gia. Nhưng tôi tò mò và thử một cái gì đó như
.transform((builder, typeDescription, classLoader, module) ->
builder
.method(named("wait").and(takesArguments(long.class)))
.intercept(MethodDelegation.to(WaitAdvice.class))
)
kết hợp với
public class WaitAdvice {
public static long COUNT = 0;
public static void waitX(long millis) throws InterruptedException {
System.out.println("Before wait -> " + millis);
COUNT++;
}
}
Điều này hoạt động nếu tôi đặt WaitAdvicevào một JAR riêng biệt và đặt nó trên đường dẫn bootstrap của JVM. Nhược điểm ở đây là tôi không biết cách gọi phương thức đích thực từ phương thức ghi đè, tức là wait(long)sẽ không thực sự được thực thi, chỉ được thay thế, có thể không phải là điều bạn muốn. Tôi sẽ phải tìm hiểu rất nhiều mã nguồn BB, bài kiểm tra đơn vị và tích hợp (vì hướng dẫn này không hữu ích lắm và cũng đã lỗi thời), các vấn đề về GitHub và câu hỏi về StackOverflow để có thể tìm ra giải pháp cho vấn đề này, nhưng cho đến nay tôi không tìm thấy bất cứ điều gì hữu ích mà không gây ra các vấn đề tiếp theo ngay lập tức. Tôi vẫn còn là một BB noob, mặc dù tôi có thể trả lời câu hỏi trước (đơn giản hơn) của bạn.
Có lẽ Rafael Winterhalter (người bảo trì BB) có một ý tưởng cho bạn.