拦截 java.lang.Object 的 wait(long) 原生方法,使用 Byte-Buddy。

huangapple go评论85阅读模式
英文:

Intercepting java.lang.Object wait(long) native method with Byte-Buddy

问题

我正在尝试记录“Object.wait”方法的调用次数。使用ByteBuddy拦截“public final native void wait(long timeout)”是无效的。我进行了一些测试:

test1、test2打印错误:java.lang.IllegalStateException: 无法调用public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException的super(或默认)方法;
java.lang.IllegalStateException: 无法调用public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException的super(或默认)方法;

test3是无效的,不打印任何内容。

test4是成功的。

以下是我的测试代码:

Java代理:

final public class AgentBootstrap {

	 public static class TestAdvice {
	    @Advice.OnMethodEnter
	    public static void before(@Advice.Origin String methodIns) {
	    	System.out.println("Byte-Buddy 进入:" + methodIns);
	    }

	    @Advice.OnMethodExit
	    public static void after(@Advice.Origin String methodIns) {
	    	System.out.println("Byte-Buddy 退出:" + 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);
 		
    }
	 
	/**
	 * 拦截方法:public final native void wait(long timeout)
	 * 打印错误:java.lang.IllegalStateException: 无法为public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException调用super(或默认)方法
	 */
	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);
	}
	/**
	 * 拦截方法:public final void wait(long timeout, int nanos)
	 * 打印错误:java.lang.IllegalStateException: 无法为public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException调用super(或默认)方法
	 */
	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);
	}
	
	/**
	 * 拦截方法:public final native void wait(long timeout)
	 * 无效,不打印任何内容
	 */
	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);
	}
	/**
	 * 拦截方法:public final void wait(long timeout, int nanos)
	 * 成功
	 */
	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();
}

拦截 java.lang.Object 的 wait(long) 原生方法,使用 Byte-Buddy。 测试4的代码适用于“public final void wait()”方法,它可以工作,但对于“public final native void wait()”方法不起作用。我想知道如何拦截“public final native void wait()”方法。

英文:

I am trying to record the count of "Object.wait" method calls. Intercept "public final native void wait(long timeout)" is invalid with ByteBuddy. I did some tests:

test1、test2 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;
java.lang.IllegalStateException: Cannot call super (or default) method for public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException

test3 is invalid, print nothing.

test4 is success.

Here is my test code:

Javaagent:


final public class AgentBootstrap {

	 public static class TestAdvice {
	    @Advice.OnMethodEnter
	    public static void before(@Advice.Origin String methodIns) {
	    	System.out.println(&quot;Byte-Buddy enter:&quot; + methodIns);
	    }

	    @Advice.OnMethodExit
	    public static void after(@Advice.Origin String methodIns) {
	    	System.out.println(&quot;Byte-Buddy after:&quot; + 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(&quot;$$mynative_&quot;)
		        .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly())
		        .with(AgentBuilder.InstallationListener.StreamWriting.toSystemError())
		        .ignore(nameStartsWith(&quot;net.bytebuddy.&quot;));
		// 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&lt;?&gt; transform(Builder&lt;?&gt; builder, TypeDescription typeDescription, ClassLoader classLoader,JavaModule module) {
              return builder.method(named(&quot;wait&quot;).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&lt;?&gt; transform(Builder&lt;?&gt; builder, TypeDescription typeDescription, ClassLoader classLoader,JavaModule module) {
              return builder.method(named(&quot;wait&quot;).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&lt;?&gt; transform(Builder&lt;?&gt; builder, TypeDescription typeDescription, ClassLoader classLoader,JavaModule module) {
				return builder.visit(Advice.to(TestAdvice.class).on(named(&quot;wait&quot;).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&lt;?&gt; transform(Builder&lt;?&gt; builder, TypeDescription typeDescription, ClassLoader classLoader,JavaModule module) {
				return builder.visit(Advice.to(TestAdvice.class).on(named(&quot;wait&quot;).and(takesArguments(2))));
			}
		})
		.installOn(inst);
	}
	 
}

Test:

public static void main(String[] args) throws Exception {
	new Thread(() -&gt; {
		Object obj = new Object();
	    while (true){
	    	try {
				synchronized (obj) {
					obj.wait(1000);
					obj.wait(1000, 1);
				}
	    	}catch (Exception e) {
	            e.printStackTrace();
	        }
	    }
	}).start();
}

拦截 java.lang.Object 的 wait(long) 原生方法,使用 Byte-Buddy。 Test4 code is for "public final void wait()" method, it works, but it doesn't work for "public final native void wait()" method. I want to know how to intercept "public final native void wait()" method.

答案1

得分: 1

经过调查,实际上在Byte Buddy版本1.10.14中存在一个错误。重新定位的方法必须具有修饰符private final,而不仅仅是private,否则JVM将不会接受重新定位的方法。

修复方法已经在主代码库中更新,如果你自己构建Byte Buddy,现在应该可以运行你的代理程序了。下一个版本1.10.15将包含此修复。

但需要注意,JVM从版本13开始不再支持添加私有(final)方法。可以使用-XX:+AllowRedefinitionToAddDeleteMethods来重置旧的行为。然而,这个选项已经被弃用,并且将在将来的JVM版本中不再存在。在没有重命名本地方法的可能性的情况下,不论是否使用Byte Buddy,在JVM上都不再可能实现这种行为。

(为了公开透明,我是Byte Buddy的维护者。)

英文:

Looking into this, there is actually a bug in Byte Buddy version 1.10.14. The rebased method must have the modifiers private final and not just private, otherwise, the JVM will not accept the rebased method.

The fix is already on master, if you build Byte Buddy yourself, you should be able to run your agent now. The next version 1.10.15 will contain this fix.

Do however note that the JVM does no longer support adding private (final) methods from version 13 on upwards. The old behavior can be reset using -XX:+AllowRedefinitionToAddDeleteMethods. This option is however deprecated and will no longer exist in a future version of the JVM. Without such a possibility to rename the native method, this behavior is no longer possible on the JVM, with or without Byte Buddy.

(For disclosure, I am the maintainer of Byte Buddy.)

答案2

得分: 0

免责声明:这并不是最终答案,仅是为了讨论所需的初步版本。

实际上,我们正在讨论我也不了解的 BB 的一部分,因为我也不是专家。但我很好奇,尝试了类似以下的代码:

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

结合以下代码:

<!-- language: java -->
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 仍然不是很熟悉。也许 BB 的维护者 Rafael Winterhalter 对此有解决办法。

英文:

Disclaimer: This is not a final answer, just a preliminary version needed for discussion.

Actually we are talking about a part of BB which I do not know because I am not an expert either. But I was curious and tried something like

<!-- language: java -->

.transform((builder, typeDescription, classLoader, module) -&gt;
  builder
    .method(named(&quot;wait&quot;).and(takesArguments(long.class)))
    .intercept(MethodDelegation.to(WaitAdvice.class))
)

in combination with

<!-- language: java -->

public class WaitAdvice {
  public static long COUNT = 0;

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

This works if I put WaitAdvice into a separate JAR and put it on the JVM's bootstrap classpath. The downside here is that I have no idea how to call the real target method from the overriding one, i.e. wait(long) will not actually be executed, only replaced, which is probably not what you want. I would have to dig through a whole lot of BB source code, unit and integration tests (because the tutorial is not very helpful and also outdated), GitHub issues and StackOverflow questions in order to maybe find a solution for this, but so far I did not find anything helpful which did not immediately cause follow-up problems. I am still too much of a BB noob, even though I could answer your previous (simpler) question.

Maybe Rafael Winterhalter (the BB maintainer) has an idea for you.

huangapple
  • 本文由 发表于 2020年9月1日 20:58:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/63688183.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定