In this sample, I will show you how to prevent JVM from crashing whenever SIGSEGV (Linux) or SIGBUS (OS X) occurs.
This sample will require some low level coding, but I hope you will be able to follow it.
After you complete this tutorial, you should end up with the layout similar to this one:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
recipeNo015 ├── Makefile ├── c │ ├── recipeNo015_SigSegv.c │ ├── recipeNo015_SigSegv.h │ └── recipeNo015_SigSegvNoHandler.h ├── java │ └── recipeNo015 │ ├── SigSegv.java │ └── SigSegvNoHandler.java ├── lib │ ├── libSigSegv.dylib │ └── libSigSegv.dylib.dSYM │ └── Contents │ ├── Info.plist │ └── Resources │ └── DWARF │ └── libSigSegv.dylib └── target └── recipeNo015 ├── SigSegv.class └── SigSegvNoHandler.class |
In this sample, we have two Java classes that call the same native function – riskyCode(). SigSegv handles the SIGSEGV/SIGBUS and SigSegvNoHandler doesn’t care about that.
First code – SigSegv.java – is the code where we call callRiskyCode function (native code). This function makes few things (make sure to check C source). First of all, it sets signal handler, second, it calls the code – riskyCode – third, it either throws exception (in case of error) or sets default handlers (in case of successful execution). Remember, SigSegv.java simply calls native code that does all the tricky things.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
/* SigSegv.java */ package recipeNo015; public class SigSegv { /* This is the native method we want to call */ public static native void callRiskyCode(); /* Inside static block we will load shared library */ static { System.loadLibrary("SigSegv"); } public static void main(String[] args) { /* This message will help you determine whether LD_LIBRARY_PATH is correctly set */ System.out.println("library: " + System.getProperty("java.library.path")); /* Call to shared library */ try { SigSegv.callRiskyCode(); } catch(Exception ex) { System.out.println("Call to callRiskyCode() finished with exception"); } } } |
As for the SigSegvNoHandler.java we do similar thing. But in this case, we just call callRiskyCodeNoHandler (native code) that calls the buggy code but it doesn’t care about handlers. This is the place where JVM will crash with famous hs_er file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
/* SigSegvNoHandler.java */ package recipeNo015; public class SigSegvNoHandler { /* This is the native method we want to call */ public static native void callRiskyCodeNoHandler(); /* Inside static block we will load shared library */ static { System.loadLibrary("SigSegv"); } public static void main(String[] args) { /* This message will help you determine whether LD_LIBRARY_PATH is correctly set */ System.out.println("library: " + System.getProperty("java.library.path")); /* Call to shared library */ try { SigSegvNoHandler.callRiskyCodeNoHandler(); } catch(Exception ex) { System.out.println("Call to callRiskyCodeNoHandler() finished with exception"); } } } |
After we create header files with JNI signatures, we will get following files. Each of them defines one function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class recipeNo015_SigSegv */ #ifndef _Included_recipeNo015_SigSegv #define _Included_recipeNo015_SigSegv #ifdef __cplusplus extern "C" { #endif /* * Class: recipeNo015_SigSegv * Method: callRiskyCode * Signature: ()V */ JNIEXPORT void JNICALL Java_recipeNo015_SigSegv_callRiskyCode (JNIEnv *, jclass); #ifdef __cplusplus } #endif #endif |
and
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class recipeNo015_SigSegvNoHandler */ #ifndef _Included_recipeNo015_SigSegvNoHandler #define _Included_recipeNo015_SigSegvNoHandler #ifdef __cplusplus extern "C" { #endif /* * Class: recipeNo015_SigSegvNoHandler * Method: callRiskyCodeNoHandler * Signature: ()V */ JNIEXPORT void JNICALL Java_recipeNo015_SigSegvNoHandler_callRiskyCodeNoHandler (JNIEnv *, jclass); #ifdef __cplusplus } #endif #endif |
These files declare methods that will be called from Java.
The mechanism is very simple. The C code will call riskyCode function two different ways. First time, it will wrap the call with signal handlers. Second time, it will call it as is – no signal handling or whatsoever. If you are completely unfamiliar with signal handlers, make sure to check the info checking man pages – man signal. In our case, we will be interested in two signals (underlined).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
+----+-----------+-------------------+-------------------------------------------------+ | No | Name | Default Action | Description | +----+-----------+-------------------+-------------------------------------------------+ | 1 | SIGHUP | terminate process | terminal line hangup | ........................................................................................ ........................................................................................ ........................................................................................ |_10_|_SIGBUS____|_create_core_image_|_bus_error_______________________________________| |_11_|_SIGSEGV___|_create_core_image_|_segmentation_violation__________________________| ........................................................................................ ........................................................................................ ........................................................................................ | 31 | SIGUSR2 | terminate process | User defined signal 2 | +----+-----------+-------------------+-------------------------------------------------+ |
(for the full list of signals take a look at Appendix A)
The point here is that for catching illegal access to memory OS X uses SIGBUS while Linux uses SIGSEGV. So, we handle both signals in the code. As for the implementation, we can put everything inside a single file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
#include <stdio.h> #include <signal.h> #include <setjmp.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "jni.h" #include "recipeNo015_SigSegv.h" #include "recipeNo015_SigSegvNoHandler.h" jmp_buf ljenv; // OS X uses SIGBUS in case of accessing incorrect memory region // Linux will use SIGSEGV - this is why we should use two handlers // there are 31 possible signals we can handle struct sigaction actions[31]; // this function will set the handler for a signal void setup_signal_handler(int sig, void (*handler)( ), struct sigaction *old) { struct sigaction action; // fill action structure // pointer to function that will handle signal action.sa_handler = handler; // for the masks description take a look // at "man sigaction" sigemptyset(&(action.sa_mask)); sigaddset(&(action.sa_mask), sig); // you can bring back original signal using // SA_RESETHAND passed inside sa_flags action.sa_flags = 0; // and set new handler for signal sigaction(sig, &action, &actions[sig - 1]); } // this function will be called whenever signal occurs void handler(int handle) { // be very condense here // just do essential stuff and get // back to the place you want to be write(STDOUT_FILENO, "Hello from handler\n", strlen("Hello from handler\n")); // set original signal handler sigaction(handle, &actions[handle - 1], NULL); // and jump to where we have set long jump siglongjmp(ljenv, 1); } // the purpose of this code is to generate SIGSEGV/SIGBUS void riskyCode() { char *error_str = "This code will fail with SIGSEGV"; *error_str = 'g'; } // riskCode protected by handler // this method will not crash JVM JNIEXPORT void JNICALL Java_recipeNo015_SigSegv_callRiskyCode (JNIEnv *env, jclass obj) { // setup signal handlers // signals are counted from 1 - 31. Array indexes are // counted from 0 - 30. This is why we do 10-1 and 11-1 setup_signal_handler(10, handler, &actions[10 - 1]); setup_signal_handler(11, handler, &actions[11 - 1]); // set the long jump for the signal handler // if handler is called it will jump // here with the error code specified // as parameter of siglongjmp // first call to sigsetjmp returns 0 if( sigsetjmp(ljenv,1) == 0) { // call the code that will fail riskyCode(); } else { // we can allocate a little bit more than we require char exceptionBuffer[1024]; sprintf(exceptionBuffer, "Error"); (*env)->ThrowNew( env, (*env)->FindClass( env, "java/lang/Exception"), exceptionBuffer); } // if everything was OK, we can set old handlers sigaction(10, &actions[10 - 1], NULL); sigaction(11, &actions[11 - 1], NULL); } // unprotected call to riskyCode; JVM will crash completely // and will produce hs_er file JNIEXPORT void JNICALL Java_recipeNo015_SigSegvNoHandler_callRiskyCodeNoHandler (JNIEnv *env, jclass obj) { // we are calling risky code, but we don't set SIGBUS/SIGSEGV handler riskyCode(); } |
After we compile everything
1 2 3 4 5 6 7 8 9 10 11 12 13 |
shell> make clean; make rm -rfv target/* target/recipeNo015/SigSegv.class target/recipeNo015/SigSegvNoHandler.class target/recipeNo015 rm c/recipeNo015_SigSegv.h rm c/recipeNo015_SigSegvNoHandler.h rm -rf lib/* /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/bin/javac -h c -d target java/recipeNo015/SigSegv.java /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/bin/javac -h c -d target java/recipeNo015/SigSegvNoHandler.java cc -g -shared -fpic -I/Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/include/darwin c/recipeNo015_SigSegv.c -o lib/libSigSegv.dylib |
We get he library and compiled Java code. Now, we can run it
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
shell> make test /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/bin/java -Djava.library.path=:./lib -cp target recipeNo015.SigSegv library: :./lib Hello from handler Call to callRiskyCode() finished with exception /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/bin/java -Djava.library.path=:./lib -cp target recipeNo015.SigSegvNoHandler library: :./lib # # A fatal error has been detected by the Java Runtime Environment: # # SIGBUS (0xa) at pc=0x000000011d036d63, pid=655, tid=6403 # # JRE version: Java(TM) SE Runtime Environment (8.0-b132) (build 1.8.0-b132) # Java VM: Java HotSpot(TM) 64-Bit Server VM (25.0-b70 mixed mode bsd-amd64 compressed oops) # Problematic frame: # C [libSigSegv.dylib+0xd63] riskyCode+0x13 # # Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again # # An error report file with more information is saved as: # /Users/michalo/Documents/pandora/trunk/private/JNICookBook/jnicookbook/recipeNo015/hs_err_pid655.log # # If you would like to submit a bug report, please visit: # http://bugreport.sun.com/bugreport/crash.jsp # The crash happened outside the Java Virtual Machine in native code. # See problematic frame for where to report the bug. # make: *** [test] Abort trap: 6 |
As you can see, first call caught the SIGSEGV/SIGBUS and we can see both, message from signal handler (Hello from handler) and Exception related message from Java code (Call to callRiskyCode() finished with exception). In second case, where no signals are caught, we can see that JVM crashes after signal is raised.