In the previous article, Divide by Zero. A comparison between Java and C, we concluded that C++ programs will crash when it meets a DivideByZero Exception. Even with a handler, it will crash anyway.
See this example:
#include <iostream>
#include <stdlib.h>
#include <csignal>
using namespace std;
//We can do something in the handler.
static void handler(int signum){
cout <<"Signal "<<signum <<" here!"<<endl;
//change SIGFPE to the Default Value
signal(SIGFPE, SIG_IGN);
}
int main(){
signal(SIGFPE,handler);
int d = 5;
d = d/(d-5);
cout <<"I want this line to be printed."<<endl;
return 0;
}
Result:
wsh@wsh-VirtualBox:~/JNI/JNI_CPP/OnlyCpp$ ./SystemHandler
Signal 8 here!
Floating point exception (core dumped)
Even with the handler, process still exited with a coredump. And the statement "I want this line to be printed" will never be printed.
After checking the GNU manual, we can explain this phenomenon:
24.2.1 Program Error Signals
The following signals are generated when a serious program error is detected by the operating system or the computer itself. In general, all of these signals are indications that your program is seriously broken in some way, and there’s usually no way to continue the computation which encountered the error.
Some programs handle program error signals in order to tidy up before terminating; for example, programs that turn off echoing of terminal input should handle program error signals in order to turn echoing back on. The handler should end by specifying the default action for the signal that happened and then reraising it; this will cause the program to terminate with that signal, as if it had not had a handler. (See Termination in Handler.)
Termination is the sensible ultimate outcome from a program error in most programs. However, programming systems such as Lisp that can load compiled user programs might need to keep executing even if a user program incurs an error. These programs have handlers which use longjmp
to return control to the command level.
The default action for all of these signals is to cause the process to terminate. If you block or ignore these signals or establish handlers for them that return normally, your program will probably break horribly when such signals happen, unless they are generated by raise
or kill
instead of a real error.
When one of these program error signals terminates a process, it also writes a core dump file which records the state of the process at the time of termination. The core dump file is named coreand is written in whichever directory is current in the process at the time. (On GNU/Hurd systems, you can specify the file name for core dumps with the environment variable COREFILE
.) The purpose of core dump files is so that you can examine them with a debugger to investigate what caused the error.
In order to prevent the process from terminating, we can use longjmp in handler, which avoids the following core dump and crash statements after invoking handler. The usage of longjmp is well illustrated in the following program.
#include <iostream>
#include <stdlib.h>
#include <csignal>
#include <setjmp.h>
using namespace std;
jmp_buf return_to_top_level;
//We can do something in the handler.
static void handler(int signum){
cout <<"Signal "<<signum <<" here!"<<endl;
//change SIGFPE to the Default Value
signal(SIGFPE, SIG_IGN);
longjmp (return_to_top_level, 1);
}
int main(){
signal(SIGFPE,handler);
if (setjmp (return_to_top_level) == 0){
int d = 5;
d = d/(d-5);
}
cout <<"I want this line to be printed."<<endl;
return 0;
}
And the result is:
wsh@wsh-VirtualBox:~/JNI/JNI_CPP/OnlyCpp$ ./SystemHandler Signal 8 here!
I want this line to be printed.
The method is useful because programmers usually complain that they cannot collect enough information after native library crashes. The whole program/Application simply exit, therefore they don't know how to debug it.
If you use longjmp, you can avoid crash, and throw an exception like java. Let's see how to use it in JNI:
C file:
#include <jni.h>
#include <iostream>
#include "SystemHandler.h"
#include <iostream>
#include <csignal>
#include <stdlib.h>
#include <setjmp.h>
using namespace std;
jmp_buf return_to_top_level;
JNIEnv* env = NULL;
//We can do something in the handler.
void handler(int a){
signal(SIGFPE, SIG_DFL);
cout <<"Signal "<<a <<" here!"<<endl;
//throw an exception
jclass newExcCls;
newExcCls = env->FindClass("java/lang/IllegalArgumentException");
if (newExcCls == NULL){
cout <<"FindClass(\"java/lang/IllegalArgumentException\") failed!"<<endl;
return;
}
env->ThrowNew( newExcCls, "thrown from C code");
//change SIGFPE to the Default Value
signal(SIGFPE,handler);
longjmp (return_to_top_level, 1);
}
JNIEXPORT void JNICALL Java_SystemHandler_print
(JNIEnv *_env, jobject obj)
{
env = _env;
signal(SIGFPE,handler);
if (setjmp (return_to_top_level) == 0){
int d = 5;
d = d/(d-5);
}
return;
}
Java file:
class SystemHandler{
private native void print();
public static void main(String[] args){
SystemHandler sh = new SystemHandler();
try{
sh.print();
}
catch (Exception e){
System.out.println("In Java:\n\t" + e);
}
}
static{
System.loadLibrary("SystemHandler");
}
}
As a result, the crash in C is caught and an exception is thrown to Java.
Result:
wsh@wsh-VirtualBox:~/JNI/JNI_CPP/SystemHandler$ java SystemHandler
Signal 8 here!
In Java:
java.lang.IllegalArgumentException: thrown from C code