Remote Debugging
The debugging of an external process from within Eclipse is supported through remote debugging. The process can be running on the same machine as the debugger or a separate machine on the network. Unfortunately, the VMs of various vendors do not all turn on their debug-listening capabilities in the same way. The following example uses the standard Java VM and its documented command-line options.
The first step is to ensure that the compiler has debugging turned on. Open the Eclipse Preferences dialog and go to the Java compiler page (Window, Preferences, Java, Compiler). Click the Compliance and Classfiles tab and look at the settings for Classfile Generation. For this example, make sure all the boxes are checked, although in general, Preserve unused local variables can be unchecked (see Figure 3.6). Clicking OK will force a rebuild of the current projects.
In the Calculator project, create a new class named CalculatorInput. This class will read a string of digits from the command line, add them up, and print the result of the addition. All the generated comments have been removed:
public class CalculatorInput { private BufferedReader _in; private SimpleCalculator _calculator; public CalculatorInput() { InputStreamReader reader = new InputStreamReader(System.in); _in = new BufferedReader(reader); _calculator = new SimpleCalculator(); } public static void main(String[] args) { CalculatorInput app = new CalculatorInput(); try { app.start(); } catch (IOException e) { System.out.println("Exception found: " + e.getMessage()); } } public void start() throws IOException { String calcStr = null; System.out.println( "Enter a space-separated list of numbers to add (q to exit):"); calcStr = _in.readLine(); int [] value = null; int result; while(calcStr.equals("q") == false) { result = 0; value = parseValues(calcStr); for (int i = 0; i < value.length; i++) { result = _calculator.add(value[i], result); } System.out.println("Result: " + result); calcStr = _in.readLine(); } } private int[] parseValues(String calcStr) { int [] result = null; Vector v = new Vector(); StringTokenizer tokenizer = new StringTokenizer(calcStr); while (tokenizer.hasMoreTokens()) { String strVal = (String) tokenizer.nextToken(); v.add(strVal); } result = new int[v.size()]; for (int i = 0; i < result.length; i++) { result[i] = Integer.parseInt((String) v.get(i)); } return result; } }
Figure 3.6 The Preferences dialog with all classfile generation options checked.
Do not worry about the missing package imports. When you have typed the preceding code into Eclipse, you can press Ctrl+Shift+O and all the missing imports will be added to the file. Save CalculatorInput.
Export the Calculator project by right-clicking the project name and selecting Export from the pop-up menu. When the Export dialog opens, select JAR file as the export destination. Click Next. In the list to the right of the project, uncheck .classpath, .project, and calculator.jpage. Exporting them will not cause any damage, but they are not needed. In the Select Export Destination field, enter c:\calc.jar or any safe location where you can find the file later, but still name the JAR file calc.jar. Click Next. The JAR Packaging Options page can be left alone, so click Next. In the JAR Manifest Specification page, click the Browse button, located toward the bottom of the page, and select CalculatorInput from the Select Main Class dialog. Click OK. The new class, eclipse.kickstart.example. CalculatorInput, is now assumed to be the class to be run when you run the JAR file from the command line. Click Finish to complete the export process.
Open a command-line window and change directory to wherever it is you saved calc.jar. If you are running on Windows, the easiest location to run this from is the C drive, which is why you entered c:\calc.jar as the target export directory. Make sure you can run Java from the command line. If not, update your path variable to include Java's bin directory so that you can run CalculatorInput from the command line.
Enter the following on the command line:
C:\> java -Xdebug -Xrunjdwp:transport=dt_socket,address=8000,suspend=n,server=y -jar calc.jar
All this must be on one line. Press Enter, and the program prompts you to enter a string of space-separated numbers, or you can enter the letter q to exit. If the program does not prompt you, check that you have entered everything as listed here.
The JVM -X Options
If you are using the standard Javasoft-supplied JVM, here is a caveat on the preceding command-line listing: The -X command-line option for the JVM defines options that may go away someday. The two options have been around for a few years, but there are no guarantees they will stay that way. Always refer to your vendor documentation to discover what options are available to turn on remote debugging.
The command-line options are as follows:
-
-XdebugNotifies the VM that an external process may connect to it.
-
-Xrunjdwp:Contains the list of comma-separated configurations needed by the VM to allow an external process to connect to it:
-
transport=dt_socketThe VM can expect the external process to connect via a socket.
-
address=8000The external process will connect using this port.
-
suspend=nThe VM should not suspend the program while it waits for the external program to attach.
-
server=yThe VM should behave like a server.
Enter the string 0 1 and press Enter. The program will display Result: 1. Let's attach the Eclipse debugger to this process.
To attach the Eclipse debugger to an external Java program, it has to be told which machine the external program is running on and what port it can use to communicate with it. This information is entered through the Launcher dialog. Select from the main menu Run, Debug to open the Launcher dialog specific to debugging. Select Remote Java Application as the configuration type and click New. This configuration information will be used by the debugger to connect to the external program. Enter the following configuration information:
Name: Remote Calculator
Project: Calculator
Connection Type: Standard (Socket Attach)
Connection Properties: Host: localhost
Connection Properties: Port: 8000
Click Debug to start up the debugger and have it attach to CalculatorInput. The Debug perspective will open and the Debug view will display the Calculator as running in a Java HotSpot Client VM. Right-click the third line of the stack frame (Thread [main](Running)) and select Suspend from the pop-up menu. The external VM will suspend the named thread "main" and the debugger displays what the current stack looks like (see Figure 3.7). The second-to-last line of the stack is the call from CalculatorInput.start(). Select this line and the CalculatorInput file will open in the editor at the proper location.
Figure 3.7 The debugger with the Debug view showing the stack trace for the remote CalculatorInput program. The editor is open to the line at which the process is suspended.
The Variables view displays the this and calcStr references as set in the external process. Set a breakpoint at the first line inside the while loop and press F8 to resume the thread. Bring the command-line window forward, enter the string "2 2" and press Enter. The debugger will suspend execution of CalculatorInput and display the line within the code where the breakpoint was hit. The Variables view displays three variables: this, calcStr, and value, which was created after the call to in.readLine().
From within the Variables view, double-click calcStr and change its value from "2 2" to "5 5". When the Set Variable Value dialog opens, do not enter quotes around the values. Click OK and then press F8 to resume execution. The result of the addition of "2 2" is now 10. From the command line window, type q to end the CalculatorInput session.
Once the debugger has connected to the external process, you can use many of the standard features of the debugger, but remember that you cannot do things like hot-swap code. If you change the code in the debugger while you are debugging, the debugger will flag the code as out of sync with the running code and will not allow you to use the file for the debugging session.