I have a minimal Android app created with the new project wizard with c++ support enabled. The purpose of the app is to allow c++ to call back into java after catching a signal (SIGSEGV). The sequence of the program is short and sweet, pseudo code would be like:
- Enter Native Method
handleSegv()
- Native code calls back into java as a test
- Native code sets up SIGSEGV handler
- Enter Native Method
sendSegv()
- Native code raises/sends SIGSEGV
- Enter Native Method
signal_handler
- Native code catches signal and logs it
- Native code calls back into java
- Native code logs again to show its stepped past the callback
The only step above that isn't working is step 3.2
. It seems that after catching SIGSEGV
nothing happens when the native code tries to call back into java. I've tried this both in the emulator and on the device with the same results. I'm not sure at this point if I'm doing something wrong or if there is something fundamental about handling the signal that won't allow me to call back into java after catching it.
I have code demonstrating this which can be cloned from the repo from on github but really there are only two source files:
package com.kevinkreiser.crashtest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class CrashActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crash);
//setup segv handler
handleSegv();
//cause a segv
sendSegv();
}
/**
* Sets up signal handler for SIGSEGV which will call the callback function below
* @return true if the handler was set
*/
public native boolean handleSegv();
/**
* Raises the SIGSEGV signal which will cause the handler to be called
*/
public native void sendSegv();
/**
* A function that the native code will call back when it receives SIGSEGV
* as an illustration it just logs
*
* @param message The message coming back from c++
*/
public void callback(String message) {
Log.e("CrashActivity.callback", message);
}
}
#include <android/log.h>
#include <jni.h>
#include <string.h>
#include <signal.h>
#include <string>
//globals persisting between calls from javaland
static JavaVM* vm = NULL;
static jobject activity = NULL;
static jmethodID callback = NULL;
//gets called first when a signal is sent to the running pid
static void signal_handler(int signal, siginfo_t*, void*) {
//get an env so we can call back to java
JNIEnv* env;
if(vm->AttachCurrentThread(&env, NULL) != JNI_OK)
return;
//call back to java with a message
__android_log_print(ANDROID_LOG_ERROR, "native-lib.signal_handler", "Calling with signal %d", signal);
std::string message = "Got signal " + std::to_string(signal);
jstring msg = env->NewStringUTF(message.c_str());
env->CallVoidMethod(activity, callback, msg);
__android_log_print(ANDROID_LOG_ERROR, "native-lib.signal_handler", "Called with signal %d", signal);
}
extern "C" JNIEXPORT void JNICALL
Java_com_kevinkreiser_crashtest_CrashActivity_sendSegv(JNIEnv*, jobject) {
raise(SIGSEGV);
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_kevinkreiser_crashtest_CrashActivity_handleSegv(JNIEnv* env, jobject obj) {
//get java hooks we need to make the callback
env->GetJavaVM(&vm);
activity = env->NewGlobalRef(obj);
if (!activity)
return false;
jclass activity_class = env->GetObjectClass(activity);
if (!activity_class)
return false;
callback = env->GetMethodID(activity_class, "callback", "(Ljava/lang/String;)V");
if (!callback)
return false;
//try calling back to java with a message
jstring message = env->NewStringUTF("No signal yet");
env->CallVoidMethod(activity, callback, message);
//register for SIGSEGV
struct sigaction action;
memset(&action, 0, sizeof(struct sigaction));
action.sa_sigaction = signal_handler;
action.sa_flags = SA_SIGINFO;
sigaction(SIGSEGV, &action, NULL);
return true;
}
When I run the program and I look at logcat
s output I see the following:
2019-01-15 11:59:50.795 11183-11183/com.kevinkreiser.crashtest E/CrashActivity.callback: No signal yet
2019-01-15 11:59:50.795 11183-11183/com.kevinkreiser.crashtest E/native-lib.signal_handler: Calling with signal 11
2019-01-15 11:59:50.795 11183-11183/com.kevinkreiser.crashtest E/native-lib.signal_handler: Called with signal 11
If I step through the program with the debugger and set a breakpoint in the native signal_handler
I can step down to the line where it logs the first time Calling with signal...
. After this if I step over any line that includes a call using the JNIEnv
(env
in this case) the debugger will detatch and the program will finish. You'll note that from the logcat
output though, that I do get the last native log line Called with signal...
after the calls making use of env
and most importantly the call back into java.
I've seen other implementations here on stackoverflow that do essentially this but I've not been able to get any of them to work. I've also tried throwing a java exception from the native code but that ends up also not getting back to javaland with a message about pending exceptions. Can anyone see what is wrong here? Thanks in advance!