Take a look inside Java classes

Learn to deduce properties of a Java class from inside a Java program

1 2 Page 2
Page 2 of 2

Ok, so now what?

At this point you might be asking, "What good does this do me?" The answer is "Quite a bit."

If you've compiled up these classes and have them in your class path, the simplest thing you can do is to print them out and have a look.

The ClassFile class defines a method named display for dumping the structure of the class file out to the terminal. I wrote a simple program named dumpclass to show how it is used. The source code to dumpclass is shown below.

import java.io.*;
import java.util.*;
import util.*;
public class dumpclass {
    public static void main(String args[]) {
    try {
        FileInputStream fi = new FileInputStream(args[0]);
            util.ClassFile cf = new util.ClassFile();
        // cf.debug = true;
        // cf.dumpConstants = true;
        if (! cf.read(fi)) {
            System.out.println("Unable to read class file.");
            System.exit(1);
        }
        cf.display(System.out);
    } catch (Exception e) { e.printStackTrace(); }
    }
}

The code above shows how dumpclass easily reads in a named class file and then displays it using the display method. The output of the display is shown below. If you look at the output you will see that generic imports in the source such as import java.io.*; are regenerated with the specific files that the dumpclass code actually imports. If nothing else, using dumpclass on your class files, and cutting and pasting the specific imports in for your generic imports, will save compile time on some compilers. The other interesting thing is that the source code looks like, well, source code. This is because the class file structure contains structural as well as implementation information. You should not use such information to illegally decompile other people's class files.

import java.io.FileInputStream;
import java.io.PrintStream;
import java.lang.Exception;
import java.lang.System;
import java.lang.Throwable;
import util.ClassFile;
/*
 * This class has 1 optional class attributes.
 * These attributes are: 
 * Attribute 1 is of type SourceFile
 *  SourceFile : dumpclass.java
 */
public synchronized class dumpclass extends java.lang.Object {
/* Methods */
    public static void main(java.lang.String a[]);
    public void dumpclass();
}

More interesting to me when I wrote these classes was the optional class file attribute. Since the ClassFile class can write as well as read class files, it is ideal for "adding on" an optional class file attribute.

For those of you who haven't seen the specification on class files, the optional class file attribute is a chunk of opaque data that has a string typename and a chunk of opaque binary data. Sun defines a few well-known attributes (the "SourceFile" attribute shown above is one such attribute), but you can use the attributes to store arbitrarily interesting data. In my secure system prototype I had space reserved in an optional class attribute for a public key signature and a capabilities certificate.

Another interesting application of class file attribute is demonstrated by the SBKTech application Jinstall, which uses an attribute to store the compressed data for its self-extracting archive process. Using these classes and the new ZIP file routines in 1.1 makes it pretty easy to generate this type of application.

Finally, perhaps the most intriguing application of reading and rewriting class files uses attributes and class loaders. Referring back to my article on writing class loaders, and knowing that attributes can be associated with methods, in addition to being generic to the class (and in fact there is an attribute with the method to indicate the exceptions it throws), consider the following application.

Let's say you have a Java class whose method code was stored in an attribute associated with that method and encrypted by a key known only to the author's server. The actual code associated with a method was some Java code that simply threw an UnlicensedUsageException. (Note that this is a fictional exception used to illustrate the design.) Now bundle with an application a custom class loader that was designed to load such a class. This class loader would work in the following way.

First, the code for the class would be read. Then the class would be decomposed into a ClassFile structure. After this, the methods in the class would be checked for encryption. The class loader, once satisfied such a thing was allowed, would contact, via the Internet, the author's server and request a decryption key. That key would be applied to the encrypted code, and the decrypted code would be substituted for the place holder code. The class would be rewritten into a byte stream and then fed into the class loader for loading and execution.

The result of these steps would be a Java class file that was very much more difficult to decompile than a "normal" Java class. Further, since the decryption happens on the fly, only a modified virtual machine could be used to extract the running code (assuming a secure decrypting key exchange).

I had thought about coding an example but realized that such a class loader would no doubt be declared to be a munition and I would be branded an arms dealer. So this description will have to suffice!

Wrapping up and further thoughts

Being able to see inside a Java class can enable a Java application to manipulate that class in useful ways. I've looked at reading and writing class files directly, and then through a custom class loader importing the class into the Java run time. Being able to write classes enables such applications as "self extracting" classes. These are meta classes around a distribution of classes. Another interesting application is the notion of an encrypted class whose contents are self-decrypted just prior to running by accessing a remote key. It all goes to show that we can learn new skills by looking inside ourselves!

Next month we will look at the Reflection API and how it achieves introspection while keeping a rein on security, and I'll show you how I'd write the initial code of the Java interpreter if I had an opportunity to update that code.

Chuck McManis currently is the director of system software at FreeGate Corp., a venture-funded start-up that is exploring opportunities in the Internet marketplace. Before joining FreeGate, Chuck was a member of the Java Group. He joined the Java Group just after the formation of FirstPerson Inc. and was a member of the portable OS group (the group responsible for the OS portion of Java). Later, when FirstPerson was dissolved, he stayed with the group through the development of the alpha and beta versions of the Java platform. He created the first "all Java" home page on the Internet when he did the programming for the Java version of the Sun home page in May 1995. He also developed a cryptographic library for Java and versions of the Java class loader that could screen classes based on digital signatures. Before joining FirstPerson, Chuck worked in the operating systems area of SunSoft, developing networking applications, where he did the initial design of NIS+. Check out his home page.

Learn more about this topic

  • "SBKTech Tools" -- Cool tools that take advantage of class file knowledge. http://www.sbktech.org/
  • Source files for this column:
  • "How to build an interpreter in Java, Part 2The structure"
    The trick to assembling the foundation classes for a simple interpreter. http://www.javaworld.com/javaworld/jw-06-1997/jw-06-indepth.html
  • "How to build an interpreter in Java, Part 1The BASICs"
    For complex applications requiring a scripting language, Java can be used to implement the interpreter, adding scripting abilities to any Java app. http://www.javaworld.com/javaworld/jw-05-1997/jw-05-indepth.html
  • "Lexical analysis, Part 2Build an application"
    How to use the StreamTokenizer object to implement an interactive calculator. http://www.javaworld.com/javaworld/jw-02-1997/jw-02-indepth.html
  • "Lexical analysis and JavaPart 1"
    Learn how to convert human-readable text into machine-readable data using the StringTokenizer and StreamTokenizer classes. http://www.javaworld.com/javaworld/jw-01-1997/jw-01-indepth.html
  • "Code reuse and object-oriented systems"
    Use a helper class to enforce dynamic behavior. http://www.javaworld.com/javaworld/jw-12-1996/jw-12-indepth.html
  • "Container support for objects in Java 1.0.2"
    Organizing objects is easy when you put them into containers. This article walks you through the design and implementation of a container. http://www.javaworld.com/javaworld/jw-11-1996/jw-11-indepth.html
  • "The basics of Java class loaders"
    The fundamentals of this key component of the Java architecture. http://www.javaworld.com/javaworld/jw-10-1996/jw-10-indepth.html
  • "Not using garbage collection"
    Minimize heap thrashing in your Java programs. http://www.javaworld.com/javaworld/jw-09-1996/jw-09-indepth.html
  • "Threads and applets and visual controls"
    This final part of the series explores reading multiple data channels. http://www.javaworld.com/javaworld/jw-07-1996/jw-07-mcmanis.html
  • "Using communication channels in applets, Part 3"
    Develop Visual Basic-style techniques to applet design -- and convert temperatures in the process. http://www.javaworld.com/javaworld/jw-06-1996/jw-06-mcmanis.html
  • "Synchronizing threads in Java, Part II"
    Learn how to write a data channel class, and then create a simple example application that illustrates a real-world implementation of the class. http://www.javaworld.com/javaworld/jw-05-1996/jw-05-mcmanis.html
  • "Synchronizing threads in Java"
    Former Java team developer Chuck McManis walks you through a simple example illustrating how to synchronize threads to assure reliable and predictable applet behavior. http://www.javaworld.com/javaworld/jw-04-1996/jw-04-synch.html

Related:
1 2 Page 2
Page 2 of 2