尝试重新定义 sun.reflect.GeneratedMethodAccessor1 时 ByteBuddy 失败
ByteBuddy fails when trying to redefine sun.reflect.GeneratedMethodAccessor1
出于好奇,我尝试导出GeneratedMethodAccessor1的字节码(JVM使用反射生成)
我尝试通过以下方式获取 class 的字节码:
public class MethodExtractor {
public static void main(String[] args) throws Exception {
ExampleClass example = new ExampleClass();
Method exampleMethod = ExampleClass.class
.getDeclaredMethod("exampleMethod");
exampleMethod.setAccessible(true);
int rndSum = 0;
for (int i = 0; i < 20; i++) {
rndSum += (Integer) exampleMethod.invoke(example);
}
Field field = Method.class.getDeclaredField("methodAccessor");
field.setAccessible(true);
Object methodAccessor = field.get(exampleMethod);
Field delegate = methodAccessor.getClass().getDeclaredField("delegate");
delegate.setAccessible(true);
Object gma = delegate.get(methodAccessor);
ByteBuddyAgent.installOnOpenJDK();
try {
ClassFileLocator classFileLocator = ClassFileLocator.AgentBased
.fromInstalledAgent(gma.getClass().getClassLoader());
Unloaded<? extends Object> unloaded = new ByteBuddy().redefine(
gma.getClass(), classFileLocator).make();
Map<TypeDescription, File> saved = unloaded.saveIn(Files
.createTempDirectory("javaproxy").toFile());
saved.forEach((t, u) -> System.out.println(u.getAbsolutePath()));
} catch (IOException e) {
throw new RuntimeException("Failed to save class to file");
}
}
}
但是我在执行此 class 时收到以下错误:
Exception in thread "main" java.lang.NullPointerException
at net.bytebuddy.dynamic.scaffold.TypeWriter$Engine$ForRedefinition.create(TypeWriter.java:172)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:1182)
at net.bytebuddy.dynamic.scaffold.inline.InlineDynamicTypeBuilder.make(InlineDynamicTypeBuilder.java:244)
at reegnz.dyna.proxy.extractor.MethodExtractor.main(MethodExtractor.java:48)
基本上,我首先在方法调用上迭代足够的次数,以便 JVM 扩展方法(生成 GeneratedMethodAccessor),然后尝试重新定义 class 以获取字节码。
我尝试了相同的方法来导出生成的代理 class,并且效果完美。这就是促使我尝试这个的原因。
当我尝试使用 loadClass 方法加载 class 时,GeneratedMethodAccessor1 class 的 DelegatingClassLoader 似乎甚至无法重新加载 class。
关于如何检索 GeneratedMethodAccessor classes 的字节码的任何想法?
首先,NullPointerException
是一个错误,我刚刚修复了它。加载程序应该抛出一个 IllegalArgumentException
而不是它,但它从来没有到那么远。感谢您提醒我注意此事。
归根结底,字节好友面临的问题是
gma.getClass().getClassLoader().findClass(gma.getClass().getName());
抛出 ClassNotFoundException
。这是对访问器 class 使用 DelegatingClassLoader
的结果。作为一个有根据的猜测,我认为这个 class 加载器打算将其 classes 与外部屏蔽,以便使它们易于垃圾收集。但是,不允许查找 class 多少会破坏 ClassLoader
的约定。除此之外,我假设这个加载例程将被重构为在将来的某个时候使用 JDK 的匿名 class 加载程序(类似于代表 lambda 表达式的 classes)。奇怪的是,它看起来像 the source code for the DelegatingClassLoader
is not available in the JDK,尽管我可以在发行版中找到它。可能是 VM 在某些地方对这些 loader 进行了特殊处理。
目前,您可以使用以下 ClassFileTransformer
,它在 class 加载器上使用一些反射魔法来定位加载的 class,然后提取字节数组。 (ClassFileLocator
接口只需要一个名称而不是加载的 class 以便允许使用通常总是这种情况的卸载类型。不知道为什么这在这种情况下不起作用。)
class DelegateExtractor extends ClassFileLocator.AgentBased {
private final ClassLoader classLoader;
private final Instrumentation instrumentation;
public DelegateExtractor(ClassLoader classLoader, Instrumentation instrumentation) {
super(classLoader, instrumentation);
this.classLoader = classLoader;
this.instrumentation = instrumentation;
}
@Override
public Resolution locate(String typeName) {
try {
ExtractionClassFileTransformer classFileTransformer =
new ExtractionClassFileTransformer(classLoader, typeName);
try {
instrumentation.addTransformer(classFileTransformer, true);
// Start nasty hack
Field field = ClassLoader.class.getDeclaredField("classes");
field.setAccessible(true);
instrumentation.retransformClasses(
(Class<?>) ((Vector<?>) field.get(classLoader)).get(0));
// End nasty hack
byte[] binaryRepresentation = classFileTransformer.getBinaryRepresentation();
return binaryRepresentation == null
? Resolution.Illegal.INSTANCE
: new Resolution.Explicit(binaryRepresentation);
} finally {
instrumentation.removeTransformer(classFileTransformer);
}
} catch (Exception ignored) {
return Resolution.Illegal.INSTANCE;
}
}
}
为了进一步简化您的代码,您可以直接使用 ClassFileLocator
s 而不是应用重写,事实上,即使您不应用任何重写,也可能会稍微修改 class 文件更改为 class.
出于好奇,我尝试导出GeneratedMethodAccessor1的字节码(JVM使用反射生成)
我尝试通过以下方式获取 class 的字节码:
public class MethodExtractor {
public static void main(String[] args) throws Exception {
ExampleClass example = new ExampleClass();
Method exampleMethod = ExampleClass.class
.getDeclaredMethod("exampleMethod");
exampleMethod.setAccessible(true);
int rndSum = 0;
for (int i = 0; i < 20; i++) {
rndSum += (Integer) exampleMethod.invoke(example);
}
Field field = Method.class.getDeclaredField("methodAccessor");
field.setAccessible(true);
Object methodAccessor = field.get(exampleMethod);
Field delegate = methodAccessor.getClass().getDeclaredField("delegate");
delegate.setAccessible(true);
Object gma = delegate.get(methodAccessor);
ByteBuddyAgent.installOnOpenJDK();
try {
ClassFileLocator classFileLocator = ClassFileLocator.AgentBased
.fromInstalledAgent(gma.getClass().getClassLoader());
Unloaded<? extends Object> unloaded = new ByteBuddy().redefine(
gma.getClass(), classFileLocator).make();
Map<TypeDescription, File> saved = unloaded.saveIn(Files
.createTempDirectory("javaproxy").toFile());
saved.forEach((t, u) -> System.out.println(u.getAbsolutePath()));
} catch (IOException e) {
throw new RuntimeException("Failed to save class to file");
}
}
}
但是我在执行此 class 时收到以下错误:
Exception in thread "main" java.lang.NullPointerException
at net.bytebuddy.dynamic.scaffold.TypeWriter$Engine$ForRedefinition.create(TypeWriter.java:172)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:1182)
at net.bytebuddy.dynamic.scaffold.inline.InlineDynamicTypeBuilder.make(InlineDynamicTypeBuilder.java:244)
at reegnz.dyna.proxy.extractor.MethodExtractor.main(MethodExtractor.java:48)
基本上,我首先在方法调用上迭代足够的次数,以便 JVM 扩展方法(生成 GeneratedMethodAccessor),然后尝试重新定义 class 以获取字节码。
我尝试了相同的方法来导出生成的代理 class,并且效果完美。这就是促使我尝试这个的原因。
当我尝试使用 loadClass 方法加载 class 时,GeneratedMethodAccessor1 class 的 DelegatingClassLoader 似乎甚至无法重新加载 class。
关于如何检索 GeneratedMethodAccessor classes 的字节码的任何想法?
首先,NullPointerException
是一个错误,我刚刚修复了它。加载程序应该抛出一个 IllegalArgumentException
而不是它,但它从来没有到那么远。感谢您提醒我注意此事。
归根结底,字节好友面临的问题是
gma.getClass().getClassLoader().findClass(gma.getClass().getName());
抛出 ClassNotFoundException
。这是对访问器 class 使用 DelegatingClassLoader
的结果。作为一个有根据的猜测,我认为这个 class 加载器打算将其 classes 与外部屏蔽,以便使它们易于垃圾收集。但是,不允许查找 class 多少会破坏 ClassLoader
的约定。除此之外,我假设这个加载例程将被重构为在将来的某个时候使用 JDK 的匿名 class 加载程序(类似于代表 lambda 表达式的 classes)。奇怪的是,它看起来像 the source code for the DelegatingClassLoader
is not available in the JDK,尽管我可以在发行版中找到它。可能是 VM 在某些地方对这些 loader 进行了特殊处理。
目前,您可以使用以下 ClassFileTransformer
,它在 class 加载器上使用一些反射魔法来定位加载的 class,然后提取字节数组。 (ClassFileLocator
接口只需要一个名称而不是加载的 class 以便允许使用通常总是这种情况的卸载类型。不知道为什么这在这种情况下不起作用。)
class DelegateExtractor extends ClassFileLocator.AgentBased {
private final ClassLoader classLoader;
private final Instrumentation instrumentation;
public DelegateExtractor(ClassLoader classLoader, Instrumentation instrumentation) {
super(classLoader, instrumentation);
this.classLoader = classLoader;
this.instrumentation = instrumentation;
}
@Override
public Resolution locate(String typeName) {
try {
ExtractionClassFileTransformer classFileTransformer =
new ExtractionClassFileTransformer(classLoader, typeName);
try {
instrumentation.addTransformer(classFileTransformer, true);
// Start nasty hack
Field field = ClassLoader.class.getDeclaredField("classes");
field.setAccessible(true);
instrumentation.retransformClasses(
(Class<?>) ((Vector<?>) field.get(classLoader)).get(0));
// End nasty hack
byte[] binaryRepresentation = classFileTransformer.getBinaryRepresentation();
return binaryRepresentation == null
? Resolution.Illegal.INSTANCE
: new Resolution.Explicit(binaryRepresentation);
} finally {
instrumentation.removeTransformer(classFileTransformer);
}
} catch (Exception ignored) {
return Resolution.Illegal.INSTANCE;
}
}
}
为了进一步简化您的代码,您可以直接使用 ClassFileLocator
s 而不是应用重写,事实上,即使您不应用任何重写,也可能会稍微修改 class 文件更改为 class.