Friday, May 1, 2009

Remote Debugging Java Applications Using Eclipse

Ok so after completing the BBSpot quiz, I was really in mood to do something interesting. Suddenly I remembered a technical paper which I need to submit to my manager for some news letter (yes one more news letter of dozens of them you get every day from all over the net)

So was just thinking some interesting topic which will catch interest of all in developer community of my team. I did not want to come up with some new emerging technology with lots of technical jargon and theories. After all am not preparing a presentation for any (dumb) client who gets impressed by such not-so-useful articles/papers. My target audience is a bunch of smart and intelligent Java developers. So I was thinking of a topic which can catch the interest of these worldly creatures who then can use it for some better purpose to ease their development/debugging life. So the idea struck to my mind was Remote Debugging. I know this is nothing new to Java world, but still I find this feature very exciting, helpful & powerful. Remote debugging has always been an integral part of JDK or J2SE SDK since long and perhaps most unused or less explored by most of us. So what's remote debugging and why we actually need it?

Many a times, it so happens that some nasty bug shows up on the functional testing or the business testing environment, which are typically on the customer network infrastructure with very limited or no debugging tools? To make matters worse, when you try to reproduce the same bug on your local development environment, it won't show up and system will behave just fine. This is a scenario which each and every software project and its developers face without exception.

Traditionally, in such scenarios, we tend to put in a lot of debug statements (logger.debug or System.out.println) or in the case of web applications some hidden buttons which just the developer is aware of and on click of which a specific business scenario is executed and the logs, thereof, are generated. This approach has many flaws, as the typical algorithm illustrates:
1. Correctly identify [or guess] the piece of code which might be causing the bug to show up on env other then dev.
2. Code change to introduce debug statements. [Prone to introduce more bugs.]
3. Code change means extra round of unit/sanity testing and then new deployment.
4. Once deployed, collect the log and carefully analyze it to find the root cause. If nothing found, introduce more detailed debug statements i.e. Go back to step 1/2 and repeat.
5. If the root cause is found, fix it locally assuming that the bug is fixed [if the bug is not reproducible locally], remove all the debug statements introduced as a part of step 2 and generate a new build after successful unit/sanity testing.
6. Pray hard and hope the bug does not resurface again in your lifetime!

This technique is highly error prone and time consuming. To overcome this, as with most of the other matured programming languages, Java too ships with a capability to debug a remotely deployed application from your local development environment using your favorite IDE such as Eclipse. Java debugging capabilities is a result of JPDA (Java Platform Debugger Architecture). For more details on JPDA please refer JPDA Architecture

In this article, I won't dig much into the details of JPDA, but will focus on how to remotely debug an application using our good old friend – Eclipse. Here, I take up three common application scenarios and describe the remote debugging steps for each of them:
1. A standalone application. (such as a batch application)
2. A web application deployed on Apache Tomcat Servlet container.
3. A web application deployed on IBM WebSphere 5.1 application server.

To debug any application remotely, you need to...
  1. Make sure you are building (compiling) your application with available debug information.
    1. In Eclipse you can control these settings from Windows -> Preferences -> Java -> Compiler.
    2. If you are building your application with an ANT build script, then in the <javac> task, set the “debug” attribute to “true”.
    3. If you are building your application with Maven, set the debug configuration tag for maven-compiler-plugin to “true”.
  2. Deploy the application in the debug mode. This opens up a port for your debugger to get attached to the application's process and do its magic1. This is done by passing some pre-defined VM arguments in the application's launch scripts. The commonly used VM arguments are:

VM ArgumentDescription
-XdebugEnables debugging
-Xrunjdwp:<options>Loads the JPDA implementation residing in targeted JVM.
The commonly used <options>...
1. transport: Transport to use in connecting to debugger application. There can be many transport implementations out of which Sun comes with two mostly used implementation - A socket transport based on TCP/IP and a shared memory transport.
2. address: Transport address for the debugger application to attach.
3. server: If set to 'y'(mostly used), it listens for a debugger application to attach. Otherwise, attach to the debugger application at the specified address.
4. suspend: If set to 'y', JVM starts in suspended mode and stays suspended until a debugger is attached to it.


For a full JVM-version-specific list of 'runjdwp' options, please refer to:
  1. Java 1.4.2: http://java.sun.com/j2se/1.4.2/docs/guide/jpda/conninv.html#Invocation
  2. Java 1.5: http://java.sun.com/j2se/1.5.0/docs/guide/jpda/conninv.html#Invocation
  3. Java 6: http://java.sun.com/javase/6/docs/technotes/guides/jpda/conninv.html#Invocation
Remote debugging a standalone application
To remotely debug a standalone application, the first thing you need to do is compile the application source with available debug information, as mentioned in the above section and package it as a .jar file.

For example your jar file name is sandbox.jar and the class launches the application is foo.bar.Test, then you can run Test as

java -cp ./sandbox.jar foo.bar.Test
However, with just this, the application will not allow remote debugging. In order to debug Test application remotely, start it in the debug mode by providing the additional VM arguments, as shown below:

-Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=10007,suspend=n
These options will allow the Test application to execute in debug mode with a socket open on port 10007 for a debugger to attach to.

To verify if your application is truly running in debug mode and has opened a socket on 10007 listening for debugger to attach, on windows machine open a command prompt and type netstat /a or on Unix/Linux environment netstat -a and you should be able to see a TCP socket on port 10007 with state as LISTEN.

In a real life scenario, you won't be starting your standalone application directly by issuing java command. You will have a script file which will be invoking your application. So to avoid any script change to run application under debug mode or not, you can set an environment variable _JAVA_OPTIONS before your script invokes java command. For instance, if your application is launched by a script file called sandbox.sh then before executing sandbox.sh you can simply set _JAVA_OPTIONS environment variable as:

set _JAVA_OPTIONS="-Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=10007,suspend=n"
Once you are done with your debugging, you can reset _JAVA_OPTIONS to blank and then re-execute sandbox.sh

Eclipse configuration
Once your application is running in debug mode, you can switch back to Eclipse and open the Debug configuration dialogue from Run -> Debug Configuration... menu item. Over there, select “Remote Java Application” from the left navigation tree. Right click and select New.

On the right hand side, under Connect tab, select the project which holds the foo.bar.Test class which you intend to debug remotely. Select connection type as "Standard (Socket Attach)"

Under the 'Connection Properties', enter the host name or IP where you are running your foo.bar.Test class in debug mode and port as 10007. Depending upon your remote JVM settings and your wish you can check/uncheck "Allow termination of remote VM" checkbox.

Click Debug button and your eclipse debugger is now connected to your remote application and ready to debug. Insert debug points in your program and you are ready to debug your application remotely.

Remote debugging a web application on Apache Tomcat
The steps for compiling and building your application remains the same, as described above. What changes is how we start the Tomcat server. To start Tomcat in the debug mode, set two environment variables as...

JPDA_TRANSPORT=dt_socket
JPDA_ADDRESS=10007
After setting the above system environment variables, start Tomcat using following command…

%CATALINA_HOME%\bin\catalina.bat jpda start
OR on Linux...

$CATALINA_HOME/bin/catalina.sh jpda start
Once the server is up, it's in debug mode with a socket on port 10007 open for any debugger to attach for debugging the deployed application. Once Tomcat is running in debug mode you can follow the Eclipse Configuration steps to debug the web application. Once you are done with debugging, you can stop the server by:

%CATALINA_HOME%\bin\catalina.bat stop
OR on Linux...

$CATALINA_HOME/bin/catalina.sh stop

Remote debugging a web application on IBM WebSphere 5.1
Once again, the steps for compiling and building your application remains the same, as described above. What changes is how we inform WebSphere to run in debug mode. To do this:
1. Open WebSphere admin console.
2. Navigate to Servers -> Application Servers -> <YOUR_SERVER_NAME> -> Process Definitions -> Java Virtual Machine.
3. Scroll down and check the Debug Mode check box.
4. You can either accept the default debug arguments in Debug Arguments textbox or can provide as per your requirement.
5. Restart your application server.
6. Done !!!

Once your WebSphere is running debug mode, follow Eclipse Configuration to do debug the web application.

Some times you might get an error message while you try to connect to remote JVM through Eclipse...

Failed to connect to remote VM. Connection refused.
The most likely case if you are on a Windows machine is that, you have the Windows firewall on and it is preventing you from connecting. Stop the firewall and try to reconnect. Don’t forget to turn your firewall on once you are done, else you might end up debugging your Windows for mysterious behavior if some hacker gets hold of your PC ;-)

Here’s to smarter debugging!

Reference
Links to JPDA documentation & debug options across J2SE 1.4.2., 1.5.0 & 6:
http://java.sun.com/javase/technologies/core/toolsapis/jpda/

Detailed article on Remote Debugging in Java:
http://www.ibm.com/developerworks/opensource/library/os-eclipse-javadebug/index.html

Remote Debugging with Eclipse
http://www.jacoozi.com/index.php?option=com_content&task=view&id=119&Itemid=134

Cheers !!!
- Jay

1 You can also configure your remote debugging so that the application you are interested to remotely debug attaches itself to the debugger. For this article I’ll limit discussion in debugging application by attaching debugger to application.

9 comments :

  1. This is the best article on remote debugging i've seen so far. It's comphrehensive and practical. Thanks very much !

    ReplyDelete
  2. Nice post.

    If we are debugging a remote stand-alone application, how can we connect the remote debugger if remote process is not a long-lasting one, i.e. it ends before remote debugger can connect to it.

    ReplyDelete
  3. If you set suspend=y, the execution of java command will actually wait till a debugger connects to it.

    ReplyDelete
  4. Nice. I was also getting issue in connect remote connection and I use your tutorial and http://muhammadkhojaye.blogspot.com/2010/02/java-remote-debugging.html which both help me in complete my task. Thanks and keeep writing. Cheers

    ReplyDelete
  5. This might probably be an old article to comment, but I was trying out what you said on my Spring project. It works for most cases but doesn't seem to work when Spring Autowiring is involved.

    Any idea to how to resolve this?

    ReplyDelete
    Replies
    1. Pankaj, can you provide more information? like what kind of spring application it is? web app, standalone app? which container? I dont see any reason for this not to work, as application will be available for (remote) debugging only after bootup and Spring autowiring will be done before bootup is finished (except lazy beans).

      Delete
    2. It is a Spring web application that runs on Tomcat Container in debug mode (JPDA).

      Delete
    3. Hi, actually its more related to AOP than Autowiring. It is unable to load classes generated dynamically at runtime.

      I have posted a question on stackoverflow regarding this.
      http://stackoverflow.com/questions/23533332/source-not-found-while-remote-debugging-spring-based-web-app-on-tomcat

      Delete
    4. I dont see any reason for it not to work. I just posted answer to your question. Also as a refresher I quickly wrote a small spring web app injecting service to controller class and it just worked fine as expected. Can share it with you if you want

      Delete

 
Disclaimer : This is a personal blog and all content represent what I think and it does not advocate/support/advertise any other person/company. I do not earn money or intended to do so with this blog or any of the contents the blog hosts (except the google ads which you see). If I post something here that you find helpful, that's wonderful. Just in case, if I say something stupid, the stupidity is mine, and mine alone and I can not be held for anything if you fall for such stupidity :-). I cannot be held responsible for any kind of damage that may be caused by downloading or viewing the files or information provided herewith. Anybody and everybody can use/refer the contents of this blog at their own will and of course at own risk. There is no need for any kind of approval of the author. Although it would be great if feedback is left for any such usage to the author.