JPrintStackTrace
Posted On July 19, 2007 by Rose Mary filed under Java
Also, the program you are trying to debug could be a legacy one or you may not have access to the source code or even if you have the source code, recompiling and building them may not be easy. In such cases, you may find the need for a tool that can enhance the Java stack trace and help you easily debug and fix the problem, imperative.
In this article, we shall get to know a tool called JPrintStackTrace (developed by the author) that can be employed for a more precise Java stack trace without making any changes to the source code.
JPrintStackTrace is Java Platform Debugger Architecture (JPDA) based tool. It runs on a separate JVM and can connect to your program for enhancing Java stack trace. Since it works at the JVM level, you don't need to make any code changes to your program. The power of JPrintStackTrace tool could be best explained with an example. Consider the Simple.java program shown in figure 1.

This program tries to dereference a null string at line 8 in function display14(). Looking carefully at the sample program you will observe that the exceptions are catched and a new exception object is thrown to the caller and this continues till the function display1() where it is displayed to the user.
Now let us compile and run the program.

From figure 2 you can clearly see that the Java stack trace is of no use. It does not provide the correct source code line that triggered the exception. You now have to either change the source code to handle the exceptions correctly or should run the program in a step-by-step mode in a debugger to identify the root cause. However, both these approaches are either time consuming or not feasible for several reasons, as described earlier.
Now let us run the same Simple.java under JPrintStackTrace tool. First compile and run the Simple.java in debug mode as shown in figure 3.

From figure 3 you can see that Simple program is started in debug mode and listens at port 4000.
You can get JPrintStackTrace usage help by running the tool without passing any command line options. It takes four command line options as described below:
1. -r <remote_host_name>: The name of the machine running the program to be debugged. In our case it is 'localhost';
2. -p <port_no>: The port number at which the program to be debugged is listening. In this case it is '4000';
3. -e <class_exclude_list_file>: List of java classes that need to be excluded by the tool. A sample class exclude list is shown in figure 4; and
4. -l <log_file>: Log file.

Now let us start the JPrintStackTrace tool and connect to Simple program as shown in figure 5.

Once connected, the JPrintStackTrace tool will run the Simple program and listens for exception event in target JVM. Upon getting the exception event, it will call the printStackTrace() method on the exception object on the target JVM to get a detailed stack trace as shown in figure 6.

From figure 6 you can clearly see the source code line that triggered the exception. It is line 8 in function display14()! On comparing the outputs shown in figure 2 and figure 6, you will definitely appreciate the power of JPrintStackTrace tool. Remember, you get all this without making any changes to your source code!
Source code for JprintStackTrace is available in CD2. Happy debugging!!!
The author is working at HCL Technologies (Networking Product Division), Chennai. He can be contacted at: rajark_hcl@yahoo.co.in.
Reference:
http://www.java.sun.com/products/jpda/index.jsp
/*
*
* JPrintStackTrace
*
* (C) Copyright 2005, Raja R.K (rajark_hcl@yahoo.co.in)
* All rights reserved.
*
*/
import java.util.*;
import java.io.*;
import com.sun.jdi.*;
import com.sun.jdi.connect.*;
import com.sun.jdi.request.*;
import com.sun.jdi.event.*;
class EventProcessor extends Thread
{
private VirtualMachine vm = null;
private PrintWriter pw = null;
private boolean stop = false;
public EventProcessor(VirtualMachine vm, PrintWriter pw)
{
this.vm = vm;
this.pw = pw;
}
private void Info(String msg)
{
try {
pw.println(msg);
} catch(Throwable t) {
t.printStackTrace();
}
}
private void handleExceptionEvent(ExceptionEvent event) throws Throwable
{
int lineNo = 0, catchLineNo = 0;
ObjectReference objRef = null;
ReferenceType refType = null;
Iterator methodsListItr = null;
Method method = null;
Vector argList = new Vector();
try {
lineNo = event.location().lineNumber();
catchLineNo = event.catchLocation().lineNumber();
Info("Exception event received: lineNo: " + lineNo +
", catchLineNo: " + catchLineNo);
objRef = event.exception();
refType = objRef.referenceType();
methodsListItr = refType.allMethods().iterator();
while( methodsListItr.hasNext() ) {
method = (Method) methodsListItr.next();
if( method.name().equals("printStackTrace") &&
method.signature().equals("()V") ) {
objRef.invokeMethod(event.thread(),method,argList,
ObjectReference.INVOKE_SINGLE_THREADED);
break;
}
}
} catch(Throwable t) {
throw t;
}
}
private void handleEvent(Event event) throws Throwable
{
try {
if( event instanceof VMStartEvent ) {
return;
}
if( (event instanceof VMDisconnectEvent) || (event instanceof VMDeathEvent) ) {
stop = true;
return;
}
if( event instanceof ExceptionEvent ) {
handleExceptionEvent((ExceptionEvent) event);
return;
}
Info("Error: Unexpected event '" + event + "' received.");
} catch(Throwable t) {
throw t;
}
}
public void run()
{
EventQueue eventQ = null;
EventSet eventSet = null;
EventIterator eventItr = null;
try {
eventQ = vm.eventQueue();
while(!stop) {
try {
eventSet = eventQ.remove();
eventItr = eventSet.eventIterator();
while( eventItr.hasNext() ) {
handleEvent(eventItr.nextEvent());
}
eventSet.resume();
} catch(Throwable t) {
t.printStackTrace();
break;
}
}
Info("JPrintStackTrace terminated.");
} catch(Throwable t) {
t.printStackTrace();
}
}
}
public class JPrintStackTrace
{
public static final String VERSION = "JPrintStackTrace 1.0 - A JPDA based stack trace logging tool.\n" +
"(C) Copyright 2005, Raja R.K (rajark_hcl@yahoo.co.in)\n" +
"All rights reserved.\n";
public static final String JPDA_CONNECTOR_NAME = "com.sun.jdi.SocketAttach";
public static final String JPDA_HOSTNAME_ARG = "hostname";
public static final String JPDA_PORT_ARG = "port";
//Arguments
private String hostName = null;
public void setHostName(String hostName) { this.hostName = hostName; }
private String portNo = null;
public void setPortNo(String portNo) { this.portNo = portNo; }
private String logFile = null;
public void setLogFile(String logFile) { this.logFile = logFile; }
private String classExcludeFile = null;
public void setClassExcludeFile(String classExcludeFile) {
this.classExcludeFile = classExcludeFile;
}
//Target VM
private VirtualMachine vm = null;
//Log file handler
PrintWriter pw = null;
//Class exclude list
Vector classExcludeList = new Vector();
private void Info(String msg)
{
try {
if( pw == null ) {
pw = new PrintWriter(new FileWriter(new File(this.logFile)),true);
}
pw.println(msg);
} catch(Throwable t) {
t.printStackTrace();
}
}
private void loadClassExcludeList() throws Throwable
{
BufferedReader br = null;
String line = null;
try {
Info("Loading class exclude list from '" + classExcludeFile + "'");
classExcludeList.clear();
br = new BufferedReader(new FileReader(classExcludeFile));
while( (line=br.readLine()) != null ) {
line = line.trim();
if( line.length() == 0 ) {
continue;
}
if( line.startsWith("#") ) {
continue;
}
classExcludeList.add(line);
}
} catch(Throwable t) {
throw t;
} finally {
try {
if( br != null ) {
br.close();
}
} catch(Throwable t1) {
t1.printStackTrace();
}
}
}
private void enableEvents() throws Throwable
{
EventRequestManager erMgr = null;
ExceptionRequest exReq = null;
int i = 0;
try {
erMgr = vm.eventRequestManager();
exReq = erMgr.createExceptionRequest(null,true,true);
exReq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
for( i=0; i<classExcludeList.size(); i++ ) {
exReq.addClassExclusionFilter((String) classExcludeList.get(i));
}
exReq.enable();
} catch(Throwable t) {
throw t;
}
}
private VirtualMachine connectToRemoteVM(Connector con, Map conArgs) throws Throwable
{
VirtualMachine vm = null;
try {
vm = ((AttachingConnector) con).attach(conArgs);
Info("JPrintStackTrace connected to remote VM (" +
vm.name() + " " + vm.version() + ").");
return vm;
} catch(Throwable t) {
throw t;
}
}
private Map setConnectorArgs(Connector con, String hostName, String portNo) throws Throwable
{
Map conArgs = null;
Iterator conArgsItr = null;
Connector.Argument arg = null;
try {
//Get default arguments
conArgs = con.defaultArguments();
//Set arguments
conArgsItr = conArgs.keySet().iterator();
while( conArgsItr.hasNext() ) {
arg = (Connector.Argument) conArgs.get((String) conArgsItr.next());
if( arg.name().equals(JPrintStackTrace.JPDA_HOSTNAME_ARG) ) {
arg.setValue(hostName);
}
if( arg.name().equals(JPrintStackTrace.JPDA_PORT_ARG) ) {
arg.setValue(portNo);
}
}
return conArgs;
} catch(Throwable t) {
throw t;
}
}
private Connector getConnector(String conName) throws Throwable
{
Iterator conItr = null;
Connector con = null;
try {
conItr = Bootstrap.virtualMachineManager().allConnectors().iterator();
while( conItr.hasNext() ) {
con = (Connector) conItr.next();
if( con.name().equals(conName) ) {
return con;
}
}
throw new Exception("Invalid connection '" + conName + "' specified.");
} catch(Throwable t) {
throw t;
}
}
public void runJPrintStackTrace() throws Throwable
{
EventProcessor ep = null;
Map conArgs = null;
Connector con = null;
try {
//Load class exclude list
loadClassExcludeList();
//Get connector
con = getConnector(JPrintStackTrace.JPDA_CONNECTOR_NAME);
//Set connector arguments
conArgs = setConnectorArgs(con,hostName,portNo);
//Connect to remote VM
vm = connectToRemoteVM(con,conArgs);
//Start event processor thread
ep = new EventProcessor(vm,pw);
ep.start();
//Enable events
enableEvents();
//Resume if suspended
vm.resume();
} catch(Throwable t) {
throw t;
}
}
public static void printInvalidFileUsage(String msg)
{
System.out.println("\nERROR: " + msg);
JPrintStackTrace.printUsage(1);
}
public static void printInvalidUsage(String arg)
{
System.out.println("\nERROR: Invalid argument " + arg + " specified");
JPrintStackTrace.printUsage(1);
}
public static void printUsage(int exitError)
{
System.out.println("\nUsage:\n");
System.out.println("JPrintStackTrace <options>\n");
System.out.println("Options: ");
System.out.println("[-V] : Display version and exit");
System.out.println("[-h] : Display help and exit");
System.out.println("-r <remote_host_name> : Remote host name");
System.out.println("-p <port_no> : Port number");
System.out.println("-e <class_exclude_list_file> : File containing list of classes to be excluded");
System.out.println("-l <log_file> : JPrintStackTrace log file");
System.exit(exitError);
}
public static void printVersion()
{
System.out.println("\n" + JPrintStackTrace.VERSION);
System.exit(0);
}
public static void main(String args[])
{
JPrintStackTrace jpst = null;
int i = 0;
try {
if( args.length == 0 ) {
JPrintStackTrace.printUsage(1);
}
jpst = new JPrintStackTrace();
while( i < args.length ) {
if( args[i].equals("-V") ) {
JPrintStackTrace.printVersion();
}
if( args[i].equals("-h") ) {
JPrintStackTrace.printUsage(0);
}
if( args[i].equals("-r") ) {
if( (i+1) >= args.length ) {
JPrintStackTrace.printInvalidFileUsage(args[i]);
}
i++;
jpst.setHostName(args[i]);
i++;
continue;
}
if( args[i].equals("-p") ) {
if( (i+1) >= args.length ) {
JPrintStackTrace.printInvalidFileUsage(args[i]);
}
i++;
jpst.setPortNo(args[i]);
i++;
continue;
}
if( args[i].equals("-l") ) {
if( (i+1) >= args.length ) {
JPrintStackTrace.printInvalidFileUsage(args[i]);
}
i++;
jpst.setLogFile(args[i]);
i++;
continue;
}
if( args[i].equals("-e") ) {
if( (i+1) >= args.length ) {
JPrintStackTrace.printInvalidFileUsage(args[i]);
}
i++;
jpst.setClassExcludeFile(args[i]);
i++;
continue;
}
JPrintStackTrace.printInvalidUsage(args[i]);
}
jpst.runJPrintStackTrace();
} catch(Throwable t) {
t.printStackTrace();
}
}
}
