Byte-Buddy로 java.lang.Object wait (long) 네이티브 메서드 가로 채기

Sep 01 2020

"Object.wait"메서드 호출 수를 기록하려고합니다. Intercept "public final native void wait (long timeout)"은 ByteBuddy에서 유효하지 않습니다. 몇 가지 테스트를했습니다.

test1 、 test2 인쇄 오류 : java.lang.IllegalStateException : public final void java.lang.Object.wait (long, int) throws java.lang.InterruptedException에 대한 수퍼 (또는 기본) 메서드를 호출 할 수 없습니다. java.lang.IllegalStateException : public final native void java.lang.Object.wait (long) throws java.lang.InterruptedException에 대한 수퍼 (또는 기본) 메소드를 호출 할 수 없습니다.

test3이 유효하지 않습니다. 아무것도 인쇄하지 않습니다.

test4는 성공입니다.

내 테스트 코드는 다음과 같습니다.

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);
    }
     
}

테스트:

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 코드는 "public final void wait ()"메서드 용입니다. 작동하지만 "public final native void wait ()"메서드에서는 작동하지 않습니다. "public final native void wait"를 가로채는 방법을 알고 싶습니다. () "메서드.

답변

1 RafaelWinterhalter Sep 06 2020 at 20:53

이것을 살펴보면 실제로 Byte Buddy 버전 1.10.14에 버그가 있습니다. rebased 메소드에는 private final뿐만 아니라 수정자가 있어야합니다 private. 그렇지 않으면 JVM이 rebased 메소드를 허용하지 않습니다.

수정은 이미 마스터에 있습니다. Byte Buddy를 직접 빌드하면 지금 에이전트를 실행할 수 있습니다. 다음 버전 1.10.15에는이 수정 사항이 포함될 것입니다.

그러나 JVM은 더 이상 버전 13 이상의 개인 (최종) 메소드 추가를 지원하지 않습니다. 이전 동작은를 사용하여 재설정 할 수 있습니다 -XX:+AllowRedefinitionToAddDeleteMethods. 그러나이 옵션은 더 이상 사용되지 않으며 향후 JVM 버전에 더 이상 존재하지 않습니다. 네이티브 메서드의 이름을 바꿀 가능성이 없으면이 동작은 Byte Buddy의 유무에 관계없이 JVM에서 더 이상 가능하지 않습니다.

(공개를 위해 저는 Byte Buddy의 관리자입니다.)

kriegaex Sep 02 2020 at 08:32

면책 조항 : 이것은 최종 답변이 아니며 논의에 필요한 예비 버전 일뿐입니다.

사실 저도 전문가가 아니기 때문에 모르는 BB의 일부에 대해 이야기하고 있습니다. 근데 궁금해서 뭔가 해봤어요

.transform((builder, typeDescription, classLoader, module) ->
  builder
    .method(named("wait").and(takesArguments(long.class)))
    .intercept(MethodDelegation.to(WaitAdvice.class))
)

함께

public class WaitAdvice {
  public static long COUNT = 0;

  public static void waitX(long millis) throws InterruptedException {
    System.out.println("Before wait -> " + millis);
    COUNT++;
  }
}

이것은 WaitAdvice별도의 JAR에 넣고 JVM의 부트 스트랩 클래스 경로에 넣으면 작동합니다 . 여기서 단점은 재정의하는 방법에서 실제 대상 메서드를 호출하는 방법을 모른다 wait(long)는 것입니다 . 즉, 실제로 실행되지 않고 대체 될 뿐이며 이는 아마도 원하는 것이 아닐 것입니다. 이에 대한 해결책을 찾으려면 BB 소스 코드, 단위 및 통합 테스트 (튜토리얼이 그다지 도움이되지 않고 구식이 아니기 때문에), GitHub 문제 및 StackOverflow 질문을 많이 조사해야하지만 지금까지는 즉시 후속 문제를 일으키지 않는 유용한 정보를 찾지 못했습니다. 이전의 (간단한) 질문에 답할 수 있지만 여전히 BB 멍청한 사람입니다.

아마도 Rafael Winterhalter (BB 관리자)가 당신을위한 아이디어를 가지고있을 것입니다.