Introduction
There are a wide range of applications that consist of two parts. The main GUI part is written in Java and the performance oriented part is written in C++. The GUI part reacts to the user inputs and passes its requests to the C++ part that usually implements the application core functionality. During application development, one can debug the Java GUI part from within Java debugger, for example, in Eclipse IDE. However the cooperative Java and C++ parts debugging during the Java application execution is not that simple. Usually the C++ part is debugged separately from the Java part in the C++ debugger by writing additional C++ test code that invokes the C++ part APIs. This is very inconvenient and quite a non-productive approach. It would be better if we could be able to debug the application code simultaneously in both the Java debugger and the C++ debugger while the same application runs.
In this article, I will show a simple solution that allows invocation of C++ debugger from Java debugger whenever the application flow reaches the C++ code in Unix/Linux environment.
Java and C++ Interaction
Java allows invocation of C/C++ (and other languages) APIs from Java code by using the so called Java Native Interface (JNI
). In JNI
, native functions are implemented in separate *.c or *.cpp files. (C++ provides a slightly cleaner interface with JNI
.). When the JVM
invokes the native function, it passes a JNIEnv
pointer, a jobject
pointer, and any Java arguments declared by the Java method. The details of JNI
interface are beyond the scope of this article. For our purposes, it is just important to mention that every C/C++ function/method that you want to call from Java must be wrapped by C function (JNI
wrapper) that will be called directly from Java and will in turn invoke the required C/C++ API.
JNIEXPORT void JNICALL Java_JNI_MyClass_MyClassMethod
(JNIEnv *env, jobject obj, jstring javaString)
{
const char *nativeString = env->GetStringUTFChars(javaString, 0);
MyClass *myobj = *(MyClass **) &obj;
myobj->MyClassMethod(nativeString);
env->ReleaseStringUTFChars(javaString, nativeString);
}
The JNI
wrappers can be written manually or can be generated automatically by Swig
utility from the C/C++ API function declarations.
Debugging C++ at Run-time
Our goal is to be able to invoke the C++ debugger on demand when execution flow of Java application passes to the C++ code through the JNI
wrapper. In Linux, the C++ debugger (gdb
) can be attached to the already running process (Java application in our case) using the process identifier (pid
). Each Linux process has its own unique integer identifier that can be obtained by calling getpid()
C system function from within the process. Moreover it is possible to attach the C++ debugger to a running process just-in-time from a certain point in C/C++ code of this process. This allows starting debugging at the specific C/C++ code location in the code. The following C code attaches the debugger from within an already running process. The code uses fork()
C system function that spawns an additional child process that is used to run the C++ debugger.
int gdb_process_pid = 0;
void exec_gdb()
{
int pid = fork();
if (pid < 0)
{
abort();
}
else if (pid)
{
gdb_process_pid = pid; sleep(10);
}
else
{
stringstream args;
args << "--pid=" << getppid();
execl("ddd", "ddd", "--debugger", "gdb", args.str().c_str(), (char *) 0);
cerr << "\nFailed to exec GDB (DDD)\n" << endl;
}
}
The above code first creates a child process and then executes DDD
graphical debugger in it. The DDD
debugger receives an application (parent) process pid
as one of its invocation flags and attaches itself to the application process. From this point, the debugger takes the control over the application execution. You are now able to debug C++ code from the debugger.
Triggering C++ Debugger from Java
Let's go back to Java. We are about to invoke some C/C++ function from Java code and want to debug this function execution in the C++ debugger. In order to achieve this, we may expose C++ native function trigger_gdb()
to Java through JNI
. The trigger_gdb()
function will post a request to run C++ debugger as soon as the execution flow reaches the C++ code. The trigger_gdb()
function just flags the request to run the C++ debugger. The debugger itself will be executed from the C++ function JNI
wrapper as we'll see below. The Java code can invoke trigger_gdb()
right before the C++ function that is needed to be debugged is called. For more convenience, you can trigger the C++ debugger from some GUI element like button or menu item.
bool do_trigger_gdb = false;
void trigger_gdb()
{
do_trigger_gdb = true;
}
public void some_function
{
trigger_gdb();
cpp_api_function();
}
What is left to do is to add conditional debugger invocation code to each C++ API JNI
wrapper that is called from Java. This task can be easily automated using perl
or other scripting language.
JNIEXPORT void JNICALL Java_JNI_MyClass_MyClassMethod
(JNIEnv *env, jobject obj, jstring javaString)
{
const char *nativeString = env->GetStringUTFChars(javaString, 0);
if (do_trigger_gdb)
{
exec_gdb();
do_trigger_gdb = false;
}
MyClass *myobj = *(MyClass **) &obj;
myobj->MyClassMethod(nativeString);
env->ReleaseStringUTFChars(javaString, nativeString);
}
Every C++ API JNI
wrapper called from Java will check whether to run the C++ API code controlled by the debugger or not. In case the debugger was requested, it just attaches the debugger to the running process using exec_gdb()
function discussed above.
Don't Multiply Entities If Not Necessary
The final notice. In order to avoid proliferation of the C++ debugger processes each time we enter the Java code that triggers the debugger invocation, we may check for existence of already activated debugger process using its process id. If it is still running, we do not execute another one. The following code performs this task:
bool is_gdb_running()
{
return kill(gdb_process_pid, 0) == 0;
}
The C system call kill
when called with signal 0
(second parameter) checks the existence of the process.
Summary
In the article, I showed how one is able to debug simultaneously Java/C++ mixed code in both Java and C++ debuggers in a convenient and productive manner. The shown approach can be easily extended for languages other than Java that are able to run C++ code.
History
- 9th February, 2009: Initial post