Sometimes you have an access to surce code of the library that you want to use. In that case, you can take slightly different approach when it comes to exit call (man 3 exit).
Instead of installing atexit handler that is application wide, you can provide a specific handler for the library. All you have to do is to use “-Dexit=new_handler_name” option. This option will make all calls to exit to be replaced with calls to new_handler_name.
In this sample, I will show how to utilize this approach.
After you are finished with the tutorial, your tree structure for the project should look like this:
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 |
recipeNo017 ├── Makefile ├── c │ ├── extLib.c │ ├── extLibCompiled.c │ ├── recipeNo017_SigTerm.c │ ├── recipeNo017_SigTerm.h │ ├── recipeNo017_SigTermExtLib.h │ ├── stopExit.c │ └── stopExit.h ├── java │ └── recipeNo017 │ ├── SigTerm.java │ └── SigTermExtLib.java ├── lib │ ├── libExtLib.dylib │ ├── libExtLib.dylib.dSYM │ │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ └── DWARF │ │ └── libExtLib.dylib │ ├── libExtLibCompiled.dylib │ ├── libExtLibCompiled.dylib.dSYM │ │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ └── DWARF │ │ └── libExtLibCompiled.dylib │ ├── libSigTerm.dylib │ ├── libSigTerm.dylib.dSYM │ │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ └── DWARF │ │ └── libSigTerm.dylib │ ├── libStopExit.dylib │ └── libStopExit.dylib.dSYM │ └── Contents │ ├── Info.plist │ └── Resources │ └── DWARF │ └── libStopExit.dylib └── target └── recipeNo017 ├── SigTerm.class └── SigTermExtLib.class |
In this project we have few files that compose the project. First of all, we have two Java classes that call JNI code. First one, SigTerm.java, call JNI code that, in turn, call library compiled with “-Dexit” switch. Second, SigTermExtLib.java, calls the JNI code, that makes a call to some externaly provided function that we have no influence on (we don’t have sources).
Of course, in this case we have sources for both libs, but we will pretend that we haven’t compiled second library.
So, let’s take a look at the sources.
SigTerm class has just a one purpose – it calls callExitCode(). Inside this function, there is a call to exit (eventually). To see how the implementation was done, take a look at extLibCompiled.c and Makefile.
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 |
/* SigTerm.java */ package recipeNo017; public class SigTerm { /* This is the native method we want to call */ public static native void callExitCode(); /* Inside static block we will load shared library */ static { System.loadLibrary("SigTerm"); } 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 { SigTerm.callExitCode(); } catch(Exception ex) { System.out.println("Application tried to call exit"); } } } |
SigTermExtLib class, has slightly different purpose. It call JNI code that will break JVM. This is the result of the call to exit function that was compiled inside shared library we have no access to. We can’t compile this library, this is why it uses exit from the system implementation. In case of handling this kind of codes, take a look at recipeNo016.
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 |
/* SigTermExtLib.java */ package recipeNo017; public class SigTermExtLib { /* This is the native method we want to call */ public static native void callExitCodeExtLib(); /* Inside static block we will load shared library */ static { System.loadLibrary("SigTerm"); } 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 { SigTermExtLib.callExitCodeExtLib(); } catch(Exception ex) { System.out.println("Application tried to call exit"); } } } |
In addition to Java classes we need few more things. First of all, we need the implementation of stopExit function that is used as a new handler for the exit function.
1 2 3 4 5 6 7 8 9 10 11 |
#include <signal.h> #include <setjmp.h> #ifndef _STOP_EXIT_H_ #define _STOP_EXIT_H_ jmp_buf ljenv; #endif |
Note that we jump, somewhere, in the function stopExit. The point here is, that we have to make sure that each code that can potentially call exit function must use sigsetjmp before calling the code from the library. Otherwise, we will jump to some random place.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <signal.h> #include <setjmp.h> #include <stdlib.h> #include "stopExit.h" extern jmp_buf ljenv; // this is the handler for the risky code // if we reach here, it means somebody // tried to call exit; this time we are declaring // exit to be stopExit void stopExit() { siglongjmp(ljenv, 1); } |
Next piece of the puzzles is the call to exit itself. So, we have two code. First one, calls the exit but is compiled without “-Dexit” switch
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <stdlib.h> #include <stdio.h> // the purpose of this code is to call exit function // we pretend that library was compiled with unknown // settings and is a black box from ou perspective void externalRiskyCode() { printf("Calling exit from C code\n"); exit(1); } |
Second one, does the same thing. The only difference is that it will be compiled with “-Dexit” switch. Can you see that the code is the same code as previous one? At least in terms of the execution.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <stdlib.h> #include <stdio.h> void stopExit(); // In this case we have an access to source code // We can compile is using settings that suit us best void externalRiskyCodeCompiled() { printf("Calling exit from C code that we have compiled\n"); exit(1); } |
The last piece of the solution is the JNI code itself. Here, we have two calls. Both are wrapped with sigsetjmp call and both call very similar functions. The difference here is that first call will prevent exit from killing whole JVM, while second call, will actually close the whole JVM.
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 |
#include <stdio.h> #include <signal.h> #include <setjmp.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "jni.h" #include "recipeNo017_SigTerm.h" #include "recipeNo017_SigTermExtLib.h" extern jmp_buf ljenv; // these are the risky codes that call exit function void externalRiskyCode(); void externalRiskyCodeCompiled(); // call to risky code compiled with -Dexit // Here, JVM will catch exception and handle it JNIEXPORT void JNICALL Java_recipeNo017_SigTerm_callExitCode (JNIEnv *env, jclass obj) { // set the long jump for the exit call 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 try to exit externalRiskyCodeCompiled(); } else { // if the code tries to call exit, we are handling it // 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); } } // call to risky code that was compile without -Dexit // Here, JVM will fail to handle the exit call. It will close itself. JNIEXPORT void JNICALL Java_recipeNo017_SigTermExtLib_callExitCodeExtLib (JNIEnv *env, jclass obj) { // set the long jump for the exit call 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 try to exit externalRiskyCode(); } else { // if the code tries to call exit, we are handling it // 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); } } |
When we compile and execute the code, you can see the different behavior of JVM.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
shell> make /Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/bin/javac -h c -d target java/recipeNo017/SigTerm.java /Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/bin/javac -h c -d target java/recipeNo017/SigTermExtLib.java # This compilation pretends that we work with binary lib that we can't compile cc -g -shared -fpic c/extLib.c -o lib/libExtLib.dylib # This is just a handler to exit (our new implementation of exit) cc -g -shared -fpic c/stopExit.c -o lib/libStopExit.dylib # This compilation pretends that we work with library we can compile from the sources # We have to add -Dexit and we have to add library with new exit call handler cc -Dexit=stopExit -g -shared -fpic c/extLibCompiled.c -Llib -lStopExit -o lib/libExtLibCompiled.dylib # This is the final library for JNI calls cc -Dexit=stopExit -g -shared -fpic -I/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/include/darwin c/recipeNo017_SigTerm.c -Llib -lExtLib -lExtLibCompiled -lStopExit -o lib/libSigTerm.dylib |
Now, take a look at execution itself.
1 2 3 4 5 6 7 8 9 10 11 |
shell> make test /Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/bin/java -Djava.library.path=:./lib -cp target recipeNo017.SigTerm library: :./lib Calling exit from C code that we have compiled Application tried to call exit /Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/bin/java -Djava.library.path=:./lib -cp target recipeNo017.SigTermExtLib library: :./lib Calling exit from C code make: *** [test] Error 1 |
As you can see, it is possible to prevent JNI library, contating call to exit, from killing the whole JVM.