Click here to Skip to main content
15,867,834 members
Articles / Programming Languages / Java / Java SE

Debugging C++ Code from Java Application

Rate me:
Please Sign up or sign in to vote.
4.40/5 (4 votes)
9 Feb 2009GPL34 min read 41.9K   21  
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.

C++
// 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.

C++
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++
// 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.

C++
// 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:

C++
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

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Software Developer (Senior) Marvell
Israel Israel
17 years experience software engineer at Marvell company in Israel.

Comments and Discussions

 
-- There are no messages in this forum --