This is a re-publication of an article that appeared on developerWorks in 2001; it has since been archived and is no longer visible. Depsite it being 8 years ago (a lot has changed since then) this is still referenced in some articles and later work. Therefore is seemed approriate to preserve it here. I have not edited this at all from the origianl. It is it was seen - please keep that in mind. I don't currently have the source referenced to hand - but will attempt to locate it. I'm pretty sure I know where it is.
When you cannot employ a pure Java language solution in an application, how do you effectively debug the Java/C hybrid programming since there is no debugger available that can check on this software chimera? In this article, software engineer Matthew White uses command-line tools to illustrate the basic techniques and problems you'll encounter when debugging multi-language applications. After reading this article, you'll know how to start applications so debuggers can be attached, what debuggers are available, and tips and hints to implement effective debugging.
For some applications, it's just not possible to use a pure Java language solution. Sometimes you need the raw performance of C/C++, or you need to use an API that only has a C/C++ interface, or you need to access operating system calls (for example, the Windows Registry) that have no equivalent in the Java class libraries. The problem is that currently there is no available debugger that lets you debug an application written using both the Java programming language and C.
In this article, we'll use command-line tools to illustrate basic debugging techniques and then discuss the problems you'll encounter when you have to run a hybrid application through its testing paces. We won't discuss basic debugging techniques (see Resources for a tutorial on Java debugging).
Note: For this article, I used Windows 2000 and Java Debugger (JDB) and GNU Debugger (GDB) as my main debuggers. The techniques are easily adaptable to a UNIX/Linux platform. Information on more advanced usage of the individual debuggers is included in Resources.
What are the problems?
Consider for a moment how the JVM is viewed from the point of view of the operating system. The operating system approaches the Java virtual machine as just another application, taking input in the form of class files and producing output.
Any debugging process that uses a Java debugger is, from the operating system's point of view, an application-level operation. However, debugging native applications is considered a more privileged activity and the entire application will be stopped to debug native code, which means that the JVM can't interact with a Java debugger.
What is JNI?
To learn more about JNI, see Resources for follow-up information.
The Java Native Interface (JNI) lets you integrate native programs
with Java programs, and can help address this deubgging problem. Read
the sidebar "What is JNI" for more information about JNI.
The two faces of JNI programs
There are two broad ways of structuring an application using JNI -- the standard way and the invocation way. Each method reflects how the JVM is started within a process. We'll examine both approaches in detail and then debug using both.
The standard JNI method
Suppose you have a procedure a procedure that does some heavy math lifting. You've written this procedure as a native library, and you want to call it from a Java application. This is what I refer to as using JNI via the standard approach; this is the main focus of JNI.
For this approach, you start the JVM by running a command like
Java myApplication, and at some place in the code, there is a native method.
Standard JNI method sample code
I've included some sample code to demonstrate the standard JNI method. The code files are available in Resources in a zip file named std.zip. The download includes the following seven files:
The Java code is
JNITest.java. This contains
main() method that loads the native library, plus two
static() methods. It also declares a
native() method --
public native int native_doubleInt(int value).
jnitest.c. This has the C implementation of the
native_doubleInt()method. When called, this method calls a simple C method and also calls back into the Java class
The invocation JNI method
The invocation method allows you to start the JVM only when it is required. For example, imagine that you've wrapped a native C interfact around a Java class library, and you want to write a C application that uses this interface. In this case, your main C application will want to start the JVM when it is necessary.
For this approach, your application will need to use the Invocation
API, which allows you to load the JVM into an arbitrary native
application. In reality, all Java programs are started using the
Invocation API. The
java.exe launcher program uses the
Invocation API to start a VM, and then runs the main class, albeit with
a lot of command processing and setup overhead. The crucial issue is
how the Java program starts from your point of view.
Invocation JNI method sample code
I've included some sample code to demonstrate the invocation JNI method. The code files are available in Resources in a zip file named invocation.zip. The download includes the following five files:
This code example uses the same class as the standard sample.
is the C code to compile into an executable. What this does first is to
create the JVM with debug options. (Always remember to set the number
of options correctly.)
goTest() method calls into the static method of the Java class. (It's made static to make the JNI code simpler.) Just before the
goTest() method, there's an infinite loop. This loop is here for debugging reasons, as I'll explain later.
Debugging a standard program
Now let's get to work. Imagine that you need to launch your Java program and then attach a C debugger to that process. It is possible with some debuggers, such as Visual C++, to run the Java executable directly. With this approach, you will need to add your DLL to the list of additional DLLs; otherwise, when you run the application, the breakpoint will not be set.
The Java application has a
System.loadLibrary call that
will dynamically load the DLL. (Note: This example demonstrates how to
debug using JDB and GDB together. If you don't want to use JDB, you
could set Java to stop waiting for input from the user after the
System.loadLibrary has occurred.) Follow these steps:
- Run the Java program in debug mode.
- From a different window, attach JDB to the JVM.
- Set a breakpoint on the line.
- Attach GDB to the JVM.
- Get the JDB to let the JVM continue running.
- Release the VM via the GDB window.
- Step through the native code.
Let's examine these steps more closely.
Step 1. The first step is to run the Java program in debug mode and attach a Java debugger to it. In the following examples, I use the Java Platform Debugger Architecture (JPDA) rather than the older debug Java interface.
suspend=y makes the VM stop as soon as it has established a port onto which the debugger can connect.
From a different window, attach JDB to the JVM. JDB needs to have
access to the source and class files. These can be specified on the JDB
command line using the
-classpath options. An alternative is to run JDB from the same directory as these files.
Step 3. We can then set a breakpoint on the line after the
System.loadLibrary call has occurred.
Note there are two ways of setting a breakpoint in JDB. One method is called stop in; the other is known as stop at. You use stop at for stopping at specific lines; stop in is used for breakpoints in specific methods or classes.
At this point, the JVM has stopped executing, but from the operating system point of view, the application is running normally. It will see the connection from the JDB process to the JVM process.
You can now attach GDB to the JVM process. To do this, you'll need to
know the process identifier (PID). You can either use the task manager
or a tool such as listdlls (from SysInternals.com) to obtain it. The
-nw -quiet options start GDB without the graphical interface and without the starting-up version information.
Step 5. GDB now has a breakpoint set in the process. You now need to get JDB to let the JVM continue running. Type
cont in the JDB window. (Note that this will appear to not
return -- this is because you have stopped the process using GDB.)
What's happened is that JDB has set the JVM to run as it would normally.
Step 6. Type
cont in the GDB window to "release" the VM. Notice that the breakpoint has been hit.
Step 7. You can now step through the native code as required. If you go back to the JDB window and enter
stop at JNITest:26
to try to stop the application before it calls the native method for
the second time, it won't respond. You'll need to let the application
step on in GDB first.
Stepping over the JNI calls in GDB can be difficult. You may need to enter
next twice to step over the calls.
A better approach is to enter a number of breakpoints, which also results in speeding up the process -- JNI calls can take some time to complete when using GDB.
Debugging using the invocation approach
Using this approach, you will be able to easily debug the C code. You can run your application in your debugger. To debug any Java code, however, you'll need to set JVM options. You can do this when you start the application using the Invocation API.
Notice that the options in the following examples are the same as in Listing 1. If you run this program directly, you'll get similar output.Listing 6. C code calls Java method to triple a number
In this example, I've got the C code to call the Java method to triple a number. I want to get the JDB into this method -- we've already supplied the port number at which you could connect in JDB. The hard part with debugging in this style is allowing JDB to interact sufficiently with the JVM while under the control of GDB.
A simple way to do this is to have a loop that never exits. You can use this loop to let the application run without performing any logic, but allowing the JVM to do work in connecting to JDB. Here are the steps to follow:
- Load the application into GDB; set a breakpoint.
- Repeatedly step through the loop in GDB.
- Stop the loop in GDB and continue debugging.
- Watch JDB stop at the breakpoint.
Let's look at these steps in more detail.
Step 1. Load the application into GDB and set a breakpoint so you can step around the loop. Note that although this is a bad loop to have in any program, it's only for debug purposes. The idea is to allow the JVM to do its work connecting to JDB. Every time you let the program step around the loop, more work is done by the JVM/JDB.
Step 2. Repeatedly step through the loop in GDB to set a breakpoint in the Java triple number method. Just keep stepping around the loop while JDB responds. You could make this more complex by adding sleeps, for example.Listing 7. Continually stepping around loops while JDB responds
Once you've completed Step 2, you can stop the loop in GDB and continue
debugging. Here, I've set a breakpoint at the end of the
goTest() method to let all the JNI calls execute.
Step 4. When the code above runs, you'll see JDB stop at the breakpoint.
Opaque JNI references
JNI references are for the most part opaque -- that is, their structure is not published. If you know the structure of the object, you may be able to see the object in memory.
In the following snippet, I've stopped some C code, just after a call to a Java method has been made. The
result contains a jstring reference.
Note the address in memory variable (
in Listing 11 below. If you print memory starting at that location, you
can see the start of the string. The Cygwin distribution of GDB offers
a graphical front end that has a memory-editing window -- which makes
it easier to see the string.
The standard Java distributions don't come with libraries for GCC, so the compilation is bit more involved. For the examples above, I've used the MinGW toolkit, using the following sequence of commands:
- Generate a library file for GCC, based on a
deffile. Create a file called
jvm.defwith the following contents.
If you run a tool such as
nmin the libjvm file, you should be able to extract the references for the other Invocation APIs.
- Create a library file for your application to link to.
dlltool -k --input-def jvm.def --dll jvm.dll --output-lib libjvm.a
- Compile your C application.
gcc -Ic:\_jdk\include -g -c main.c
- Link together main applications with the JVM.
gcc -o main.exe main.o -L./ -ljvm
Some final JNI tips
Here a few tips for using JNI to avoid some common problems:
- Always check the return values and exceptions from JNI calls.
- When you check for exceptions, remember that if you call
ExceptionDescribe()you may get an exception described that occurred some time ago, not as a result of the last call.
- Remember to call
threadDetach()before any of your threads terminate. Failure to make this call will cause big problems when the garbage collector runs. It will try to find the stack frame of a thread that no longer exists.
- When running with JPDA, always use
java.compiler=NONE. If you use
java.compiler=fred, it will stop the JIT, but won't actually work.
For more insight, I recommend The Java Native Interface by Sheng Liang and Sun's JNI site (see Resources for links). If you want to use the Essential JNI book (which has a good section on JNI basics), note that its functions for Java 2 were based on a beta version of the JDK, so they don't exist in that form.
Remember, when you are blessed with a robust, but not pure Java-language application, don't forget how to use JNI to effect a complete, complex debugging process.
- Download the example code used in this article. std.zip contains the files for the standard JNI approach, and invocation.zip contains the files for the invocation JNI method.
- This step-by-step tutorial on the IBM Distributed Debugger
offers a distributed debugger scenario, examples of local and remote
debugging, and instructions on how to attach a debugger to a running
- In the jGuru JNI forum, Davanum Srinivas manages an active discussion of "all things JNI."
- Sun's JPDA page
is an exhaustive source that provides an overview of and architecture
notes and specifications for the Java Platform Debugger Architecture.
The company's JNI page offers links to the specification, JNI enhancements, and a tutorial.
- Learn all about GCC and its related tools.
- The Sysinternals freeware site is devoted to Windows development, with such great tools as Process Explorer (a GUI-based utility that allows users to see DLLs and Handles opened and loaded by processes) and ListDLLs (which display DLLs loaded by processes).
is a collection of header files and import libraries that let
developers use GCC and produce native Windows32 programs that don't
rely on third-party DLLs.
is a UNIX environment for Windows that consists of a DLL that acts as a
UNIX emulation layer to provide UNIX API functionality and a collection
of UNIX-ported tools to serve up a UNIX/Linux look and feel.
- For an open source graphical Java debugger that uses the JPDA, try JSwat -- an extensible, stand-alone debugger front end.
a full-featured IDE based on the GCC tools, is able to create Windows
or console-based C/C++ programs using the MinGW or the Cygwin compiler.
- These GNU related projects offer a different approach to compiling JNI with GCC, as well as a roundup of resources for GCC, Cygwin, and MinGW.
- If you're going to use JNI seriously, then read Sheng Liang's
The Java Native Interface
(Addison-Wesley, 1999) which covers such topics as writing native
methods, passing data types between the Java language and native
programming languages, embedding a JVM in native applications, and
improving the efficiency and reliability of your code.
- For debugging in Windows, take a look at these books:
- John Robbins's Debugging Applications proffers a holistic approach to debugging and redefining bugs, including everything from user interface problems and performance issues to incomprehensible product manuals.
- Everett McKay and Mike Woodring's Debugging Windows Programs is a guide for debugging Windows for Visual C++ programmers.
- Eric Allen's Diagnosing Java code column on developerWorks offers tips for fixing your code before it becomes a problem.
- The Java debugging tutorial by Laura Bennett (developerWorks, February 2001) provides a solid introduction to the concepts of Java debugging.
- Find more Java technology resources on the developerWorks Java technology zone.
|Just a reminder that what you've been reading is an exact copy of a 2001 developerWorks article of mine - I re-posted it as is without changes.|