ByteBuddy 截获的方法未被调用

ByteBuddy intercepted method not getting called

private void orsc.ORSCApplet.draw() 绘制视频游戏的每一帧。目标是拦截 draw() 方法,以便我们可以自己绘制当前绘制的框架,这样我们就可以显示用户通常不可见的信息。

下面我用ByteBuddy新建了一个orsc.ORSCApplet的subclass,然后拦截draw()函数,这样它就可以正常调用draw()也呼叫 public void PaintCallback.paint().

        ClassLoader myLoader = this.getClass().getClassLoader();
        
        new ByteBuddy().subclass(orsc.ORSCApplet.class)
                       .method(ElementMatchers.named("draw"))
                       .intercept(SuperMethodCall.INSTANCE.andThen(MethodCall.invoke(PaintCallback.class.getMethod("paint"))))
                       .make()
                       .load(myLoader); //I have also tried fromInstalledAgent (BuddyByteAgent.install() ran) and INJECTION
        

电子游戏的main()orsc.OpenRSC里面。因此,我们将使用带有拦截方法的 class 加载器创建一个新的 orsc.OpenRSC 对象:

        Class loadedMyClass = myLoader.loadClass("orsc.OpenRSC");
        Constructor constructor = loadedMyClass.getConstructor();
        Object myClassObject = constructor.newInstance();
        return myClassObject; //later on, we call orsc.OpenRSC.main() with this object.

但是,我没有接到任何打给 PaintCallback.paint() 的电话。为什么我的拦截不起作用?我有三个理论:

  1. 尽管使用了 myLoader class 加载程序,但 class 正在其他地方加载。但是,我尝试使用 ByteBuddyAgent.install() 并在 .load(myLoader, ClassLoadingStrategy.fromInstalledAgent()).

    中指定了它
  2. 我应该尝试 redefine 而不是 subclass 吗?如果是这样,如果我interceptredefine,我可以调用原始函数吗?如果是这样,如何?我尝试了很多不同的方法,结果我的轮子在泥里打转了。

  3. 而不是subclassredefine,我应该做rebase吗?我尝试使用以下代码进行此操作,并从检测库收到异常:

             new ByteBuddy().rebase(orsc.ORSCApplet.class)
                        .method(ElementMatchers.named("draw"))
                        .intercept(SuperMethodCall.INSTANCE.andThen(MethodCall.invoke(PaintCallback.class.getMethod("paint"))))
                        .make()
                        .load(myLoader, 
                                ClassReloadingStrategy.fromInstalledAgent());
    
     java.lang.IllegalStateException: Error invoking java.lang.instrument.Instrumentation#retransformClasses
         at net.bytebuddy.dynamic.loading.ClassReloadingStrategy$Dispatcher$ForJava6CapableVm.retransformClasses(ClassReloadingStrategy.java:503)
         at net.bytebuddy.dynamic.loading.ClassReloadingStrategy$Strategy.apply(ClassReloadingStrategy.java:568)
         at net.bytebuddy.dynamic.loading.ClassReloadingStrategy.load(ClassReloadingStrategy.java:225)
         at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:100)
         at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6292)
         at reflector.Reflector.injectCallbacksAndReturnClient(Reflector.java:319)
         at reflector.Reflector.createClient(Reflector.java:50)
         at bot.Main.main(Main.java:164)
     Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method
         at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
         at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:167)
         at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
         at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
         at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
         at java.base/java.lang.reflect.Method.invoke(Method.java:564)
         at net.bytebuddy.dynamic.loading.ClassReloadingStrategy$Dispatcher$ForJava6CapableVm.retransformClasses(ClassReloadingStrategy.java:495)
         ... 7 more
    

当您创建一个子class时,您必须确保这个子class在创建它的地方返回,通常是从某种工厂返回。否则,您的 subclass 将存在,但永远不会被使用。我不清楚您在代码中是否这样做。

Rebase 可以看作是 subclassing,其中原始 class 在其自身内扩展,以避免这种从通常很难创建工厂的代理进行 subclassing 的限制.然而,这通常需要添加方法或字段,而当 class 已经加载时这是不可能的,即使使用 Java 代理也是如此。但是,在您的情况下,如果您避免引用 orsc.ORSCApplet.class,则可以创建一个带有 TypePool.Default 的描述并将其提供给 Byte Buddy。如果您随后通过注入加载经过检测的 class,它将优先于原始 class 文件并在您的应用程序中使用。

然而,最优雅的方法是使用重新定义,它可以应用于加载的 classes。通常,您会为此使用 AgentBuilder。如果要将代码添加到现有方法,理想情况下,您最好使用 Advice,而不是此处的委托,因为建议将代码添加到方法的开头 and/or 结尾。确保使用DynamicType.Builder::visit而不是常规拦截将建议作为装饰应用。