微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

类重新转换不适用于Java 11上的动态代理

如何解决类重新转换不适用于Java 11上的动态代理

似乎类的动态重新转换仅对Java 8有效,而对Java 11不起作用。在后一种情况下,我从javassist中获得了关于未找到的不同标准java类的异常,例如,我直接引用的类。甚至是要转换的方法的签名。

我该怎么做才能在Java 11上解决该问题?我也想在这里动态地转换类。

出于说明目的,我创建了一个repro文件在这里,我重新转换了两个类:一个是我自己的,另一个是系统。我已经创建了agentmainpremain进行比较。当将主参数传递给应用程序时执行动态变体(我将其作为“ o”传递)。重新转换后,我调用了两个方法(属于我自己的类和系统的一个)。转换成功后,我会收到其他日志记录(“ hi-”和“ scaled!”)。

// MyMain.java
package mypackage;

import com.sun.tools.attach.VirtualMachine;
import javassist.*;
import javassist.bytecode.AccessFlag;
import sun.java2d.SunGraphics2D;
import sun.java2d.SurfaceData;

import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.lang.management.ManagementFactory;
import java.security.ProtectionDomain;

public class MyMain {

    public static void premain(String args,Instrumentation inst) {
        System.out.println("premain start");
        inst.addTransformer(new MyFormer(),true);
        try {
            inst.retransformClasses(MyMain.class);
        } catch (UnmodifiableClassException e) {
            e.printstacktrace();
        }
        System.out.println("premain end");
    }

    public static void agentmain(String args,Instrumentation inst) {
        System.out.println("agentmain start");
        inst.addTransformer(new MyFormer(),true);
        try {
            inst.retransformClasses(MyMain.class);
        } catch (UnmodifiableClassException e) {
            e.printstacktrace();
        }
        System.out.println("agentmain end");
    }

    public static void main(String[] args) {
        if (args.length > 0) {
            attachToThisVm();
        }

        Frame f = new JFrame();
        f.setVisible(true);

        System.out.println(new MyMain().hi());

        SunGraphics2D system = new SunGraphics2D(SurfaceData.getPrimarySurfaceData(new BufferedImage(10,10,BufferedImage.TYPE_INT_ARGB)),Color.BLACK,Color.WHITE,Font.getFont("System"));
        system.draWrenderedImage(null,new AffineTransform() {

            @Override
            public void setToScale(double sx,double sy) {
                super.setToScale(sx,sy);
                System.out.println("scaled!");
            }
        });
    }

    public static void attachToThisVm() {
        System.out.println("dynamically loading javaagent");
        String name = ManagementFactory.getRuntimeMXBean().getName();
        int p = name.indexOf('@');
        String pid = name.substring(0,p);

        try {
            VirtualMachine vm = VirtualMachine.attach(pid);
            vm.loadAgent("javaAgentTest-1.0-SNAPSHOT.jar",null);
            vm.detach();
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
        System.out.println("dynamically loaded javaagent");
    }

    public int hi() {
        return 3;
    }

    public static class MyFormer implements ClassFileTransformer {

        @Override
        public byte[] transform(ClassLoader loader,String className,Class<?> classBeingredefined,ProtectionDomain protectionDomain,byte[] classfilebuffer) throws IllegalClassFormatException {
            return transformClass(className,classfilebuffer);
        }

        private byte[] transformClass(String className,byte[] buffer) {
            if ("mypackage/MyMain".equals(className)) {
                System.out.println(className);

                Classpool cp = Classpool.getDefault();
                String name = className.replace("/",".");
                cp.insertClasspath(new ByteArrayClasspath(name,buffer));
                try {
                    CtClass clazz = cp.get(name);
                    CtBehavior[] declaredBehaviors = clazz.getDeclaredBehaviors();
                    for (CtBehavior db : declaredBehaviors) {
                        if ("hi".equals(db.getName())) {
                            if ((db.getmethodInfo().getAccessFlags() & AccessFlag.STATIC) != 0) {
                                System.out.println("bad access flags,skipping...");
                                return buffer;
                            }

                            System.out.println("Forming hi...");
                            db.insertBefore("System.out.print(\"hi-\");");  // crashes on 11,direct usage case,even referencing to java.lang.Object will crash
                        }
                    }

                    return clazz.toBytecode();
                } catch (Throwable e) {
                    e.printstacktrace();
                    System.out.println("error");
                    return buffer;
                }
            }

            if ("sun/java2d/SunGraphics2D".equals(className)) {
                System.out.println(className);
                Classpool cp = Classpool.getDefault();
                String name = className.replace("/",buffer));
                try {
                    CtClass clazz = cp.get(name);
                    CtBehavior[] declaredBehaviors = clazz.getDeclaredBehaviors();
                    for (CtBehavior db : declaredBehaviors) {
                        if ("sun.java2d.SunGraphics2D.draWrenderedImage(java.awt.image.RenderedImage,java.awt.geom.AffineTransform)".equals(db.getLongName())) {
                            if ((db.getmethodInfo().getAccessFlags() & AccessFlag.STATIC) != 0) {
                                System.out.println("bad access flags,skipping...");
                                return buffer;
                            }

                            System.out.println("Forming draWrenderedImage...");
                            db.insertBefore("$2.setToScale(2.0,2.0);");  // crashes on 11,signature case
                        }
                    }
                    return clazz.toBytecode();
                } catch (NotFoundException | CannotCompileException | IOException e) {
                    e.printstacktrace();
                    return buffer;
                }
            }

            return buffer;
        }
    }
}

我通过Gradle构建了jar

// build.gradle,module name is javaAgentTest
plugins {
    id 'java'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

sourceCompatibility = "1.8"
targetCompatibility = "1.8"

def inline = { deps -> deps.collect { it.isDirectory() ? it : zipTree(it) } }

jar {
    manifest {
        attributes(
                "Can-redefine-Classes": true,"Can-Retransform-Classes": true,"Premain-Class": "mypackage.MyMain","Agent-Class": "mypackage.MyMain",)
    }

    from {
        inline(configurations.runtimeClasspath)  // fat jar
    }
}

dependencies {
    implementation "org.javassist:javassist:3.27.0-GA"
}

在Java 8上,静态和动态变体均起作用:

$ java -version
openjdk version "1.8.0_265"
OpenJDK Runtime Environment (build 1.8.0_265-8u265-b01-0ubuntu2~20.04-b01)
OpenJDK 64-Bit Server VM (build 25.265-b01,mixed mode)
$ java -cp javaAgentTest-1.0-SNAPSHOT.jar -javaagent:javaAgentTest-1.0-SNAPSHOT.jar mypackage.MyMain
premain start
mypackage/MyMain
Forming hi...
premain end
sun/java2d/SunGraphics2D
Forming draWrenderedImage...
hi-3
scaled!
$ java -cp javaAgentTest-1.0-SNAPSHOT.jar:/usr/lib/jvm/java-8-openjdk-amd64/lib/tools.jar mypackage.MyMain o
dynamically loading javaagent
agentmain start
mypackage/MyMain
Forming hi...
agentmain end
dynamically loaded javaagent
sun/java2d/SunGraphics2D
Forming draWrenderedImage...
hi-3
scaled!

在Java 11上,动态变体不起作用(如果没有对hi的引用(例如,仅System.out,它将为db.insertBefore("return 22;");方法派生):

$ java -version
openjdk version "11.0.8" 2020-07-14
OpenJDK Runtime Environment (build 11.0.8+10-post-Ubuntu-0ubuntu120.04)
OpenJDK 64-Bit Server VM (build 11.0.8+10-post-Ubuntu-0ubuntu120.04,mixed mode,sharing)
$ java -cp javaAgentTest-1.0-SNAPSHOT.jar -javaagent:javaAgentTest-1.0-SNAPSHOT.jar mypackage.MyMain
premain start
mypackage/MyMain
Forming hi...
premain end
sun/java2d/SunGraphics2D
Forming draWrenderedImage...
hi-3
scaled!
$ java -cp javaAgentTest-1.0-SNAPSHOT.jar -Djdk.attach.allowAttachSelf=true mypackage.MyMain o
dynamically loading javaagent
agentmain start
mypackage/MyMain
Forming hi...
javassist.CannotCompileException: [source error] no such class: System.out
        at javassist.CtBehavior.insertBefore(CtBehavior.java:806)
        at javassist.CtBehavior.insertBefore(CtBehavior.java:766)
        at mypackage.MyMain$MyFormer.transformClass(MyMain.java:112)
        at mypackage.MyMain$MyFormer.transform(MyMain.java:91)
        at java.instrument/java.lang.instrument.ClassFileTransformer.transform(ClassFileTransformer.java:246)
        at java.instrument/sun.instrument.TransformerManager.transform(TransformerManager.java:188)
        at java.instrument/sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:563)
        at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
        at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:167)
        at mypackage.MyMain.agentmain(MyMain.java:38)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:513)
        at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain(InstrumentationImpl.java:535)
Caused by: compile error: no such class: System.out
        at javassist.compiler.MemberResolver.searchImports(MemberResolver.java:479)
        at javassist.compiler.MemberResolver.lookupClass(MemberResolver.java:422)
        at javassist.compiler.MemberResolver.lookupClassByJvmName(MemberResolver.java:329)
        at javassist.compiler.TypeChecker.atCallExpr(TypeChecker.java:711)
        at javassist.compiler.JvstTypeChecker.atCallExpr(JvstTypeChecker.java:170)
        at javassist.compiler.ast.CallExpr.accept(CallExpr.java:49)
        at javassist.compiler.CodeGen.doTypeCheck(CodeGen.java:266)
        at javassist.compiler.CodeGen.atStmnt(CodeGen.java:360)
        at javassist.compiler.ast.Stmnt.accept(Stmnt.java:53)
        at javassist.compiler.Javac.compileStmnt(Javac.java:578)
        at javassist.CtBehavior.insertBefore(CtBehavior.java:786)
        ... 15 more
error
agentmain end
dynamically loaded javaagent
sun/java2d/SunGraphics2D
Forming draWrenderedImage...
javassist.CannotCompileException: cannot find java.awt.image.RenderedImage
        at javassist.CtBehavior.insertBefore(CtBehavior.java:803)
        at javassist.CtBehavior.insertBefore(CtBehavior.java:766)
        at mypackage.MyMain$MyFormer.transformClass(MyMain.java:140)
        at mypackage.MyMain$MyFormer.transform(MyMain.java:91)
        at java.instrument/java.lang.instrument.ClassFileTransformer.transform(ClassFileTransformer.java:246)
        at java.instrument/sun.instrument.TransformerManager.transform(TransformerManager.java:188)
        at java.instrument/sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:563)
        at java.desktop/sun.java2d.loops.GraphicsPrimitiveMgr.<clinit>(GraphicsPrimitiveMgr.java:56)
        at java.desktop/sun.java2d.loops.Blit.<clinit>(Blit.java:114)
        at java.desktop/sun.java2d.xr.XRPMBlitLoops.register(XRPMBlitLoops.java:46)
        at java.desktop/sun.java2d.xr.XRSurfaceData.initXRSurfaceData(XRSurfaceData.java:106)
        at java.desktop/sun.awt.X11GraphicsEnvironment$1.run(X11GraphicsEnvironment.java:124)
        at java.base/java.security.AccessController.doPrivileged(Native Method)
        at java.desktop/sun.awt.X11GraphicsEnvironment.<clinit>(X11GraphicsEnvironment.java:61)
        at java.base/java.lang.class.forName0(Native Method)
        at java.base/java.lang.class.forName(Class.java:315)
        at java.desktop/java.awt.GraphicsEnvironment$LocalGE.createGE(GraphicsEnvironment.java:101)
        at java.desktop/java.awt.GraphicsEnvironment$LocalGE.<clinit>(GraphicsEnvironment.java:83)
        at java.desktop/java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(GraphicsEnvironment.java:129)
        at java.desktop/java.awt.Window.initGC(Window.java:487)
        at java.desktop/java.awt.Window.init(Window.java:507)
        at java.desktop/java.awt.Window.<init>(Window.java:549)
        at java.desktop/java.awt.Frame.<init>(Frame.java:423)
        at java.desktop/java.awt.Frame.<init>(Frame.java:388)
        at java.desktop/javax.swing.JFrame.<init>(JFrame.java:180)
        at mypackage.MyMain.main(UnkNown Source)
Caused by: javassist.NotFoundException: java.awt.image.RenderedImage
        at javassist.Classpool.get(Classpool.java:430)
        at javassist.bytecode.Descriptor.toCtClass(Descriptor.java:571)
        at javassist.bytecode.Descriptor.getParameterTypes(Descriptor.java:424)
        at javassist.CtBehavior.getParameterTypes(CtBehavior.java:323)
        at javassist.CtBehavior.insertBefore(CtBehavior.java:781)
        ... 25 more
3

解决方法

我花了一些时间来分析您的代码,并在那里发现了几个概念性问题,所有这些问题都位于总体主题引导下。简而言之,它类似于旧问题:首先是鸡肉还是鸡蛋?

您的系统包含多个组件:

  • Java代理
  • 类文件转换器(使用Javassist)
  • 必要时进行代理热连接的主类。
  • 转化目标类

您没有将它们放入单独的类中,而是将所有内容填充到一个类MyMain中。好的,转换器属于静态内部类,但这通常不会改变这种情况。因此,您想要做的是启动一个代理,该代理在已经运行时对其进行自我转换,因为它是其自身的转换目标。这是一个坏主意。

如果您只是稍微重构一下意大利面条代码,问题就会消失。抱歉,为了更好的可读性,我无法拒绝重命名一些东西,但是转换应用程序类和Java2D类的这两种方法仍然包含很多冗余(重复代码),由于时间不够,我没有清除它们。所以我留给你。即使您确切地知道目标方法是非静态的,对静态标志的奇怪检查也可能消失,除非您的真实代码更通用并且可以转换多个方法。为简单起见,我将其从您的示例代码版本中删除。

我也建议将Java代理+类文件转换器放入单独的代理JAR中,即使如果所有内容都在一个JAR中,我的重构版本也可以使用。

请注意不要对可能已经转换的类手动调用retransformClasses,例如在上课时。

类文件转换器

package mypackage;

import javassist.*;

import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class MyTransformer implements ClassFileTransformer {
  @Override
  public byte[] transform(ClassLoader loader,String className,Class<?> classBeingRedefined,ProtectionDomain protectionDomain,byte[] classfileBuffer) throws IllegalClassFormatException {
    return transformClass(className,classfileBuffer);
  }

  private byte[] transformClass(String className,byte[] buffer) {
    switch (className) {
      case "mypackage/MyApplication":
        return transformMyApplication(className,buffer);
      case "sun/java2d/SunGraphics2D":
        return transformSunGraphics2D(className,buffer);
      default:
        return buffer;
    }
  }

  private byte[] transformMyApplication(String className,byte[] buffer) {
    System.out.println(className);

    ClassPool cp = ClassPool.getDefault();
    String name = className.replace("/",".");
    cp.insertClassPath(new ByteArrayClassPath(name,buffer));
    try {
      CtClass clazz = cp.get(name);
      clazz.defrost();
      CtBehavior[] declaredBehaviors = clazz.getDeclaredBehaviors();
      for (CtBehavior db : declaredBehaviors) {
        if ("hi".equals(db.getName())) {
          System.out.println("Transforming hi...");
          db.insertBefore("System.out.println(\"Hi!\");");
        }
      }

      return clazz.toBytecode();
    }
    catch (Throwable e) {
      e.printStackTrace();
      System.out.println("error");
      return buffer;
    }
  }

  private byte[] transformSunGraphics2D(String className,byte[] buffer) {
    System.out.println(className);
    ClassPool cp = ClassPool.getDefault();
    String name = className.replace("/",buffer));
    try {
      CtClass clazz = cp.get(name);
      CtBehavior[] declaredBehaviors = clazz.getDeclaredBehaviors();
      for (CtBehavior db : declaredBehaviors) {
        if ("sun.java2d.SunGraphics2D.drawRenderedImage(java.awt.image.RenderedImage,java.awt.geom.AffineTransform)".equals(db.getLongName())) {
          System.out.println("Transforming drawRenderedImage...");
          db.insertBefore("$2.setToScale(2.0,2.0);");
        }
      }
      return clazz.toBytecode();
    }
    catch (NotFoundException | CannotCompileException | IOException e) {
      e.printStackTrace();
      return buffer;
    }
  }
}

Java代理:

请注意如何使用MyAgent.instrumentation以便公开访问代理附加的信息。我们将在以后看到此功能。

package mypackage;

import java.lang.instrument.Instrumentation;

public class MyAgent {
  public static Instrumentation instrumentation;

  public static void premain(String args,Instrumentation inst) {
    System.out.println("premain - start");
    instrumentation = inst;
    inst.addTransformer(new MyTransformer(),true);
    System.out.println("premain - end");
  }

  public static void agentmain(String args,Instrumentation inst) {
    System.out.println("agentmain - start");
    premain(args,inst);
    System.out.println("agentmain - end");
  }
}

主要类按需进行热连接:

如您所见,我使用MyAgent.instrumentation来自动检测代理是否已连接。因此,不再需要使用命令行参数。

package mypackage;

import com.sun.tools.attach.VirtualMachine;

import java.lang.instrument.UnmodifiableClassException;
import java.lang.management.ManagementFactory;

public class MyMain {
  private static final String AGENT_PATH = "build/libs/SO_Javassist_SystemOutRecognisedAsClass_64340794-1.0-SNAPSHOT.jar";

  public static void main(String[] args) {
    if (MyAgent.instrumentation == null) {
      attachAgent();
      // This is only necessary if you want to transform an already loaded class,// which in this example is not the case
      // transform(MyApplication.class,SunGraphics2D.class);
    }
    MyApplication.main(args);
  }

  public static void attachAgent() {
    System.out.println("Dynamically attaching Java agent - start");
    String jvmName = ManagementFactory.getRuntimeMXBean().getName();
    String pid = jvmName.substring(0,jvmName.indexOf('@'));

    try {
      VirtualMachine vm = VirtualMachine.attach(pid);
      vm.loadAgent(AGENT_PATH,null);
      vm.detach();
    }
    catch (Exception e) {
      throw new RuntimeException(e);
    }
    finally {
      System.out.println("Dynamically attaching Java agent - end");
    }
  }

  public static void transform(Class<?>... targetClasses) {
    try {
      MyAgent.instrumentation.retransformClasses(targetClasses);
    }
    catch (UnmodifiableClassException e) {
      e.printStackTrace();
    }
  }
}

样本目标类别:

这只是一个示例类。在这种情况下,它包含您要转换的hi方法。

package mypackage;

import sun.java2d.SunGraphics2D;

import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;

import static javax.swing.WindowConstants.EXIT_ON_CLOSE;
import static sun.java2d.SurfaceData.getPrimarySurfaceData;

public class MyApplication {
  public static void main(String[] args) {
    System.out.println(new MyApplication().hi());

    JFrame jFrame = new JFrame();
    jFrame.setDefaultCloseOperation(EXIT_ON_CLOSE);
    SunGraphics2D graphics2D = new SunGraphics2D(
      getPrimarySurfaceData(new BufferedImage(10,10,BufferedImage.TYPE_INT_ARGB)),Color.BLACK,Color.WHITE,Font.getFont("System")
    );
    graphics2D.drawRenderedImage(
      null,new AffineTransform() {
        @Override
        public void setToScale(double sx,double sy) {
          super.setToScale(sx,sy);
          System.out.println("scaled!");
        }
      }
    );
    jFrame.setVisible(true);
  }

  public int hi() {
    return 3;
  }
}

您还想更新清单文件生成器:

  manifest {
    attributes(
      "Can-Redefine-Classes": true,"Can-Retransform-Classes": true,"Premain-Class": "mypackage.MyAgent","Agent-Class": "mypackage.MyAgent",)
  }

现在,一切都可以在Java 11+上按预期运行。 Javassist中不再存在系统类路径问题,因为您无需再尝试将代理程序本身热连接到属于Attach Listener线程组的system线程中时对其进行转换。

XXX> java -cp build/libs/SO_Javassist_SystemOutRecognisedAsClass_64340794-1.0-SNAPSHOT.jar -javaagent:build/libs/SO_Javassist_SystemOutRecognisedAsClass_64340794-1.0-SNAPSHOT.jar mypackage.MyMain
premain - start
premain - end
mypackage/MyApplication
Transforming hi...
Hi!
3
sun/java2d/SunGraphics2D
Transforming drawRenderedImage...
scaled!

XXX> java -cp build/libs/SO_Javassist_SystemOutRecognisedAsClass_64340794-1.0-SNAPSHOT.jar -Djdk.attach.allowAttachSelf=true mypackage.MyMain
Dynamically attaching Java agent - start
agentmain - start
premain - start
premain - end
agentmain - end
Dynamically attaching Java agent - end
mypackage/MyApplication
Transforming hi...
Hi!
3
sun/java2d/SunGraphics2D
Transforming drawRenderedImage...
scaled!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。