java.lang.Objectのwait(long)ネイティブメソッドをByte-Buddyでインターセプトする

Sep 01 2020

「Object.wait」メソッド呼び出しの数を記録しようとしています。インターセプト「publicfinalnative 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:パブリックファイナルネイティブのスーパー(またはデフォルト)メソッドを呼び出すことができません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 voidwait()"をインターセプトする方法を知りたいです。 ()" 方法。

回答

1 RafaelWinterhalter Sep 06 2020 at 20:53

これを調べると、実際にはByteBuddyバージョン1.10.14にバグがあります。リベースされたメソッドには、修飾子private finalだけprivateでなく修飾子が必要です。そうでない場合、JVMはリベースされたメソッドを受け入れません。

修正はすでにマスターにあります。自分でByteBuddyをビルドすると、エージェントを実行できるようになります。次のバージョン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メンテナ)があなたのためのアイデアを持っています。