尝试重新定义sun.reflect.GeneratedMethodAccessor1时,ByteBuddy失败 [英] ByteBuddy fails when trying to redefine sun.reflect.GeneratedMethodAccessor1
问题描述
在好奇心的驱使下,我试图导出GeneratedMethodAccessor1的字节码(在使用反射时由JVM生成)。
我试图获取类的字节码以下方式:
public class MethodExtractor {
public static void main(String [] args)throws异常{
ExampleClass example = new ExampleClass();
方法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());
已卸载<? 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){
抛出新的RuntimeException(无法将类保存到文件中);
}
}
}
但是我收到以下错误执行此类时:
线程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),然后尝试重新定义类以获取字节码。
我尝试使用相同的方法导出生成的Proxy类,并且它运行完美。这就是驱使我尝试这个的原因。
当我尝试使用loadClass方法加载类时,似乎GeneratedMethodAccessor1类的DelegatingClassLoader甚至无法重新加载类。
我是如何检索GeneratedMethodAccessor类的字节码的?
首先, NullPointerException
是一个错误,我刚刚解决了这个问题。加载器应该抛出一个 IllegalArgumentException
,但它从来没有这么远。感谢您引起我的注意。
简而言之,Byte Buddy面临的问题是
gma.getClass()getClassLoader()的findClass(gma.getClass()的getName())。。
抛出 ClassNotFoundException
。这是使用 DelegatingClassLoader
作为访问者类的结果。作为一个有根据的猜测,我认为这个类加载器打算从外部屏蔽它的类,以使它们容易被垃圾收集。但是,不允许查找类有点违反 ClassLoader
的合同。除此之外,我假设这个加载例程将被重构为在将来的某个时候使用JDK的匿名类加载器(类似于表示lambda表达式的类)。奇怪的是,它似乎是 JDK 中没有 DelegatingClassLoader
的源代码,即使我可以在发行版中找到它。可能VM特别在某个地方处理这些加载器。
现在,您可以使用以下 ClassFileTransformer
在类加载器上使用一些反射魔法来定位加载的类,然后提取字节数组。 ( ClassFileLocator
接口只接受一个名称而不是一个加载的类,以便允许使用卸载的类型,这通常是这种情况。不知道为什么这不起作用case。)
class DelegateExtractor扩展ClassFileLocator.AgentBased {
private final ClassLoader classLoader;
私人最终仪器仪表;
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);
//开始讨厌的黑客
Field field = ClassLoader.class.getDeclaredField(classes);
field.setAccessible(true);
instrumentation.retransformClasses(
(Class<?>)((Vector<?>)field.get(classLoader))。get(0));
//结束令人讨厌的黑客
byte [] binaryRepresentation = classFileTransformer.getBinaryRepresentation();
返回binaryRepresentation == null
? Resolution.Illegal.INSTANCE
:new Resolution.Explicit(binaryRepresentation);
} finally {
instrumentation.removeTransformer(classFileTransformer);
}
} catch(忽略异常){
返回Resolution.Illegal.INSTANCE;
}
}
}
为了进一步简化代码,您可以直接使用 ClassFileLocator
,而不是应用重写,即使您没有对类应用任何更改,也可能稍微修改类文件。 / p>
Driven by curiosity, I tried to export the bytecode of GeneratedMethodAccessor1 (generated by the JVM when using reflection).
I try to get the bytecode of the class the following way:
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");
}
}
}
I however get the following error when executing this 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)
Basically I first iterate on the method call enough times for the JVM to inflate the method (generate the GeneratedMethodAccessor) and then try to redefine the class to get the bytecode.
I tried the same method to export a generated Proxy class, and it worked flawlessly. That's what drove me to try this.
It seems that the DelegatingClassLoader of the GeneratedMethodAccessor1 class can't even reload the class when I try to load the class with the loadClass method.
Any ideas how I could retrieve the bytecode for GeneratedMethodAccessor classes?
First of all, the NullPointerException
is a bug, I just fixed that. The loader should have thrown an IllegalArgumentException
instead but it never got that far. Thanks for bringing this to my attention.
Boiled down, the problem Byte Buddy is facing is that
gma.getClass().getClassLoader().findClass(gma.getClass().getName());
throws a ClassNotFoundException
. This is a consequence of using a DelegatingClassLoader
for the accessor classes. As an educated guess, I think that this class loader intends to shield its classes from the outside in order to make them easily garbage collectable. However, not allowing the lookup of a class what somewhat breaks the contract for a ClassLoader
. Apart from that, I assume that this loading routine will be refactored to use the JDK's anonymous class loaders at some point in the future (similar to classes representing lambda expressions). Strangely enough, it seems like the source code for the DelegatingClassLoader
is not available in the JDK even though I can find it in the distribution. Probably, the VM treats these loader specially at some place.
For now, you can use the following ClassFileTransformer
which uses some reflection magic on the class loader to locate the loaded class and to then extract the byte array. (The ClassFileLocator
interface only takes a name instead of a loaded class in order to allow working with unloaded types which is normally always the case. No idea why this does not work in this case.)
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;
}
}
}
To further simplify your code, you can use the ClassFileLocator
s directly instead of applying a rewrite which as a matter of fact might slightly modify the class file even if you do not apply any changes to a class.
这篇关于尝试重新定义sun.reflect.GeneratedMethodAccessor1时,ByteBuddy失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!