Search code examples
javajava-bytecode-asm

ASMifier doesn't display enough


I am trying to use ASMifier to understand exactly what is in my .class files. The problem is that the tool omits certain mv.visitXXX. For instance, it doesn't display mv.visitLabel(Label) for line-declaring labels.

It makes sense because ASMifier displays only the mv.visit needed to create the class but I would like to use it in a different way.

Is there anyway to ask the tool to display everything that it visits?


Solution

  • There is no conditional treatment in ASMifier for labels associated with source code lines, in other words, if the ClassReader reports them, ASMifier will show them.

    For this to work, two conditions must be met:

    1. The method must have a LineNumberTable attribute associated with its Code attribute (for ordinary classes, this is driven by the compiler options used when generating the .class file).

    2. You must not have specified SKIP_DEBUG to the accept method of the ClassReader

    When using javac, the presence or absence of debug information is controlled by the -g option. The default is to generate LineNumberTable attributes, so not specifying the -g option is already sufficient. So the following program

    package bytecodetests;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    import org.objectweb.asm.ClassReader;
    import org.objectweb.asm.util.ASMifier;
    import org.objectweb.asm.util.TraceClassVisitor;
    
    public class BytecodeTests {
    
      public static void main(String[] args) throws IOException {
        new ClassReader(BytecodeTests.class.getResourceAsStream("BytecodeTests.class"))
          .accept(new TraceClassVisitor(null, new ASMifier(), new PrintWriter(System.out)), 0);
      }
    
    }
    

    will produce

    package asm.bytecodetests;
    import java.util.*;
    import org.objectweb.asm.*;
    public class BytecodeTestsDump implements Opcodes {
    
    public static byte[] dump () throws Exception {
    
    ClassWriter cw = new ClassWriter(0);
    FieldVisitor fv;
    MethodVisitor mv;
    AnnotationVisitor av0;
    
    cw.visit(52, ACC_PUBLIC + ACC_SUPER, "bytecodetests/BytecodeTests", null, "java/lang/Object", null);
    
    cw.visitSource("BytecodeTests.java", null);
    
    {
    mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
    mv.visitCode();
    Label l0 = new Label();
    mv.visitLabel(l0);
    mv.visitLineNumber(9, l0);
    mv.visitVarInsn(ALOAD, 0);
    mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
    mv.visitInsn(RETURN);
    Label l1 = new Label();
    mv.visitLabel(l1);
    mv.visitLocalVariable("this", "Lbytecodetests/BytecodeTests;", null, l0, l1, 0);
    mv.visitMaxs(1, 1);
    mv.visitEnd();
    }
    {
    mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, new String[] { "java/io/IOException" });
    mv.visitCode();
    Label l0 = new Label();
    mv.visitLabel(l0);
    mv.visitLineNumber(12, l0);
    mv.visitTypeInsn(NEW, "org/objectweb/asm/ClassReader");
    mv.visitInsn(DUP);
    mv.visitLdcInsn(Type.getType("Lbytecodetests/BytecodeTests;"));
    mv.visitLdcInsn("BytecodeTests.class");
    mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getResourceAsStream", "(Ljava/lang/String;)Ljava/io/InputStream;", false);
    mv.visitMethodInsn(INVOKESPECIAL, "org/objectweb/asm/ClassReader", "<init>", "(Ljava/io/InputStream;)V", false);
    mv.visitTypeInsn(NEW, "org/objectweb/asm/util/TraceClassVisitor");
    mv.visitInsn(DUP);
    mv.visitInsn(ACONST_NULL);
    mv.visitTypeInsn(NEW, "org/objectweb/asm/util/ASMifier");
    mv.visitInsn(DUP);
    mv.visitMethodInsn(INVOKESPECIAL, "org/objectweb/asm/util/ASMifier", "<init>", "()V", false);
    mv.visitTypeInsn(NEW, "java/io/PrintWriter");
    mv.visitInsn(DUP);
    mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
    mv.visitMethodInsn(INVOKESPECIAL, "java/io/PrintWriter", "<init>", "(Ljava/io/OutputStream;)V", false);
    mv.visitMethodInsn(INVOKESPECIAL, "org/objectweb/asm/util/TraceClassVisitor", "<init>", "(Lorg/objectweb/asm/ClassVisitor;Lorg/objectweb/asm/util/Printer;Ljava/io/PrintWriter;)V", false);
    mv.visitInsn(ICONST_0);
    Label l1 = new Label();
    mv.visitLabel(l1);
    mv.visitLineNumber(13, l1);
    mv.visitMethodInsn(INVOKEVIRTUAL, "org/objectweb/asm/ClassReader", "accept", "(Lorg/objectweb/asm/ClassVisitor;I)V", false);
    Label l2 = new Label();
    mv.visitLabel(l2);
    mv.visitLineNumber(14, l2);
    mv.visitInsn(RETURN);
    Label l3 = new Label();
    mv.visitLabel(l3);
    mv.visitLocalVariable("args", "[Ljava/lang/String;", null, l0, l3, 0);
    mv.visitMaxs(8, 1);
    mv.visitEnd();
    }
    cw.visitEnd();
    
    return cw.toByteArray();
    }
    }
    

    Note the presence of the

    Label l0 = new Label();
    mv.visitLabel(l0);
    mv.visitLineNumber(12, l0);
    …
    Label l1 = new Label();
    mv.visitLabel(l1);
    mv.visitLineNumber(13, l1);
    …
    Label l2 = new Label();
    mv.visitLabel(l2);
    mv.visitLineNumber(14, l2);
    …
    

    calls for the main method.


    When running ASMifier from command line, it does indeed default to using the SKIP_DEBUG flag unless the -debug option has been given as first argument.