У меня есть минимальное приложение для Android, созданное с помощью мастера новых проектов с включенной поддержкой c ++. Цель приложения - позволить c ++ перезвонить в java после перехвата сигнала (SIGSEGV). Последовательность программы короткая и приятная, псевдокод будет выглядеть так:
- Введите собственный метод
handleSegv()
- Собственный код возвращает обратно в Java в качестве теста
- Собственный код устанавливает обработчик SIGSEGV
- Введите собственный метод
sendSegv()
- Собственный код повышает / отправляет SIGSEGV
- Введите собственный метод
signal_handler
- Собственный код ловит сигнал и регистрирует его
- Собственный код перезванивает в Java
- Собственный код снова регистрируется, чтобы показать, что он прошел мимо обратного вызова
Единственный вышеприведенный шаг, который не работает, это шаг 3.2
. Кажется, что после перехвата SIGSEGV
ничего не происходит, когда нативный код пытается перезвонить в java. Я пробовал это как в эмуляторе, так и на устройстве с одинаковыми результатами. На данный момент я не уверен, что делаю что-то не так или есть что-то фундаментальное в обработке сигнала, которое не позволит мне перезвонить в java после его перехвата.
У меня есть код, демонстрирующий это, который можно клонировать из репозитория из на github , но на самом деле есть только два исходных файла:
CrashActivity.java
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);
}
}
нативной lib.cpp :
#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;
}
Когда я запускаю программу и смотрю вывод logcat
s, я вижу следующее:
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
Если я перехожу через программу с помощью отладчика и устанавливаю точку останова в нативном signal_handler
, я могу перейти к строке, в которой он впервые регистрируется Calling with signal...
. После этого, если я перейду через любую строку, которая включает вызов с использованием JNIEnv
(env
в данном случае), отладчик отключится и программа завершит работу. Однако вы заметите, что из вывода logcat
я получаю последнюю собственную строку журнала Called with signal...
после вызовов, использующих env
и, что наиболее важно, обратный вызов в java.
Я видел другие реализации здесь в stackoverflow, которые делают это по существу, но я не смог заставить ни одну из них работать. Я также пытался выдать исключение Java из нативного кода, но в итоге я не возвращаюсь в javaland с сообщением об ожидающих исключениях. Кто-нибудь может увидеть, что здесь не так? Заранее спасибо!