Class retransformation doesn’t work for dynamic agent on Java 11

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

Class retransformation doesn't work for dynamic agent on Java 11

问题

以下是您提供的代码部分的翻译:

// 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 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)) {
                // ... (对 MyMain 类的转换操作)
            }

            if ("sun/java2d/SunGraphics2D".equals(className)) {
                // ... (对 SunGraphics2D 类的转换操作)
            }

            return buffer;
        }
    }
}
// build.gradle
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"
}

请注意,以上内容仅为您提供的代码部分的翻译,其中包括了MyMain.java文件以及build.gradle文件的翻译。如果您需要对这些翻译进行调整或有任何其他问题,请随时告诉我。

英文:

It seems like dynamic retransformations of classes work for me only on Java 8 but not on Java 11. In the latter case, I get exceptions from javassist about different not found standard Java classes, for example, the ones directly referenced by me or even from the signature of the method-to-transform.

What should I do to fix that on Java 11? I want to transform classes dynamically here too.

For illustration purposes, I've created a repro file. Here I retransform two classes: one is my own, another is system. I've created both agentmain and premain to compare. Dynamic variant is executed when a main argument is passed to the app (I pass it as just "o"). After the retransformation, I call two methods (of my own class and of the system one). When the transformation is successful, I receive additional logging ("hi-" and "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(&quot;premain start&quot;);
        inst.addTransformer(new MyFormer(), true);
        try {
            inst.retransformClasses(MyMain.class);
        } catch (UnmodifiableClassException e) {
            e.printStackTrace();
        }
        System.out.println(&quot;premain end&quot;);
    }

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

    public static void main(String[] args) {
        if (args.length &gt; 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(&quot;System&quot;));
        system.drawRenderedImage(null,new AffineTransform() {

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

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

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

    public int hi() {
        return 3;
    }

    public static class MyFormer implements ClassFileTransformer {

        @Override
        public byte[] transform(ClassLoader loader, String className, Class&lt;?&gt; classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            return transformClass(className, classfileBuffer);
        }

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

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

                            System.out.println(&quot;Forming hi...&quot;);
                            db.insertBefore(&quot;System.out.print(\&quot;hi-\&quot;);&quot;);  // 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(&quot;error&quot;);
                    return buffer;
                }
            }

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

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

            return buffer;
        }
    }
}

I build jar via Gradle:

// build.gradle, module name is javaAgentTest
plugins {
    id &#39;java&#39;
}

group &#39;org.example&#39;
version &#39;1.0-SNAPSHOT&#39;

repositories {
    mavenCentral()
}

sourceCompatibility = &quot;1.8&quot;
targetCompatibility = &quot;1.8&quot;

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

jar {
    manifest {
        attributes(
                &quot;Can-Redefine-Classes&quot;: true,
                &quot;Can-Retransform-Classes&quot;: true,
                &quot;Premain-Class&quot;: &quot;mypackage.MyMain&quot;,
                &quot;Agent-Class&quot;: &quot;mypackage.MyMain&quot;,
        )
    }

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

dependencies {
    implementation &quot;org.javassist:javassist:3.27.0-GA&quot;
}

On Java 8, both static and dynamic variants work:

$ java -version
openjdk version &quot;1.8.0_265&quot;
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!

On Java 11, dynamic variant doesn't work (it will fork for hi method if there is no reference to System.out, for example, just db.insertBefore(&quot;return 22;&quot;);):

$ java -version
openjdk version &quot;11.0.8&quot; 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.&lt;clinit&gt;(GraphicsPrimitiveMgr.java:56)
at java.desktop/sun.java2d.loops.Blit.&lt;clinit&gt;(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.&lt;clinit&gt;(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.&lt;clinit&gt;(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.&lt;init&gt;(Window.java:549)
at java.desktop/java.awt.Frame.&lt;init&gt;(Frame.java:423)
at java.desktop/java.awt.Frame.&lt;init&gt;(Frame.java:388)
at java.desktop/javax.swing.JFrame.&lt;init&gt;(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

答案1

得分: 2

我花了一些时间来分析你的代码,并发现了一些概念上的问题,都涉及到了**引导(bootstrapping)**这个主题。简单来说,这类似于一个古老的问题:先有鸡还是先有蛋?

你的系统有多个组件:

  • Java代理(Java agent)
  • 类文件转换器(使用Javassist)
  • 主类执行代理的热附加,如果需要的话。
  • 转换目标类

但是你没有将它们放入单独的类中,而是把所有东西都塞进了一个名为MyMain的类里。好吧,转换器在一个静态内部类中,但总体情况没有改变。所以,你尝试的是在代理已经运行的情况下对自身进行转换,因为它是自身的转换目标。这是一个不好的想法。

如果你稍微重构一下你的代码,问题就会消失。抱歉,我忍不住为了更好的可读性给一些东西重新命名,但是转换应用类和Java2D类的两个方法仍然包含了很多冗余代码,因为时间不够,我没有进行清理。所以,这部分就留给你了。即使你真正的代码更通用,会转换多个方法,也建议你将对静态标志的奇怪检查去掉,除非你的真实代码更通用,会转换多个方法。出于简单起见,我在我的版本中将其移除了。

我还建议将Java代理+类文件转换器放入单独的代理JAR中,即使在我重构后的版本中,如果所有东西都在一个JAR中,也能正常工作。

请注意,我在MyAgent.instrumentation中使用了一个特性,以便在后面可以公开访问代理被附加的信息。

主类进行按需热附加:

正如你所看到的,我使用MyAgent.instrumentation来自动检测代理是否已经附加。因此,不再需要使用命令行参数。

示例目标类:

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

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

现在一切都按预期在Java 11+上运行。在Javassist中不再有系统类路径问题,因为你不再尝试在Attach Listener线程中将代理本身进行转换,该线程属于system线程组。

以上是你提供的代码的翻译部分。

英文:

I took some time to analyse your code and found several conceptual problems there, all under the overarching topic bootstrapping. Simply put, it is similar to the old question: What was there first, the chicken or the egg?

Your system has multiple components:

  • Java agent
  • class file transformer (using Javassist)
  • main class doing agent hot-attachment, if necessary.
  • transformation target classes

Instead of putting them into separate classes, you stuffed everything into a single class MyMain. Okay, the transformer is in a static inner class, but that does not change the situation in general. So what you are trying to do is to start an agent which transforms itself while it is already running, because it is its own transformation target. This is a bad idea.

If you just refactor your spaghetti code a little bit, the problems will go away. Sorry, I could not resist renaming a few things for better readability, but the two methods transforming the application class and the Java2D class still contain a lot of redundancy (duplicate code) which I did not clean up because I was running out of time. So I leave that up to you. The strange check for a static flag even though you exactly know that your target methods are non-static could also go away, unless your real code is more generic and transforms multiple methods. For simplicity's sake I removed it from my version of your sample code.

I would also recommend to put the Java agent + class file transformer into a separate agent JAR, even though my refactored version also works if everything is in one JAR.

Please also be careful to not manually call retransformClasses on classes which might have been transformed already, e.g. during class-loading.

Class file transformer

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&lt;?&gt; classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
    return transformClass(className, classfileBuffer);
  }

  private byte[] transformClass(String className, byte[] buffer) {
    switch (className) {
      case &quot;mypackage/MyApplication&quot;:
        return transformMyApplication(className, buffer);
      case &quot;sun/java2d/SunGraphics2D&quot;:
        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(&quot;/&quot;, &quot;.&quot;);
    cp.insertClassPath(new ByteArrayClassPath(name, buffer));
    try {
      CtClass clazz = cp.get(name);
      clazz.defrost();
      CtBehavior[] declaredBehaviors = clazz.getDeclaredBehaviors();
      for (CtBehavior db : declaredBehaviors) {
        if (&quot;hi&quot;.equals(db.getName())) {
          System.out.println(&quot;Transforming hi...&quot;);
          db.insertBefore(&quot;System.out.println(\&quot;Hi!\&quot;);&quot;);
        }
      }

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

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

Java agent:

Please note how MyAgent.instrumentation is used in order to make the information that the agent was attached publicly accessible. We will see this feature being used later.

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(&quot;premain - start&quot;);
    instrumentation = inst;
    inst.addTransformer(new MyTransformer(), true);
    System.out.println(&quot;premain - end&quot;);
  }

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

Main class doing on-demand hot-attachment:

As you can see, I use MyAgent.instrumentation in order to auto-detect if the agent was already attached or not. So there is no more need to use a command line parameter for it.

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 = &quot;build/libs/SO_Javassist_SystemOutRecognisedAsClass_64340794-1.0-SNAPSHOT.jar&quot;;

  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(&quot;Dynamically attaching Java agent - start&quot;);
    String jvmName = ManagementFactory.getRuntimeMXBean().getName();
    String pid = jvmName.substring(0, jvmName.indexOf(&#39;@&#39;));

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

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

Sample target class:

This is just a sample class. In this case it contains the hi method you want to transform.

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(&quot;System&quot;)
    );
    graphics2D.drawRenderedImage(
      null,
      new AffineTransform() {
        @Override
        public void setToScale(double sx, double sy) {
          super.setToScale(sx, sy);
          System.out.println(&quot;scaled!&quot;);
        }
      }
    );
    jFrame.setVisible(true);
  }

  public int hi() {
    return 3;
  }
}

You also want to update the manifest file generator:

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

Now everything runs as expected also on Java 11+. There is no more system class path issue in Javassist because you no longer try to transform the agent itself while it is being hot-attached in the Attach Listener thread belonging to the system thread group.

XXX&gt; 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&gt; 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!

huangapple
  • 本文由 发表于 2020年10月14日 02:09:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/64340794.html
匿名

发表评论

匿名网友

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

确定