Friday, 5 February 2016

Java Class Viewer

Introduction

Java Class File is one of the key reasons that Java can run on various platforms. The Java class file is designed as a byte stream, with a specific structure as described in the JVM Spec, chapter 4.
Well, it is not easy for a beginner to understand the VM Spec; Java Class Viewer is a visual yet powerful application which can show the meaning of each byte of the class file.
Java Class Viewer - Main Window

Background

You can ignore this "Background" section if you are not interested in the history of Java Class Viewer.

1. The Reason for Java Class Viewer

Years ago, I was asked to write a plug-in which can hook the Java applications. The principle of the plug-in was simple: Find the object/class at run time, and try to change its behavior. At that time, I had to read the class file byte by byte via the binary file reader like Notepad++. It is really not interesting at all; it is boring.
So, I decided to write a Java Class Viewer, which can display the class file visually, and it can display the meaning of each byte of the class file. This is the reason for creating the Java Class Viewer application.
In the very beginning (September, 2007), Java Class Viewer was a command line tool. And one and a half years later (I am lazy enough...), the graphical version of this application was created.

2. Java Class Format library

When creating the graphical application, I noticed that this application can be divided into two parts:
  1. Java Class File library. It parses the class byte array file, provides various helper classes which can help to get the information of the parsed class. It may provide more developer friendly information that the Official JDK javac error message if the class file has any problem. For example: You can easily write a program to list all the fields and methods in a .class file using this library.
  2. Java Class Viewer. It is a swing window application, uses the Java Class Format library, to give a graphical view of the class file.

Related Libraries

There is some other library available for class file manufacturing and verifying, like the Byte Code Engineering Library (BCEL) from Apache. The design principle of Java Class File library is quite different from BCEL. BCEL is a powerful library for edit/change Java class file, and there is also a class file verifier available in BCEL.
Well, it is (almost) impossible to use BCEL to write a Class File Viewer which can show each byte's meaning, since it does not record the location when parsing the class file; BCEL source code class and method naming convention is not following the JVM Spec Class File Structure definition strictly, so not easy to understand for beginners.
Well Java Class File library records the offset of the class file when parsing; the library follows theClassFile structure in JVM Spec strictly.
If you are running Java Class Viewer, while reading the JVM Spec, it will be much easier for a beginner to understand the Class file.

The Class File Format

The Class file has the following structure:
ClassFile {
        u4 magic;
        u2 minor_version;
        u2 major_version;
        u2 constant_pool_count;
        cp_info constant_pool[constant_pool_count-1];
        u2 access_flags;
        u2 this_class;
        u2 super_class;
        u2 interfaces_count;
        u2 interfaces[interfaces_count];
        u2 fields_count;
        field_info fields[fields_count];
        u2 methods_count;
        method_info methods[methods_count];
        u2 attributes_count;
        attribute_info attributes[attributes_count];
} 
You may want to view the section The class File Format in the JVM Spec for a detailed description for the ClassFile structure. Here is a brief description:
magic - 0xCAFEBABE, the magic number of class file. If the first 4 bytes are not 0xCAFEBABE, it is not recognized as a class file.
minor_version, major_version - The major version and minor version determine the class file version together.
constant_pool_count, cp_info constant_pool[constant_pool_count-1] - Constant pool of the class file. The constant pool may contain eleven types of constants:
01. class/interface info
02. field reference info
03. method reference info
04. interface method reference info
05. String 
06. Integer
07. Float
08. Long
09. Double
10. NameAndType
11. Utf8 
access_flags - The access flag of the class
this_classsuper_class - The class info of current class and super class. Only thejava.lang.Object class's super class is null; if the super class is not specified for this class, the super class is java.lang.Object.
interfaces_count, interfaces[interfaces_count] - The direct super interfaces.
fields_count, field_info fields[fields_count] - The fields of this class, if there are any.
methods_count, method_info methods[methods_count] - The methods of this class. The Javacompiler will generate a default constructor for a class (inner class excluded) if there is none. So, there will be at least one method in this class.
attributes_count, attribute_info attributes[attributes_count] - The attribute of this class. There is at least one attribute named "SourceFile" for the file name of the source code.

Parse a Class File using Java Class File Library

1. Parse the Class File

The class org.freeinternals.format.classfile.ClassFile is the parser of class file. It accepts a byte array as input parameter; the byte array contains the class file. The byte array may come from a.class file, a .jar file, a .war file, etc. Or alternatively, the byte array may be built by libraries like BCEL.
// ArticleCodeDemo.src.zip - org.freeinternals.demo.jCFL_CodeDemo.extractClassFile()
File file = new File("C:/Temp/File.class");
byte[] classByteArray = Tool.readClassFile(file);
ClassFile classfile = new ClassFile(classByteArray);
// ArticleCodeDemo.src.zip - org.freeinternals.demo.jCFL_CodeDemo.extractJarFile()
File file = new File("C:/Temp/tools.jar");
JarFile jarFile = new JarFile(file, false, JarFile.OPEN_READ);
ZipFile zipFile = jarFile;
 final Enumeration zipEntries = zipFile.entries();
while (zipEntries.hasMoreElements()) {
    ZipEntry zipEntry = (ZipEntry) zipEntries.nextElement();
    if (!zipEntry.getName().endsWith(".class")) {
        continue;
    }
     byte[] classByteArray = Tool.readClassFile(zipFile, zipEntry);
    ClassFile classfile = new ClassFile(classByteArray);
     System.out.println();
    System.out.println(zipEntry.getName());
    jCFL_CodeDemo.printClassFile(classfile);
}
There is a tool class org.freeinternals.javaclassviewer.ui.Tool which can help us to read from a file or a zip file. We know that .jar and .war files are both in fact in zip format.
The constructor of ClassFile throws anorg.freeinternals.format.classfile.ClassFormatException if the byte array is not a valid class file, or a java.io.IOException if there is some error in IO. You may surround the statement with try...catch block or add a throws clause in the method declaration.

2. Get the Information of the Class File

Once we get the ClassFile instance successfully, we can get the information of the class file via thegetXxxxx methods.
Here is an example to print out all of the component information of the class file:
// ArticleCodeDemo.src.zip - org.freeinternals.demo.jCFL_CodeDemo.printClassFile()
 // Minor & Major version
MinorVersion minorVersion = classfile.getMinorVersion();
System.out.println("Class File Minor Version: " + minorVersion.getValue());
 MajorVersion majorVersion = classfile.getMajorVersion();
System.out.println("Class File Major Version: " + majorVersion.getValue());
 // Constant Pool
CPCount cpCount = classfile.getCPCount();
System.out.println("Constant Pool size: " + cpCount.getValue());
 AbstractCPInfo[] cpArray = classfile.getConstantPool();
for (int i = 1; i < cpCount.getValue(); i++) {
    System.out.println(
            String.format("Constant Pool [%d]: %s", i, classfile.getCPDescription(i)));
    short tag = cpArray[i].getTag();
    if ((tag == AbstractCPInfo.CONSTANT_Double) || 
            (tag == AbstractCPInfo.CONSTANT_Long)) {
        i++;
    }
}
 // Access flag, this & super class
AccessFlags accessFlags = classfile.getAccessFlags();
System.out.println("Class Modifier: " + accessFlags.getModifiers());
 ThisClass thisClass = classfile.getThisClass();
System.out.println("This Class Name Index: " + thisClass.getValue());
System.out.println("This Class Name: " + 
    classfile.getCPDescription(thisClass.getValue()));
 SuperClass superClass = classfile.getSuperClass();
System.out.println("Super Class Name Index: " + superClass.getValue());
if (superClass.getValue() == 0) {
    System.out.println("Super Class Name: java.lang.Object");
} else {
    System.out.println("Super Class Name: " + 
        classfile.getCPDescription(superClass.getValue()));
}
 // Interfaces
InterfaceCount interfactCount = classfile.getInterfacesCount();
System.out.println("Interface Count: " + interfactCount.getValue());
 if (interfactCount.getValue() > 0) {
    Interface[] interfaceArray = classfile.getInterfaces();
    for (int i = 0; i < interfaceArray.length; i++) {
        System.out.println(
                String.format("Interface [%d] Name Index: %d", i, 
                interfaceArray[i].getValue()));
        System.out.println(
                String.format("Interface [%d] Name: %s", i, 
        classfile.getCPDescription(interfaceArray[i].getValue())));
    }
}
 // Fields
FieldCount fieldCount = classfile.getFieldCount();
System.out.println("Field count: " + fieldCount.getValue());
 if (fieldCount.getValue() > 0) {
    FieldInfo[] fieldArray = classfile.getFields();
    for (int i = 0; i < fieldArray.length; i++) {
        System.out.println(String.format("Field [%d]: %s", i, 
                fieldArray[i].getDeclaration()));
    }
}
 // Methods
MethodCount methodCount = classfile.getMethodCount();
System.out.println("Method count: " + methodCount.getValue());
 if (methodCount.getValue() > 0) {
    MethodInfo[] methodArray = classfile.getMethods();
    for (int i = 0; i < methodArray.length; i++) {
        System.out.println(String.format("Method [%d]: %s", i, 
                methodArray[i].getDeclaration()));
    }
}
 // Attributes
AttributeCount attributeCount = classfile.getAttributeCount();
System.out.println("Attribute count: " + attributeCount.getValue());
 AttributeInfo[] attributeArray = classfile.getAttributes();
for (int i = 0; i < attributeArray.length; i++) {
    System.out.println(String.format("Attribute [%d]: %s", i, 
                attributeArray[i].getName()));
}
Here are some special notes for the code above.
  • Constant Pool: The constant pool array is from index 1 to (constant_pool_count-1); and theCONSTANT_Long_info and CONSTANT_Double_info will take two indexes position while all other types will take only one position.
  • Super Class: The super class index will be zero only when the current class isjava.lang.Object. Otherwise, it should be an item in the constant pool.
  • Interfaces & Fields: One class may have no interfaces or fields, so we need to check theInterfaceCount and FieldCount variable before getting the array for interface/field.
  • Methods: For a non-inner class, one class must have at least one method, which is the default instance constructor created by javac; but for inner class, there can be no method. So we should check the MethodCount variable is not zero.
  • Attributes: One class must have at least one attribute, the SourceFile attribute; we don't have to add similar logic for it.
By using some code just like the above, it is not hard work to write a visual UI control for a class file. And it will be very easy for us to write any application to analyse the meta data in the class.

Add the Parsed Class File to the Swing Control

1. A Tree Control for the Class File Component Hierarchy

The class org.freeinternals.javaclassviewer.ui.JTreeClassFile is a subclass of JTree, which accepts a ClassFile object in the constructor. It will add all components of a class file into the tree control.

2. A Split Container for the Class File Interactive Binary Viewer

The class org.freeinternals.javaclassviewer.ui.JSplitPaneClassFile is a subclass ofJSplitPane, which is divided into two panels: the left panel is the JTreeClassFile, while the right panel is a binary viewer for class file.
While we select each component in the tree, the corresponding bytes will be highlighted; this is the reason for the word "interactive".
// JavaClassViewer.src.zip - org.freeinternals.javaclassviewer.Main.open_ClassFile()
private JSplitPaneClassFile cfPane;

private void open_ClassFile(final File file) {
    this.cfPane = new JSplitPaneClassFile(Tool.readClassFile(file));
    this.add(this.cfPane, BorderLayout.CENTER);
    this.resizeForContent();
}
For example, after opening the class file File.class (java.io.File), when we select the node for the method getName(), the corresponding bytes will be highlighted.
If we select the name_indexdescriptor_index node for this method, only the index section (for the value 119, 24) will be highlighted.
If we select the code node in the Code attribute, and open the Opcode tab, the opcodes of this method are extracted.
Java Class Viewer is not a decompiler, it displays the raw code and some comments according to the context. You may refer to the JVM Spec for the meaning of the opcode. The extracted opcode is partly human readable.

3. A Tree Control for the ZipFile (jar, war, etc.)

The class org.freeinternals.javaclassviewer.ui.JTreeZipFile is a subclass of JTree, which accepts a ZipFile in the constructor. It will build a tree for all entries in the zip file. The.jar/.war file is in fact a zip file.
// JavaClassViewer.src.zip - org.freeinternals.javaclassviewer.Main.open_JarFile()
// Only key logic is left here

private JTreeZipFile zftree;

private void open_JarFile(final File file) {
    this.zftree = new JTreeZipFile(new JarFile(file, false, JarFile.OPEN_READ));
    this.zftreeContainer = new JPanelForTree(this.zftree);
    this.add(this.zftreeContainer, BorderLayout.CENTER);
    this.resizeForContent();
}
Here is a screen shot of the ZipFile tree control.
BTW, if we double click an xxxxx.class node, a new window will be opened for the class file.

Build a Java Class Viewer

Based on the available controls above, it is very easy to write a class file viewer. We just need to add menu/toolbar, and layout the controls onto a JFrame.
You may need to refer to the source code at the top of this article for details.
The class file viewer is a good starting point to understand Java. We may use it as a tool to study the new feathers provided by new versions of Java in JVM .class file level.

No comments:

Post a Comment