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?
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:
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).
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.