Debugging C++ Code from Java Application






4.40/5 (4 votes)
How to debug simultaneously Java/C++ mixed code in both Java and C++ debuggers
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.
// JNI wrapper for void MyClass::MyClassMethod(const char*) C++ method
JNIEXPORT void JNICALL Java_JNI_MyClass_MyClassMethod
(JNIEnv *env, jobject obj, jstring javaString)
{
//Get the native string from javaString
const char *nativeString = env->GetStringUTFChars(javaString, 0);
// Invoke the native C/C++ function here
MyClass *myobj = *(MyClass **) &obj;
myobj->MyClassMethod(nativeString);
//DON'T FORGET THIS LINE!!!
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()
{
// Create child process for running GDB debugger
int pid = fork();
if (pid < 0) /* error */
{
abort();
}
else if (pid) /* parent */
{
// Application process
gdb_process_pid = pid; // save debugger pid
sleep(10); /* Give GDB time to attach */
// Continue the application execution controlled by GDB
}
else /* child */
{
// GDB process. We run DDD GUI wrapper around GDB debugger
stringstream args;
// Pass parent process id to the debugger
args << "--pid=" << getppid();
// Invoke DDD debugger
execl("ddd", "ddd", "--debugger", "gdb", args.str().c_str(), (char *) 0);
// Get here only in case of DDD invocation failure
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.
// C++ code that triggers C++ debugger
bool do_trigger_gdb = false;
void trigger_gdb()
{
do_trigger_gdb = true;
}
// Java code that triggers C++ debugger
public void some_function
{
// Trigger debugger invocation as soon as the application control
// reaches cpp_api_function() C++ code
trigger_gdb();
// Call C++ JNI wrapper
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.
// JNI wrapper for void MyClass::MyClassMethod(const char*) C++ method
JNIEXPORT void JNICALL Java_JNI_MyClass_MyClassMethod
(JNIEnv *env, jobject obj, jstring javaString)
{
//Get the native string from javaString
const char *nativeString = env->GetStringUTFChars(javaString, 0);
// Attach C++ debugger to the application if requested
if (do_trigger_gdb)
{
exec_gdb();
do_trigger_gdb = false;
}
// Invoke the native C/C++ function here
MyClass *myobj = *(MyClass **) &obj;
myobj->MyClassMethod(nativeString);
//DON'T FORGET THIS LINE!!!
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