如何解决类重新转换不适用于Java 11上的动态代理
似乎类的动态重新转换仅对Java 8有效,而对Java 11不起作用。在后一种情况下,我从javassist中获得了关于未找到的不同标准java类的异常,例如,我直接引用的类。甚至是要转换的方法的签名。
我该怎么做才能在Java 11上解决该问题?我也想在这里动态地转换类。
出于说明目的,我创建了一个repro文件。在这里,我重新转换了两个类:一个是我自己的,另一个是系统。我已经创建了agentmain
和premain
进行比较。当将主参数传递给应用程序时执行动态变体(我将其作为“ 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 举报,一经查实,本站将立刻删除。