为什么这段简单的代码会输出损坏的类文件?

时间:2018-10-15 20:24:51

标签: java java-bytecode-asm bytecode-manipulation

ClassReader classReader = new ClassReader(new FileInputStream(new File("input.class")));
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
Files.write(Paths.get("output.class"), classWriter.toByteArray());

反编译output.class时得到

package corrupted_class_files;

input.class很好,我可以使用ClassReader来阅读说明,但是我无法保存课程

1 个答案:

答案 0 :(得分:2)

您的代码缺少将类功能从源实际复制到目标的步骤:

try(FileInputStream in = new FileInputStream(new File("input.class")) {
    ClassReader classReader = new ClassReader(in);
    ClassWriter classWriter = new ClassWriter(classReader, 0);
    classReader.accept(classWriter, 0);
    Files.write(Paths.get("output.class"), classWriter.toByteArray());
}

ClassReader传递给ClassWriter的构造函数不会复制功能,而是会启用优化(如果您的转换保留了大多数原始类文件)。或者,如the documentation of ClassWriter(ClassReader classReader, int flags)所述:

  

构造一个新的ClassWriter对象,并为“主要添加”字节码转换启用优化。这些优化如下:

     
      
  • 将原始类中的常量池和引导程序方法照原样复制到新类中,这样可以节省时间。如有必要,将在末尾添加新的常量池条目和新的引导程序方法,但是不会删除未使用的常量池条目或引导程序方法
  •   
  • 未转换的方法直接从原始类的字节码中复制(即在新类中照原样复制(即,不发出所有方法指令的访问事件)),从而节省了 lot 的时间。通过以下事实检测未转换的方法:ClassReader接收来自ClassWriter(而不是来自任何其他ClassVisitor实例)的MethodVisitor对象。
  •   

因此,当您在ClassWriter方法中将ClassReader直接链接到accept时,所有方法访问者都将来自编写器,因此,所有访问者都将直接复制。 / p>

当您要显着更改类或构造新类时,应改用构造函数ClassWriter(int flags)

请注意,COMPUTE_FRAMES已经暗示COMPUTE_MAXS。在上面的示例中,我没有指定任何方法,因为无论如何都复制了方法。当您实际上要更改或添加代码并需要COMPUTE_FRAMES时,值得向读者指定SKIP_FRAMES,因为无论如何从头开始重新计算原始帧,解码它们都没有意义。

因此典型的转换设置如下:

public class MyClassVisitor extends ClassVisitor {

    public MyClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
                                     String signature, String[] exceptions) {
        MethodVisitor visitor = super.visitMethod(
            access, name, desc, signature, exceptions);
        if(method matches criteria) {
            visitor = new MyMethodVisitorAdapter(visitor);
        }
        return visitor;
    }
}
try(FileInputStream in = new FileInputStream(new File("input.class"))) {
    ClassReader classReader = new ClassReader(in);
    ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES);
    classReader.accept(new MyClassVisitor(classWriter), ClassReader.SKIP_FRAMES);
    Files.write(Paths.get("output.class"), classWriter.toByteArray());
}

在通过构造函数链接访问者时,您未重写的每个方法都会委托给链接的访问者,并在最终目标为ClassWriter时复制原始构造。 MethodVisitor提供的ClassWriter。如果该方法不满足您的转换条件,那么您返回原始的MethodVisitor,则上述优化仍然适用。方法访问者遵循与类访问者相同的模式,覆盖您要拦截的那些方法。

顺便说一句,您应该避免混合使用旧的I / O和NIO。您的代码的简化变体看起来像

ClassReader classReader = new ClassReader(Files.readAllBytes(Paths.get("input.class")));
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES);
classReader.accept(new MyClassVisitor(classWriter), ClassReader.SKIP_FRAMES);
Files.write(Paths.get("output.class"), classWriter.toByteArray());

注意读写之间的对称性

但是,当您使用getResource等时,可能会被迫处理InputStream。但是对于通过系统类加载器可访问的类,您也可以仅将类名称传递给ClassReader(String)构造函数。