Saturday, February 5, 2011

Determining Level of Java Debug in Class File via javap

The Java Class File Disassembler (javap) is a useful tool for the Java developer that I have referenced in previous blog posts covering a variety of contexts such as detecting the innards of a Groovy class, comparing to the output of javac -Xprint, investigating synthetic methods, and looking at code offsets leading to "code too large" problems. In this post, I look at how javap might be used to determine what level of debugging a particular Java class was compiled with.

The Sun/Oracle javac implementation provides the -g option to control the level of debug support built into the compiled .class file. When -g is not specified at all during the compilation process, the javac default is that "only line number and source file information is generated." Specifying -g by itself enables inclusion of "local variable debugging information" in addition to source code and line numbers. The -g option can also be used to include only one of the three pieces of debugging information. The option -g:lines includes line numbers, -g:source includes source information, and -g:vars includes local variable debugging information. More than one keyword (lines, source, and vars) can be specified with a single -g option as long as the multiple keywords are separated by a comma.

An obvious way to determine which of these types of debug has been included in a compiled class is to actually attempt to access debug information via a debugger or other tool that requires debug information to be available. However, in cases where we'd like to know before firing up such tools, javap provides an alternative approach for determining what types of debug information are included in a given .class file.

For the demonstrations of using javap to determine the types of debug supported in a given class, I'm using a very simple Java class called SimpleClass.

SimpleClass.java
import static java.lang.System.out;

import java.util.Date;

/**
 * Simple class whose primary reason for existence is to demonstrate how javap
 * can be used to determine if a class was compiled with debug options enabled.
 */
public class SimpleClass
{
   private Date startDate;

   public SimpleClass() {}

   public void printTodaysDate()
   {
      out.println("Today is " + new Date());
   }

   public static void main(final String[] arguments)
   {
      final SimpleClass me = new SimpleClass();
      me.printTodaysDate();
   }
}

With a simple class defined in Java source code, I can now use javac with various -g options to compile .class files with correspondingly different debug support. I show the javap output for each of these and use the differences to explain how to tell what levels of debug javap is telling us the compiled class supports.


Compiling with javac Without -g Option

As described above, using javac without explicitly specifying the -g option leads to the default debug information of line numbers and source information being included in the compiled class. For this example, the command javac SimpleClass.java generates a SimpleClass.class file that javap disassembles as shown next.

Compiled from "SimpleClass.java"
public class SimpleClass extends java.lang.Object{
public SimpleClass();
  LineNumberTable: 
   line 13: 0



public void printTodaysDate();
  LineNumberTable: 
   line 17: 0
   line 18: 31



public static void main(java.lang.String[]);
  LineNumberTable: 
   line 22: 0
   line 23: 8
   line 24: 12



}

The first line in the javap output above (Compiled from "SimpleClass.java") is evidence that the "source code information" was included in the debug. The several lines with line numbers (such as "line 22") are similar evidence of line numbers being included in the compiled class. As expected, javap's output tells us that line numbers and source code information were compiled into the class.


Compiling with javac and -g:none Option

When the command javac -g:none SimpleClass.java is executed, there is no debug information included in the compiled class. The javap output, which is shown next, is therefore not surprising:

public class SimpleClass extends java.lang.Object{
public SimpleClass();



public void printTodaysDate();



public static void main(java.lang.String[]);



}

The javap output proves that a Java class compiled with javac -g:none has neither the source information (no "Compiled from") nor the line numbers that are provided by default.


Compiling with javac and -g:source Option

The javap output for a class compiled with javac -g:source has the beginning line that states where the .class was compiled from:

Compiled from "SimpleClass.java"
public class SimpleClass extends java.lang.Object{
public SimpleClass();



public void printTodaysDate();



public static void main(java.lang.String[]);



}


Compiling with javac and -g:lines Option

The javap output for a class compiled with javac -g:lines is shown next. It has the line numbers as did the default case, but lacks the "Compiled from" source information of the default case.

public class SimpleClass extends java.lang.Object{
public SimpleClass();
  LineNumberTable: 
   line 13: 0



public void printTodaysDate();
  LineNumberTable: 
   line 17: 0
   line 18: 31



public static void main(java.lang.String[]);
  LineNumberTable: 
   line 22: 0
   line 23: 8
   line 24: 12



}


Compiling with javac and -g:vars Option

The one type of debug output we have not yet seen is that for variable debugging information provided when javac -g:vars is used to compile the class. This javap output is shown next. The LocalVariableTable output here is the sign that the class in question was compiled with -g:vars specified. The variables' names are also in the output.

public class SimpleClass extends java.lang.Object{
public SimpleClass();

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      5      0    this       LSimpleClass;


public void printTodaysDate();

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      32      0    this       LSimpleClass;


public static void main(java.lang.String[]);

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      13      0    arguments       [Ljava/lang/String;
   8      5      1    me       LSimpleClass;


}


Conclusion

It is easy to use javap to determine what types of debug information were compiled into a generated .class file. When the first line of the javap output states "Compiled from," we know that source information was included. When we see various lines of the output with the keyword "line," we know that line number information was included. Finally, when we see "LocalVariableTable" in the javap output, we know that the class was compiled with variable debugging information included.


Additional Reference

There was a JDC Tech Tip called Getting Started with javap published on 29 August 2000 that apparently is no longer available in its original form. Fortunately, it is available in an Apache mail forum thread.

No comments: